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

Python 为什么这个装饰程序会产生意外的输出?,python,python-3.x,python-decorators,Python,Python 3.x,Python Decorators,我编写了以下程序,为两个函数price\u report和sales\u report提供包装器(装饰器)。我刚刚将包装器分配给这些函数(下面代码中的最后两行),没有显式调用price\u report()或sales\u report()。但代码生成的输出将在下面进一步显示。为什么 事实上,如果我显式调用price\u report(),我会得到错误消息TypeError:“NoneType”对象不可调用 # wrapper.py def wrapper(report): def h

我编写了以下程序,为两个函数
price\u report
sales\u report
提供包装器(装饰器)。我刚刚将包装器分配给这些函数(下面代码中的最后两行),没有显式调用
price\u report()
sales\u report()
。但代码生成的输出将在下面进一步显示。为什么

事实上,如果我显式调用
price\u report()
,我会得到错误消息
TypeError:“NoneType”对象不可调用

# 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
从命令行运行):


要准确地了解代码中发生了什么事情比需要的要困难得多,因为在编写装饰程序时,您选择了令人困惑的名称。这是一个与代码完全相同的版本,名称已更改:

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
    → <代码>头和脚
  • 头和脚
    → <代码>包装器
  • 报告
    → <代码>函数
  • 您调用的
    wrapper
    (我已将其重命名为
    head\u and\u foot
    )函数是一个装饰器。这意味着它接受一个函数作为参数,并返回另一个函数来替换它接受的函数

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

    为了保持这一切的直白,传统的做法是通过一个描述其效果的名称来调用装饰器(例如,
    head\u和\u foot
    ,调用它接受的函数
    func
    ,并调用它返回的包装器
    wrapper
    。这就是我上面所做的

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

  • wrapper
    应该是被修饰函数的替代品,因此它应该具有相同的签名–这意味着它应该具有相同数量和类型的参数。您的函数
    price\u report
    sales\u report
    根本不接受任何参数(即,在它们的
    def
    语句中,括号
    ()
    之间没有任何内容),但是
    wrapper
    将它应该替换的函数作为参数,这毫无意义

    该行应该是
    def wrapper():
    ,以匹配被替换函数的签名

  • 装饰器应该返回替换函数,但您的装饰器正在调用替换并返回结果。您只需要
    返回包装器
    ,而不是
    返回包装器(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 
    

    我已经在书中标记了至少10篇关于装饰者的不同文章,但没有一篇提到命名约定的两点以及需要如此明确地返回的内容。不只是2美分,而是200万美元。我承认,当我构思和编写此代码时,我的常识离开了法国。
    >>> 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