Почему эта программа-декоратор выдает неожиданный вывод? - PullRequest
0 голосов
/ 08 ноября 2018

Я написал следующую программу для предоставления оболочек (декораторов) для двух функций 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 Ответ

0 голосов
/ 10 ноября 2018

Сложнее, чем нужно, чтобы точно увидеть, что происходит с вашим кодом, потому что вы выбрали непонятные имена при написании вашего декоратора. Вот версия, которая делает то же самое, что и ваш код, с измененными именами:

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)

Здесь есть три изменения:

  • wrapperhead_and_foot
  • head_and_footwrapper
  • reportfunc

Функция, которую вы назвали wrapper, которую я переименовал в head_and_foot, является декоратором . Это означает, что она принимает функцию в качестве аргумента и возвращает другую функцию, которая предназначена для замены принятой.

Обычно функция замены, которую она возвращает, является оболочкой для исходной функции, что означает, что она делает то же самое, что и исходная функция, заключенная в некоторые дополнительные действия.

Чтобы сохранить все это, принято вызывать декоратор по имени, описывающему его эффект (например, head_and_foot, вызывать функцию, которую он принимает func, и вызывать оболочку, которую он возвращает wrapper. Это то, что я ' мы сделали выше.

После того, как у вас появятся разумные имена, вам будет легче понять, что у вас есть две проблемы:

  1. wrapper должен быть заменой для декорируемых функций, поэтому он должен иметь такую ​​же сигнатуру - это означает, что он должен принимать то же число и тип аргументов. Ваши функции price_report и sales_report вообще не принимают никаких аргументов (т. Е. Между скобками () в их выражении def нет ничего), но wrapper принимает функцию, которую предполагается заменить, в качестве аргумента, что не имеет никакого смысла.

    Эта строка должна быть просто def wrapper():, чтобы соответствовать сигнатуре заменяемых функций.

  2. Предполагается, что декоратор возвращает функцию замены, но ваш декоратор вызывает замену и возвращает результат. Вместо 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 
...