我想知道如何在另一个函数中访问函数。 我看到这样的代码:
>>> def make_adder(x):
def adder(y):
return x+y
return adder
>>> a = make_adder(5)
>>> a(10)
15
那么,还有另一种方法来调用adder
函数吗?我的第二个问题是为什么在最后一行中我将adder
称为adder(...)
?
非常感谢良好的解释。
答案 0 :(得分:18)
你真的不想走下这个兔子洞,但如果你坚持,那就有可能。做了一些工作。
每次调用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
操作码为函数构建闭包单元格。 / p>
不调用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()
的 point 是为其他东西创建该函数,以便以后调用它。
上面的会话是考虑到Python 2和3之间的兼容性。较旧的Python 2版本的工作方式相同,尽管有些细节略有不同;某些属性具有不同的名称,例如func_code
而不是__code__
。如果您想了解详细信息,请在inspect
module和Python datamodel中查看相关文档。
答案 1 :(得分:6)
不,你不能直接调用它,因为它是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
答案 2 :(得分:4)
您将函数adder
返回给调用者,而不是调用它的结果,因此没有括号。
由于make_adder
返回adder
,您已经可以直接访问adder
。实际上,a(10)
实际上是对adder(10)
的调用。
答案 3 :(得分:1)
作为@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
创建新的本地到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)
然后将其用作:
inc(self, n)
例如。如果您想保留调用语法,Python允许这样做:而不是定义__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)
,从而产生:
{{1}}
它显示了班级中有点精神分裂的“两种方式”。 : - )
答案 4 :(得分:0)
我不确定这是否有帮助,但是您可以使用__call__
样板来模拟所需的行为。例如:
class MakeAdder:
def __init__(self, x):
self.x = x
def __call__(self, y):
return self.x + y
a = MakeAdder(5)
a(10)
15