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

Posted

技术标签:

【中文标题】Python 修饰函数中的参数如何工作【英文标题】:How arguments in Python decorated functions work 【发布时间】:2018-07-07 07:29:42 【问题描述】:

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

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>

【问题讨论】:

你的直觉是正确的。 my_decorator 返回 wrapper 函数。 @ 语法展开到以下调用:my_function = my_decorator(my_function)。因此,它实际上将您最初定义的函数指针替换为它构建的函数指针来代替它的调用。 【参考方案1】:

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了解更多。

【讨论】:

【参考方案2】:

你的理解是完全正确的。装饰器语法只是语法糖,以下几行:

@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 现在绑定到在您的my_decorator() 函数中创建的wrapper() 函数。 original 函数对象作为 func 参数传递到 my_decorator(),因此仍可用于 wrapper() 函数,作为 闭包。所以调用func()会调用原来的函数对象。

所以当你调用修饰的my_function(1) 对象时,你真的调用了wrapper(1)。此函数通过名称func_arg 接收1,然后wrapper() 本身调用func(func_arg),这是原始函数对象。所以最后还是把原来的函数传给1too

你可以在解释器中看到这个结果:

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

【讨论】:

以上是关于Python 修饰函数中的参数如何工作的主要内容,如果未能解决你的问题,请参考以下文章

python中的@

python中的修饰符@的作用

delphi如何调用多个参数的函数

python中怎么设定函数形参的类型

python3修饰器简单理解

python如何使用__new__()函数的参数列表中的属性创建类?