带有参数的装饰者:这会是一个更好的方法吗?

时间:2018-04-21 09:12:37

标签: python python-decorators

我刚刚开始学习装饰器。我觉得我理解如何修改装饰器以使用参数,但我不明白为什么Python中的装饰器已经实现,因此需要进行此修改。

[编辑:为清楚起见。] 对于没有参数的装饰器,以下是等效的:

@decorate
def function(arg):
    pass

function = decorate(function)

那么,对于带参数的装饰器,为什么不让下面的内容相同?

@decorate(123)
def function(arg):
    pass

function = decorate(function, 123)

我发现这更一致,更易于阅读,也更容易编码。

下面是python装饰器如何工作,然后是我期望它们如何工作。 [编辑:我也添加了预期的输出。]:

OUTPUT:
function HELLA NESTED! Decorated with OMG!
function NOT AS NESTED! Decorated with NOT SO BAD!

def python_decorator(dec_arg):
    def actual_decorator(func):
        def decorated_func(func_arg):
            print(func.__name__, func(func_arg), "Decorated with", dec_arg)

        return decorated_func
    return actual_decorator

@python_decorator("OMG!")
def function(arg):
    return arg

function("HELLA NESTED!")

def my_expected_decorator(func, dec_arg):
    # I expected the `func` parameter to be like `self` in classes---it's 
    # always first and is given special meaning with decorators.
    def decorated_func(func_arg):
        print(func.__name__, func(func_arg), "Decorated with", dec_arg)

    return decorated_func

# @my_expected_decorator("NOT SO BAD!") # This is what I expected.
def function(arg):
    return arg
function = my_expected_decorator(function, "NOT SO BAD!")

function("NOT AS NESTED!")

我看了PEP 318并且没有看到装饰器没有以上述方式实现的原因,但我再次对这个想法不熟悉。我觉得上面的改变会使装饰器更容易使用,也更加一致。

1 个答案:

答案 0 :(得分:4)

选择当前实施的原因是因为它更多一致。如果以你建议的方式实现它,python必须根据它们的使用方式以非常不同的方式处理装饰器:

  • 如果装饰器没有使用@python_decorator之类的其他参数,则可以使用函数作为唯一参数立即调用它。
  • 如果使用@python_decorator("OMG!")之类的参数调用装饰器,python必须在某处存储这些参数,然后将该函数作为第一个参数插入。
  • 如果装饰者被称为而没有像@python_decorator()这样的参数,会发生什么?这应该等同于@python_decorator吗?但是为什么有两种不同的方法来使用没有参数的装饰器?也许不应该允许这种语法?

正如您所看到的,这样的实现会增加一些不一致性。另一方面,所选择的实现非常直观:@符号后面的代码可以被认为是一个表达式,该表达式的结果用作装饰器:

  • @python_decorator的情况下,python_decorator是一个只返回现有装饰器函数的表达式。
  • 对于@python_decorator("OMG!"),表达式python_decorator("OMG!")也会返回装饰器函数。

从这个意义上说,所选择的实现比你提出的实现更加一致。

此基本原理也在the PEP中解释:

  

具有返回装饰器的函数的基本原理是   @符号后面的部分可以被认为是一个表达式   (虽然在语法上仅限于一个函数),等等   调用表达式返回。请参阅declaration arguments [16]

关于评论中提出的建议:

  • 禁用语法@python_decorator语法并强制执行@python_decorator()

    我看到的问题是,圆括号没有任何实际意义,只会增加混乱。绝大多数装饰器都是在没有参数的情况下应用的。更改此语法只是为了与带参数的装饰器保持一致是不合理的。

  • 将装饰函数显式传递为第一个参数

    同样,这不会增加任何语义含义。很明显,装饰器将被调用在@python_decorator行正下方定义的函数上。强制用户显式传递函数作为参数是坏的,因为

    1. 这是错误的来源。它并没有真正添加任何代码,但是一个简单的拼写错误就会导致错误。如果要重命名已修饰的函数,则现在必须在两个位置重命名它,而不是仅重命名。
    2. 装饰的功能甚至还不存在!由于功能定义来自装饰后,因此无法通过在装饰者中命名
    3. 装饰器的重点是简化和改进在装饰器存在之前必须使用的func = decorator(func)语法。必须写@decorator(func)而不是真正的重大改进,不是吗?