Почему эта программа-декоратор выдает неожиданный вывод?
Я написал следующую программу, чтобы обеспечить обертки (декораторы) для двух функций 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 или из командной строки как 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
1 ответ
Сложнее, чем нужно, чтобы точно увидеть, что происходит с вашим кодом, потому что вы выбрали непонятные имена при написании вашего декоратора. Вот версия, которая делает то же самое, что и ваш код, с измененными именами:
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