如何在函数内部访问函数?

问题描述 投票:12回答:5

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

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

因此,还有另一种方法可以调用adder函数吗?我的第二个问题是为什么在最后一行中我叫adder而不是adder(...)

很好的解释,不胜感激。

python function
5个回答
7
投票

不,您不能直接调用它,因为它是make_adder的局部变量。

您需要使用adder(),因为调用return adderadder返回了功能对象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

19
投票

您确实不想掉进这个兔子洞,但是如果您坚持,那是可能的。有一些工作。

为每次调用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操作码在那里创建了一个带有闭包的函数,一个嵌套函数从父函数引用了xLOAD_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)invokecall函数。在这种情况下,该函数返回another函数对象,您也可以调用该对象。在上面关于如何不调用adder()来创建make_adder()的曲折示例中,我引用了make_adder函数对象而不调用它。例如,以反汇编附加到它的Python字节代码,或从中检索常量或闭包。同样,make_adder()函数返回adder函数对象。 make_adder()point是创建该函数供以后调用的其他函数。

上面的会议是在考虑到Python 2和3之间的兼容性的情况下进行的。较旧的Python 2版本以相同的方式工作,尽管其中一些细节有所不同。例如,某些属性具有不同的名称,例如func_code而不是__code__。如果您想了解具体细节,请查阅inspect moduleinspect中的文档。


4
投票

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

因为adder返回make_adder,所以您已经可以直接访问adder。实际上,adder实际上是对a(10)的调用。


1
投票

作为@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

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


0
投票

我不确定这是否有帮助,但是您可以使用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__
© www.soinside.com 2019 - 2024. All rights reserved.