装饰器,调用没有括号的函数?

时间:2017-05-17 14:35:18

标签: python python-3.x function decorator python-decorators

我正在教自己如何在一些在线教程的帮助下编写代码。我遇到了“装饰者”,我似乎可以理解它是如何工作的但是有些困扰我。这是给出的代码:

def decor(func):
    def wrap():
        print("-----------")
        func()
        print("-----------")
    return wrap

def print_text():
    print("Hello World")

decorated = decor(print_text)
decorated()

output:
-----------
Hello World
-----------

我想要理解的是:

  • 为什么必须调用“return wrap”而不是“return wrap()”?如果你不这样做,你会得到一个“TypeError:'NoneType'对象不可调用。

  • 当我指定了修饰变量的值时。为什么我还必须使用“print_text”而不是“print_text()”,否则如果我这样做会引发相同的TypeError?

  • 当我使用变量“装饰”时。为什么我必须像函数一样调用它(最后添加())。当我用“装饰”或“打印(装饰)”来称它时,它会说完全不同的东西吗?

抱歉愚蠢的问题。但我刚刚开始,所以请耐心等待。另外,请让您的回复初学者友好。谢谢

1 个答案:

答案 0 :(得分:7)

在Python中,几乎所有东西都是一个对象。函数也是对象。您可以按名称引用它们:

>>> def print_text():
...     print("Hello World")
...
>>> print_text   # **no** call here, this is just referencing the object
<function print_text at 0x10e3f1c80>
>>> print_text() # With a call, so now we *run* the function
Hello World

在名称中添加()告诉Python调用该函数,这导致它实际执行函数体,没有调用,它只是显示名称引用的内容。

您也可以将功能对象分配给其他名称。仍然可以调用其他名称,调用函数:

>>> other_name = print_text
>>> other_name
<function print_text at 0x10e3f1c80>
>>> other_name()
Hello World

所以other_name只是对同一个对象的另一个引用,添加()(一个调用表达式)会导致函数对象被执行。 print_text()other_name()执行完全相同的操作,在函数内运行代码。

这就是func内部decor()所指的名称;它是对同一个函数对象的引用。你用decor(print_text)传递了它。仅在稍后,在wrapper()内,表达式func()调用该函数对象。如果您传入了print_text(),则会传入返回函数的None对象,并且无法调用None

>>> return_value = print_text()
Hello World
>>> return_value is None
True
>>> return_value()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'NoneType' object is not callable

接下来,return wrapper将新创建的wrapper函数对象返回给调用者。如果您执行了return wrapper(),则会返回函数结果,而不是函数对象本身。

装饰器的重点是用一个新对象替换原始函数对象,这个对象会执行额外的操作,这就是装饰器返回该替换对象的原因(在您的示例中为wrapper),以便将来当您调用decorated(),在调用原始函数之前和之后调用该包装函数执行额外操作(通过引用func的{​​{1}}名称。)

所以print_text()做的是返回一个 new 函数对象,一个打印的东西,调用传入的函数对象,然后打印其他东西。然后可以使用该新函数对象替换旧的函数对象。