我想知道如何在另一个函数中访问一个函数。我看到这样的代码:
>>> def make_adder(x):
def adder(y):
return x+y
return adder
>>> a = make_adder(5)
>>> a(10)
15
那么,还有另一种调用adder
函数的方法吗?我的第二个问题是为什么在最后一行我adder
不打电话adder(...)
?
非常感谢良好的解释。
你真的不想掉进这个兔子洞,但如果你坚持,这是可能的。随着一些工作。
每次调用都会重新创建嵌套函数make_adder()
:
>>> 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)
您调用,或调用该函数。在这种情况下,该函数返回另一个函数对象,您也可以调用它。adder()
上面曲折的不调用创建的例子中make_adder()
,我引用了make_adder
函数对象而不调用它;例如,反汇编附加到它的 Python 字节码,或从中检索常量或闭包。同理,make_adder()
函数返回adder
函数对象;的重点是make_adder()
创建该函数以供其他东西稍后调用它。
上述会话是在考虑 Python 2 和 3 之间的兼容性的情况下进行的。较旧的 Python 2 版本以相同的方式工作,尽管一些细节略有不同;某些属性具有不同的名称,例如func_code
代替__code__
,例如。如果您想了解细节,请在inspect
模块和Python 数据模型中查找有关这些内容的文档。
不,您不能直接调用它,因为它是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
。返回的对象实际上称为闭包,因为即使函数make_addr
已经返回,adder
它返回的函数对象仍然可以访问变量x
。在 py3.x 中还可以修改x
usingnonlocal
语句的值。
>>> 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
您将函数返回adder
给调用者,而不是调用它的结果,因此没有括号。
因为make_adder
退货adder
,您已经可以直接访问adder
. 事实上,a(10)
实际上是对adder(10)
.
作为@AshwiniChaudhary 答案的附录,您可以使用可修改的对象来模拟 Python 3.x 的非本地。例如:
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)
在 python2.7 中,这会产生:
$ python closure.py
spams: 3
hams: 1
spams: 4
hams: 3
使用的原因x[0]
是尝试重新分配x
创建一个新的 local-to- 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)
然后将其用作:
spam = Counter('spams')
spam.inc(3)
例如。如果你想保留调用语法,Python 允许这样做:而不是定义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__
样板来模拟您正在寻找的行为。例如:
class MakeAdder:
def __init__(self, x):
self.x = x
def __call__(self, y):
return self.x + y
a = MakeAdder(5)
a(10)
15