我编写了以下程序,为 price_report
和 sales_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
。这就是我上面所做的。
一旦你有了合理的名称,就更容易发现你有两个问题:
wrapper
应该是被修饰函数的替代品,因此它应该具有相同的签名 - 这意味着它应该采用相同的数字和参数类型。您的函数price_report
和sales_report
根本不接受任何参数(即,它们的def< 中的括号
语句),但是()
之间没有任何内容wrapper
将它应该替换的函数作为参数,这根本没有意义。该行应该只是
defwrapper():
以匹配被替换函数的签名。装饰器应该返回替换函数,但您的装饰器正在调用替换函数并返回结果。您只需要
返回包装器
,而不是返回包装器(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/