我想知道如何在另一个函数中访问一个函数。我看到了这样的代码:
>>> def make_adder(x):
def adder(y):
return x+y
return adder
>>> a = make_adder(5)
>>> a(10)
15
因此,还有另一种方法可以调用adder
函数吗?我的第二个问题是为什么在最后一行中我叫adder
而不是adder(...)
?
很好的解释,不胜感激。
不,您不能直接调用它,因为它是make_adder
的局部变量。
您需要使用adder()
,因为调用return adder
时adder
返回了功能对象make_adder(5)
。要执行此功能对象,您需要()
def make_adder(x):
def adder(y):
return x+y
return adder
...
>>> make_adder(5) #returns the function object adder
<function adder at 0x9fefa74>
在这里您可以直接调用它,因为您可以访问它,因为它是由函数make_adder
返回的。返回的对象实际上称为closure,因为即使函数make_addr
已经返回,但返回的函数对象adder
仍然可以访问变量x
。在py3.x中,您还可以使用x
语句修改nonlocal
的值。
>>> make_adder(5)(10)
15
Py3.x示例:
>>> def make_addr(x):
def adder(y):
nonlocal x
x += 1
return x+y
return adder
...
>>> f = make_addr(5)
>>> f(5) #with each call x gets incremented
11
>>> f(5)
12
#g gets it's own closure, it is not related to f anyhow. i.e each call to
# make_addr returns a new closure.
>>> g = make_addr(5)
>>> g(5)
11
>>> g(6)
13
您确实不想掉进这个兔子洞,但是如果您坚持,那是可能的。有一些工作。
为每次调用make_adder()
创建嵌套函数anew:
>>> import dis
>>> dis.dis(make_adder)
2 0 LOAD_CLOSURE 0 (x)
3 BUILD_TUPLE 1
6 LOAD_CONST 1 (<code object adder at 0x10fc988b0, file "<stdin>", line 2>)
9 MAKE_CLOSURE 0
12 STORE_FAST 1 (adder)
4 15 LOAD_FAST 1 (adder)
18 RETURN_VALUE
MAKE_CLOSURE
操作码在那里创建了一个带有闭包的函数,一个嵌套函数从父函数引用了x
(LOAD_CLOSURE
操作码为该函数创建了闭包单元。
没有调用make_adder
函数,您只能访问代码对象;它与make_adder()
功能代码一起存储为常数。 adder
的字节码依赖于能够作为作用域单元访问x
变量,但是,这使代码对象几乎对您无用:
>>> make_adder.__code__.co_consts
(None, <code object adder at 0x10fc988b0, file "<stdin>", line 2>)
>>> dis.dis(make_adder.__code__.co_consts[1])
3 0 LOAD_DEREF 0 (x)
3 LOAD_FAST 0 (y)
6 BINARY_ADD
7 RETURN_VALUE
LOAD_DEREF
从封闭单元格加载一个值。为了使代码对象再次成为函数对象,您必须将其传递给函数构造函数:
>>> from types import FunctionType
>>> FunctionType(make_adder.__code__.co_consts[1], globals(),
... None, None, (5,))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: arg 5 (closure) expected cell, found int
但是您可以看到,构造函数希望找到一个闭包,而不是整数值。要创建一个闭包,我们需要一个具有自由变量的函数。被编译器标记为可以结束的代码。并且它需要将那些封闭的值返回给我们,否则无法创建封闭。因此,我们创建了一个嵌套函数,仅用于创建一个闭包:
def make_closure_cell(val):
def nested():
return val
return nested.__closure__[0]
cell = make_closure_cell(5)
现在我们无需调用adder()
即可重新创建make_adder
:
>>> adder = FunctionType(make_adder.__code__.co_consts[1], globals(),
... None, None, (cell,))
>>> adder(10)
15
也许只是调用make_adder()
会更简单。
偶然地,如您所见,函数是Python中的一流对象。 make_adder
是一个对象,通过添加(somearguments)
您invoke或call函数。在这种情况下,该函数返回another函数对象,您也可以调用该对象。在上面关于如何不调用adder()
来创建make_adder()
的曲折示例中,我引用了make_adder
函数对象而不调用它。例如,以反汇编附加到它的Python字节代码,或从中检索常量或闭包。同样,make_adder()
函数返回adder
函数对象。 make_adder()
的point是创建该函数供以后调用的其他函数。
上面的会议是在考虑到Python 2和3之间的兼容性的情况下进行的。较旧的Python 2版本以相同的方式工作,尽管其中一些细节有所不同。例如,某些属性具有不同的名称,例如func_code
而不是__code__
。如果您想了解具体细节,请查阅inspect
module和inspect
中的文档。
您将函数Python datamodel返回给调用者,而不是调用它的结果,因此没有括号。
因为adder
返回make_adder
,所以您已经可以直接访问adder
。实际上,adder
实际上是对a(10)
的调用。
作为@AshwiniChaudhary答案的附录,您可以使用可修改的对象来模拟Python 3.x的非本地语言。例如:
adder(10)
在python2.7中产生:
def counter(name):
x = [0]
def inc(n):
x[0] += n
print "%s: %d" % (name, x[0])
return inc
spam = counter('spams')
ham = counter('hams')
spam(3)
ham(1)
spam(1)
ham(2)
使用$ python closure.py
spams: 3
hams: 1
spams: 4
hams: 3
的原因是尝试重新分配x[0]
以创建新的本地到x
inc
:
x
尝试使用此方法会产生:
def counter(name):
x = 0
def inc(n):
x += n # this doesn't work!
print "%s: %d" % (name, x[0])
return inc
剩余的显而易见的事情,尝试使用Traceback (most recent call last):
File "closure.py", line 11, in <module>
spam(3)
File "closure.py", line 4, in inc
x += n
UnboundLocalError: local variable 'x' referenced before assignment
,也因为尝试访问模块级global
而不是x
内部的模块而失败。 (这就是为什么首先添加counter
的原因!)
关于闭包的另一点:它们可以机械地转换为带有实例变量的类。除了定义上面的nonlocal
,我可以创建一个类:
counter
然后将其用作:
class Counter(object):
def __init__(self, name):
self.name = name
self.x = 0
def inc(self, n):
self.x += n
print "%s: %d" % (self.name, self.x)
例如。如果您想保留调用语法,Python允许这样做:不用定义spam = Counter('spams')
spam.inc(3)
,而是定义inc(self, n)
或将__call__(self, n)
定义为调用__call__
,从而产生:
inc
这显示了类中有点精神分裂的“两种调用方式”界面。 :-)
我不确定这是否有帮助,但是您可以使用class Counter(object):
def __init__(self, name):
self.name = name
self.x = 0
def inc(self, n):
self.x += n
print "%s: %d" % (self.name, self.x)
__call__ = inc
spam = Counter('spams')
ham = Counter('hams')
spam.inc(3)
ham.inc(1)
spam(1)
ham(2)
样板来模拟所需的行为。例如:
__call__