- android - 多次调用 OnPrimaryClipChangedListener
- android - 无法更新 RecyclerView 中的 TextView 字段
- android.database.CursorIndexOutOfBoundsException : Index 0 requested, 光标大小为 0
- android - 使用 AppCompat 时,我们是否需要明确指定其 UI 组件(Spinner、EditText)颜色
我编写了以下程序来为 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 中运行还是从命令行作为 python wrapper.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)
这里有三个变化:
wrapper
→ head_and_foot
head_and_foot
→ wrapper
report
→ func
你调用的函数wrapper
,我已将其重命名为 head_and_foot
, 是一个装饰器。这意味着它接受一个函数作为参数,并返回另一个函数来替换它接受的函数。
通常,它返回的替换函数是原始函数的包装,这意味着它做的事情与原始函数相同,只是包装在一些额外的操作中。
为了保持直截了当,通常通过描述其效果的名称调用装饰器(例如 head_and_foot
,调用它接受的函数 func
,并调用它返回的包装器 wrapper
。这就是我的意思以上已经完成。
一旦你有了合理的名字,就会更容易看出你有两个问题:
wrapper
应该是被装饰的函数的替代品,所以它应该有相同的签名——这意味着它应该采用相同数量和类型的参数。你的功能price_report
和 sales_report
根本不接受任何参数(即 ()
语句中的括号 def
之间没有任何内容),但是 wrapper
将它应该替换的函数作为参数,这根本没有意义。
那一行应该是def wrapper():
以匹配被替换函数的签名。
装饰器应该返回替换函数,但您的装饰器正在调用替换函数并返回结果。而不是 return wrapper(func)
, 你只需要 return wrapper
.
在进行了这两项更改之后,我们得到了这样的结果:
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/
我是一名优秀的程序员,十分优秀!