python - 为什么这个装饰器程序会产生意外的输出?

标签 python python-3.x python-decorators

我编写了以下程序,为 price_reportsales_report 这两个函数提供包装器(装饰器)。我刚刚将包装器分配给这些函数(下面代码中的最后两行),而没有显式调用 price_report()sales_report()。但该代码会产生如下所示的输出。怎么会这样?

事实上,如果我显式调用 price_report(),我会收到错误消息 TypeError: 'NoneType' object is not callable

# wrapper.py

def wrapper(report):
    def head_and_foot(report):
        print(report.__name__)
        report()
        print("End of", report.__name__, "\n\n")
    return head_and_foot(report)

def price_report():
    cars  = ['Celerio', 'i10', 'Amaze', 'Figo']
    price = [500_000, 350_000, 800_000, 550_000]
    for x, y in zip(cars, price):
        print(f'{x:8s}', f'{y:8,d}')

def sales_report():
    cars  = ['Celerio', 'i10', 'Amaze', 'Figo']
    units = [5000, 3000, 1000, 800]
    for x, y in zip(cars, units):
        print(f'{x:8s}', f'{y:8,d}')

sales_report = wrapper(sales_report)
price_report = wrapper(price_report)

上述程序的输出(无论是在 Jupyter Notebook 中运行还是从命令行作为 pythonwrapper.py):

sales_report
Celerio     5,000
i10         3,000
Amaze       1,000
Figo          800
End of sales_report


price_report
Celerio   500,000
i10       350,000
Amaze     800,000
Figo      550,000
End of price_report

最佳答案

准确地了解代码发生了什么比需要的更困难,因为您在编写装饰器时选择了令人困惑的名称。这是一个与您的代码执行完全相同操作的版本,但名称已更改:

def head_and_foot(func):
    def wrapper(func):
        print(func.__name__)
        func()
        print("End of", func.__name__, "\n\n")
    return wrapper(func)

def price_report():
    cars  = ['Celerio', 'i10', 'Amaze', 'Figo']
    price = [500_000, 350_000, 800_000, 550_000]
    for x, y in zip(cars, price):
        print(f'{x:8s}', f'{y:8,d}')

def sales_report():
    cars  = ['Celerio', 'i10', 'Amaze', 'Figo']
    units = [5000, 3000, 1000, 800]
    for x, y in zip(cars, units):
        print(f'{x:8s}', f'{y:8,d}')

sales_report = head_and_foot(sales_report)
price_report = head_and_foot(price_report)

这里有三处变化:

  • 包装head_and_foot
  • head_and_foot包装
  • 报告func

您称为 wrapper 的函数(我已将其重命名为 head_and_foot)是一个装饰器。这意味着它接受一个函数作为参数,并返回另一个函数来替换它接受的函数。

通常,它返回的替换函数是原始函数的包装器,这意味着它执行与原始函数相同的操作,但包含一些额外的操作。

为了保持这一切的顺利进行,通常通过描述其效果的名称来调用装饰器(例如head_and_foot,调用它接受的函数func,并调用包装器它返回wrapper。这就是我上面所做的。

一旦你有了合理的名称,就更容易发现你有两个问题:

  1. wrapper 应该是被修饰函数的替代品,因此它应该具有相同的签名 - 这意味着它应该采用相同的数字和参数类型。您的函数 price_reportsales_report 根本不接受任何参数(即,它们的 def< 中的括号 () 之间没有任何内容 语句),但是 wrapper 将它应该替换的函数作为参数,这根本没有意义。

    该行应该只是 defwrapper(): 以匹配被替换函数的签名。

  2. 装饰器应该返回替换函数,但您的装饰器正在调用替换函数并返回结果。您只需要返回包装器,而不是返回包装器(func)

进行这两项更改后,我们最终得到以下结果:

def head_and_foot(func):
    def wrapper():
        print(func.__name__)
        func()
        print("End of", func.__name__, "\n\n")
    return wrapper

def price_report():
    cars  = ['Celerio', 'i10', 'Amaze', 'Figo']
    price = [500_000, 350_000, 800_000, 550_000]
    for x, y in zip(cars, price):
        print(f'{x:8s}', f'{y:8,d}')

def sales_report():
    cars  = ['Celerio', 'i10', 'Amaze', 'Figo']
    units = [5000, 3000, 1000, 800]
    for x, y in zip(cars, units):
        print(f'{x:8s}', f'{y:8,d}')

sales_report = head_and_foot(sales_report)
price_report = head_and_foot(price_report)

当我们运行这个固定代码时,我们不会得到任何意外的输出,但我们确实得到了两个函数来完成我们所期望的操作:

>>> price_report()
price_report
Celerio   500,000
i10       350,000
Amaze     800,000
Figo      550,000
End of price_report 


>>> sales_report()
sales_report
Celerio     5,000
i10         3,000
Amaze       1,000
Figo          800
End of sales_report 

关于python - 为什么这个装饰器程序会产生意外的输出?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53201565/

相关文章:

python - 测试使用 mysql 的 python 应用程序

python - 如何更有效地修改 pandas 数据框列

python - 如何修复简单自动编码器中的尺寸错误?

python - 内存使用实例属性的函数

python - 从另一个 View 调用装饰器

python - Django REST 框架查询集对象没有属性 pk

python - 将 Unicode 转换为二进制

python - 分配一个 pandas dataframe NULL=0, non-NULLvalue=1

python - 给定需要添加的空格数量,通过在单词之间添加空格来格式化字符串

Python:装饰器可以从 foo1() 访问参数并将其提供给 foo2() 吗?