Python装饰函数中的参数如何工作

时间:2018-01-28 13:53:54

标签: python python-3.x function 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)

我有一个带有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>

2 个答案:

答案 0 :(得分:6)

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

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

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

my_function = my_decorator(my_function)

在装饰者被称为 * 之前,实际上没有设置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

可以通过__closure__属性访问闭包,您可以通过cell_contents属性访问闭包的当前值。这是原始修饰的函数对象。

请务必注意,每次调用my_decorator()时,都会创建一个 new 函数对象。它们都是wrapper(),但它们是单独的对象,每个对象都有自己的__closure__

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

>>> 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

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

答案 1 :(得分:2)

Python decorator是一个函数,它将另一个函数作为参数,生成一个新函数。

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 = my_decorator(my_function)

这意味着此装饰器的返回值将替换原始函数定义。

只需在另一个答案中添加几点。您可以通过任意数量的其他功能(装饰器的堆叠)来装饰您的my_function。当我们堆叠多个装饰器时,执行顺序是从最里面到最外层。

例如:

@i_get_called_last
@i_get_called_second 
@i_get_called_first 
def my_function(arg): 
   print(arg + 1)

在这里,您需要确保第一个和第二个被调用的装饰器返回兼容的函数,前面的装饰器可以使用它们。

您可以通过this了解更多内容。