【问题标题】:Decorator with Arguments: Would this be a better way?Decorator with Arguments:这会是更好的方法吗?
【发布时间】:2018-10-01 21:51:15
【问题描述】:

我刚开始学习装饰器。我觉得我了解如何修改装饰器以使用参数,但是我不明白为什么要实现 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 并没有看到装饰器没有以上述方式实现的原因,但我还是这个想法的新手。我觉得上面的改变会让装饰器更容易和更一致的使用。

【问题讨论】:

    标签: python python-decorators


    【解决方案1】:

    选择当前实现的原因是因为它更加一致。如果按照您建议的方式实现,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]


    关于 cmets 中提出的建议:

    • 禁止使用@python_decorator 语法并改为强制使用@python_decorator()

      我看到的问题是括号没有真正的用途,只会增加混乱。绝大多数装饰器是在没有参数的情况下应用的。仅仅为了与接受参数的装饰器保持一致而改变它的语法是不合理的。

    • 显式传递修饰函数作为第一个参数

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

      1. 这是错误的来源。它实际上并没有向您的代码添加任何内容,但一个简单的错字可能会导致错误。如果您想重命名一个装饰函数,您现在必须在 两个 位置重命名它,而不仅仅是一个。
      2. 被装饰的函数甚至还不存在! 因为函数定义在装饰器之后,所以不可能通过名字来引用函数in装饰者。
      3. 装饰器的全部意义在于简化和改进在装饰器存在之前必须使用的func = decorator(func) 语法。不得不写@decorator(func) 真的不会是一个显着的改进,不是吗?

    【讨论】:

    • 另外,我们可以像使用__init__(self) 那样写@ decorate(func)。为什么一个是显式的而另一个是隐式的?要求 func 参数将使带参数的装饰器成为不带参数的装饰器的简单而清晰的扩展——只需添加更多参数。
    • RE:“如果在没有 @python_decorator() 之类的参数的情况下调用装饰器,会发生什么?”我认为@python_decorator()(或者@python_decorator(func),如果我们想要更明确的实现,比如__init__(self))将是调用装饰器函数所需的语法。而且这种隐含意义是有先例的:class Name(Object): vs class Name(): vs class Name:
    • @JDG 我已经更新了我的答案以解决您的 cmets。关于与self参数的比较:那是完全不同的。装饰器的放置已经很明显地表明了哪个功能正在被装饰。强迫用户在装饰器中重复函数名只是一个烦恼和错误的来源。另一方面,self 参数如果未在函数签名中明确列出,则将完全隐含。就好像那个参数突然出现了一样。
    • 要清楚,我不是说我们写@decorate(the_specific_function_name_written_below);建议是@decorate_this(func)@decorate_some_other(func)@decorate_yet_another(func)---就像我们总是按照惯例使用self,无论我是在ThisClass还是ThatClass工作。 (感谢您的回答。)
    • 针对您的第 3 点,我相信装饰器的一点——除了删除 func = dec(func) repetition 之外——是将该行放在最需要它的 func 声明中。至于重新输入,我要强调的是,我建议使用通用的func(如self),而不是在下面命名特定的函数。
    猜你喜欢
    • 2017-05-11
    • 1970-01-01
    • 1970-01-01
    • 2017-12-25
    • 2012-06-02
    • 2011-04-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多