Python中的参数如何使函数起作用

isf*_*f85 3 python function python-3.x python-decorators

我很难理解如何将参数传递给装饰器内的包装器函数.举一个简单的例子:

def my_decorator(func):
    def wrapper(func_arg):
        print('Before')
        func(func_arg)
        print('After')
    return wrapper

@my_decorator
def my_function(arg):
    print(arg + 1)

my_function(1)
Run Code Online (Sandbox Code Playgroud)

我有一个函数,需要1个参数,它被装饰.我在理解func_arg如何工作方面遇到了麻烦.调用my_function(1)时,值1如何传递给包装器.根据我对此的一点理解,my_function被一个新函数'替换',如:my_function = my_decorator(my_function).

print(my_function)
<function my_decorator.<locals>.wrapper at 0x7f72fea9c620>
Run Code Online (Sandbox Code Playgroud)

Mar*_*ers 6

你的理解是完全正确的.装饰器语法只是语法糖,行:

@my_decorator
def my_function(arg):
    print(arg + 1)
Run Code Online (Sandbox Code Playgroud)

被执行为

def my_function(arg):
    print(arg + 1)

my_function = my_decorator(my_function)
Run Code Online (Sandbox Code Playgroud)

没有my_function实际上已经被设置前的装饰被称为*.

所以my_function现在绑定到wrapper()你创建的函数my_decorator()功能.在函数对象传递到my_decorator()作为func参数,因此仍然是可用的wrapper()功能,作为一个封闭.所以调用func()调用原始函数对象.

因此,当您调用装饰my_function(1)对象时,您真的会打电话wrapper(1).此函数1通过名称接收func_arg,wrapper()然后自己调用func(func_arg),这是原始函数对象.所以最后,原来的功能也被传递1 .

您可以在解释器中看到此结果:

>>> def my_decorator(func):
...     def wrapper(func_arg):
...         print('Before')
...         func(func_arg)
...         print('After')
...     return wrapper
...
>>> @my_decorator
... def my_function(arg):
...     print(arg + 1)
...
>>> my_function
<function my_decorator.<locals>.wrapper at 0x10f278ea0>
>>> my_function.__closure__
(<cell at 0x10ecdf498: function object at 0x10ece9730>,)
>>> my_function.__closure__[0].cell_contents
<function my_function at 0x10ece9730>
>>> my_function.__closure__[0].cell_contents(1)
2
Run Code Online (Sandbox Code Playgroud)

可以通过__closure__属性访问闭包,您可以通过属性访问闭包的当前值cell_contents.在这里,这是原始装饰的功能对象.

请务必注意,每次调用时my_decorator(),都会创建一个新的函数对象.它们都被命名,wrapper()但它们是独立的对象,每个对象都有自己的对象__closure__.


* Python生成字节码,用于创建函数对象而不将其赋值给名称; 它生活在堆栈上.然后,下一个字节码指令调用装饰器对象:

>>> import dis
>>> dis.dis(compile('@my_decorator\ndef my_function(arg):\n    print(arg + 1)\n', '', 'exec'))
  1           0 LOAD_NAME                0 (my_decorator)
              2 LOAD_CONST               0 (<code object my_function at 0x10f25bb70, file "", line 1>)
              4 LOAD_CONST               1 ('my_function')
              6 MAKE_FUNCTION            0
              8 CALL_FUNCTION            1
             10 STORE_NAME               1 (my_function)
             12 LOAD_CONST               2 (None)
             14 RETURN_VALUE
Run Code Online (Sandbox Code Playgroud)

所以首先LOAD_NAME查找my_decorator名称.接下来,加载为函数对象生成的字节码以及函数的名称.MAKE_FUNCTION从这两条信息中创建函数对象(从堆栈中删除它们)并将生成的函数对象重新放入.CALL_FUNCTION然后在堆栈上获取一个参数(它的操作数1告诉它要采用多少个位置参数),并调用堆栈上的下一个对象(加载装饰器对象).然后将该调用的结果存储在名称下my_function.