13

我想知道如何在另一个函数中访问一个函数。我看到这样的代码:

>>> def make_adder(x):
      def adder(y):
        return x+y
      return adder
>>> a = make_adder(5)
>>> a(10)
15

那么,还有另一种调用adder 函数的方法吗?我的第二个问题是为什么在最后一行我adder不打电话adder(...)

非常感谢良好的解释。

4

5 回答 5

25

你真的不想掉进这个兔子洞,但如果你坚持,这是可能的。随着一些工作。

每次调用都会重新创建嵌套函数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 数据模型中查找有关这些内容的文档。

于 2013-06-30T22:14:21.123 回答
7

不,您不能直接调用它,因为它是make_adder.

您需要使用adder(),因为调用时return adder返回了函数对象。要执行此功能对象,您需要addermake_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 中还可以修改xusingnonlocal语句的值。

>>> 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
于 2013-06-30T22:15:16.370 回答
4

您将函数返回adder给调用者,而不是调用它的结果,因此没有括号。

因为make_adder退货adder,您已经可以直接访问adder. 事实上,a(10)实际上是对adder(10).

于 2013-06-30T22:15:05.903 回答
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创建一个新的 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)

这显示了类中有点精神分裂的“两种调用方式”接口。:-)

于 2013-07-01T01:36:16.260 回答
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

于 2020-04-05T04:01:56.220 回答