Python 装饰器@func().attribute 语法错误

Posted

技术标签:

【中文标题】Python 装饰器@func().attribute 语法错误【英文标题】:Python decorator @func().attribute syntax error 【发布时间】:2015-12-10 14:49:39 【问题描述】:

我试图在这里找到答案,但找不到。

@obj.func # works
@obj.func(**kwargs)  #works
@obj.func1(**kwargs).func2   #-> syntax error 

我不明白为什么第三种形式是 SyntaxError,对我来说这似乎没有违反任何 python 语法,而且我很清楚用户想要做什么(参见下面的示例)。

我查看了装饰器实现的pep 0318,但没有找到任何答案。

下面是一个使用示例:

class ItemFunc(object):
    def __init__(self, fcall=None, **kwargs):
        self.defaults = kwargs
        self.fcall = None

    def __call__(self, *args, **kwargs):
        kwargs = dict(self.defaults, **kwargs)
        # do something more complex with kwargs 
        output = self.fcall(*args, **kwargs)
        # do something more with output  
        return output

    def caller(self, fcall):
        """ set call and return self """
        self.call = fcall # after some check obviously
        return self

    def copy(self,**kwargs):
        kwargs = dict(self.defaults, **kwargs)
        return self.__class__(self.fcall, **kwargs)

    def copy_and_decorate(self, **kwargs):
        return self.copy(**kwargs).caller 

你可以使用 ItemFunc 作为装饰器:

@ItemFunc
def plot(**kwargs):
    pass

redcross = plot.copy(color="red", marker="+")
@redcross.caller
def plot_data1(**kwargs):
    pass

bluecross = redcross.copy(color="blue")
@bluecross.caller
def plot_data2(**kwargs):
    pass

但是为什么禁止使用以下“快捷语法”:

@redcross.copy(color="blue").caller
def plot_data2(**kwargs):
    pass

但我可以:

@redcross.copy_and_decorate(color="blue")
def plot_data2(**kwargs):
    pass         

第一种形式看起来更好,至少我更了解背后的意图。

【问题讨论】:

【参考方案1】:

Function definitions grammar 不允许调用带有更多点名的电话;语法仅限于带点的名称和一个可选的调用在结尾

decorated      ::=  decorators (classdef | funcdef)
decorators     ::=  decorator+
decorator      ::=  "@" dotted_name ["(" [argument_list [","]] ")"] NEWLINE
funcdef        ::=  "def" funcname "(" [parameter_list] ")" ":" suite
dotted_name    ::=  identifier ("." identifier)*

请注意,这不是一个完整的表达式,而是一个非常有限的子集。

这与 PEP 相呼应,其中指出:

装饰器语句在它可以接受的范围内是有限的——任意表达式都不起作用。 Guido 更喜欢这个,因为直觉 [17]。

拥有一个返回装饰器的函数的基本原理是@符号之后的部分可以被认为是一个表达式(尽管在语法上仅限于一个函数),以及该表达式返回的任何内容叫做。请参阅声明参数 [16]。

强调我的。

理由是Guido feels there isn't a real use case for allowing more:

因此,虽然将语法更改为 @test 在 未来,我想坚持使用更受限制的形式,除非是真正的 提出了允许@test 会增加可读性的用例。 (@foo().bar() 不算数,因为我不认为你会需要 那个)。

您必须让 Guido 和其他核心开发人员相信您的案例是一个合适的用例,值得解除这些限制!

【讨论】:

是的,这更像是一个哲学问题。对我来说,用 .getter 或 .caller 之类的东西完成装饰器(就像在我的示例中一样)清楚地表明了我们想要做什么,同时允许调用最后发送 .caller 方法(我的示例再次)的函数,但更少可读。你不觉得吗? @user3240484:我可以看到你的设置要去哪里;但也许不是使用属性,而是让__call__ 返回实际的装饰器,这也支持__call__ 你是对的,但是如果你看一下我的例子,call 方法已经像'fcall'函数的装饰器一样,为用户保持对象函数式.我可以用调用函数替换 call 并使用 call 来返回装饰器,但是我在那里获得的东西我把它放在那里了。谢谢 是的,因为你的类是一个装饰器工厂,装饰器 包装器合二为一。我建议你拉出包装部分。装饰的结果本身不需要再次用作装饰器,不是吗?【参考方案2】:

正如另一个答案中所解释的,Guido had a "gut feeling" 最初是限制的原因。

在 Python 3.9 中取消了此限制。,允许装饰器成为任何有效的表达式。

Previously, the grammar 装饰器是:

decorator: '@' dotted_name [ '(' [arglist] ')' ] NEWLINE

在 Python 3.9 中,the grammar 被简化为:

decorator: '@' namedexpr_test NEWLINE

这是 3.8 和 3.9 之间唯一的语法变化。详情请参阅PEP 614 -- Relaxing Grammar Restrictions On Decorators。

【讨论】:

以上是关于Python 装饰器@func().attribute 语法错误的主要内容,如果未能解决你的问题,请参考以下文章

python装饰器

021day--python装饰器

python 装饰器

python 装饰器

Python 装饰器@func().attribute 语法错误

python闭包和装饰器(转)