为什么应用带括号和不带括号的 Python 装饰器不同?
Why is applying a Python decorator with and without parenthesis different?
我有一个自写的基于class的Python装饰器。据我所知,当我将装饰器应用于 class 中的方法时,存在差异。通常,像 @classmethod
、@staticmethod
、@property
或 @unique
这样的装饰器是在没有括号的情况下应用的。这些装饰器不需要参数,并且(大部分?)被编写为基于函数的装饰器。
因此,与这些示例相比,我的装饰器是基于 class 的,并且在应用时需要一个可选参数。
我的装饰师:
class DocumentMemberAttribute(Attribute):
def __init__(self, value=True):
super().__init__()
self.value = value
属性class(真正的装饰者):
class Attribute:
__AttributesMemberName__ = "__pyattr__"
def __call__(self, func):
self._AppendAttribute(func, self)
return func
@staticmethod
def _AppendAttribute(func, attribute):
# inherit attributes and append myself or create a new attributes list
if (Attribute.__AttributesMemberName__ in func.__dict__):
func.__dict__[Attribute.__AttributesMemberName__].append(attribute)
else:
func.__setattr__(Attribute.__AttributesMemberName__, [attribute])
def __str__(self):
return self.__name__
示例class:
class MyClass:
def __init__(self, message=""):
super().__init__()
self.message = message
@DocumentMemberAttribute
def Method1(self):
return "foo"
@DocumentMemberAttribute()
def Method2(self):
return "bar"
@DocumentMemberAttribute(False)
def Method3(self):
return "spam"
@DocumentMemberAttribute(True)
def Method4(self):
return "egg"
附加信息用于自定义 autodoc-skip-member
处理程序,以决定是否应记录或跳过某个方法。这类似于 docit
扩展。
所以当我们查看生成的文档(Sphinx)时,我们可以看到这些结果:
class MyClass(message="")
Method1 = <lib.SphinxExtensions.DocumentMemberAttribute object at 0x0000000004AD9E80>
Method2()
Method4()
我们在这里可以看到什么:
- Method1 没有括号表示 function/method,因此它被视为 class 字段,Sphinx 使用
__str__
(或 __repr__
?)记录初始值
- 方法 3 未按预期记录。
所以我的问题:
- 为什么行为会有所不同?
- 我必须应用带括号的基于 class 的属性吗?
- 用户应该如何知道如何使用这两种装饰器?
(我可以自己记录下来,但其他人可能只记得名字而忘记添加括号,因为其他装饰器不需要它们。)
pyAttributes is a set of attributes (real attributes, no Python attributes) written by me. They behave almost like custom-attributes .NET
正如@Ryan 指出的那样,@
符号后面的字符串是一个表达式,它被转换为函数调用。该调用的参数是应用装饰器的对象。
示例 1 - 基于函数的装饰器:
我将使用 enum.unique
装饰器。它被写成一个函数。
from enum import Enum, unique
@unique
class MyEnum(Enum):
foo = 0
bar = 1
翻译成
from enum import Enum, unique
class MyEnum(Enum):
foo = 0
bar = 1
MyEnum = unique(MyEnum)
示例 2 - 基于 class 的装饰器:
我将使用问题中的装饰器。它被写成 class.
class MyClass:
@DocumentMemberAttribute()
def Method1():
pass
@DocumentMemberAttribute(True)
def Method2():
pass
翻译成
class MyClass:
def Method1():
pass
Method1 = DocumentMemberAttribute()(Method1)
def Method2():
pass
Method2 = DocumentMemberAttribute(True)(Method2)
请注意在将 Method1
作为参数传递给 class' __call__
方法之前的空括号。这些是初始化程序的 (__init__
) 括号。因此,当将 True 作为参数传递时,这些括号会填充 Method2
。
总之:
- 基于函数的装饰器在没有括号的情况下应用
基于 - class 的装饰器应用括号
PyCharm 用户注意事项:
看看彩色的 @
标志:
- blue -> 基于函数的装饰器
- red -> class-based decorator
我有一个自写的基于class的Python装饰器。据我所知,当我将装饰器应用于 class 中的方法时,存在差异。通常,像 @classmethod
、@staticmethod
、@property
或 @unique
这样的装饰器是在没有括号的情况下应用的。这些装饰器不需要参数,并且(大部分?)被编写为基于函数的装饰器。
因此,与这些示例相比,我的装饰器是基于 class 的,并且在应用时需要一个可选参数。
我的装饰师:
class DocumentMemberAttribute(Attribute):
def __init__(self, value=True):
super().__init__()
self.value = value
属性class(真正的装饰者):
class Attribute:
__AttributesMemberName__ = "__pyattr__"
def __call__(self, func):
self._AppendAttribute(func, self)
return func
@staticmethod
def _AppendAttribute(func, attribute):
# inherit attributes and append myself or create a new attributes list
if (Attribute.__AttributesMemberName__ in func.__dict__):
func.__dict__[Attribute.__AttributesMemberName__].append(attribute)
else:
func.__setattr__(Attribute.__AttributesMemberName__, [attribute])
def __str__(self):
return self.__name__
示例class:
class MyClass:
def __init__(self, message=""):
super().__init__()
self.message = message
@DocumentMemberAttribute
def Method1(self):
return "foo"
@DocumentMemberAttribute()
def Method2(self):
return "bar"
@DocumentMemberAttribute(False)
def Method3(self):
return "spam"
@DocumentMemberAttribute(True)
def Method4(self):
return "egg"
附加信息用于自定义 autodoc-skip-member
处理程序,以决定是否应记录或跳过某个方法。这类似于 docit
扩展。
所以当我们查看生成的文档(Sphinx)时,我们可以看到这些结果:
class MyClass(message="")
Method1 = <lib.SphinxExtensions.DocumentMemberAttribute object at 0x0000000004AD9E80>
Method2()
Method4()
我们在这里可以看到什么:
- Method1 没有括号表示 function/method,因此它被视为 class 字段,Sphinx 使用
__str__
(或__repr__
?)记录初始值 - 方法 3 未按预期记录。
所以我的问题:
- 为什么行为会有所不同?
- 我必须应用带括号的基于 class 的属性吗?
- 用户应该如何知道如何使用这两种装饰器? (我可以自己记录下来,但其他人可能只记得名字而忘记添加括号,因为其他装饰器不需要它们。)
pyAttributes is a set of attributes (real attributes, no Python attributes) written by me. They behave almost like custom-attributes .NET
正如@Ryan 指出的那样,@
符号后面的字符串是一个表达式,它被转换为函数调用。该调用的参数是应用装饰器的对象。
示例 1 - 基于函数的装饰器:
我将使用 enum.unique
装饰器。它被写成一个函数。
from enum import Enum, unique
@unique
class MyEnum(Enum):
foo = 0
bar = 1
翻译成
from enum import Enum, unique
class MyEnum(Enum):
foo = 0
bar = 1
MyEnum = unique(MyEnum)
示例 2 - 基于 class 的装饰器:
我将使用问题中的装饰器。它被写成 class.
class MyClass:
@DocumentMemberAttribute()
def Method1():
pass
@DocumentMemberAttribute(True)
def Method2():
pass
翻译成
class MyClass:
def Method1():
pass
Method1 = DocumentMemberAttribute()(Method1)
def Method2():
pass
Method2 = DocumentMemberAttribute(True)(Method2)
请注意在将 Method1
作为参数传递给 class' __call__
方法之前的空括号。这些是初始化程序的 (__init__
) 括号。因此,当将 True 作为参数传递时,这些括号会填充 Method2
。
总之:
- 基于函数的装饰器在没有括号的情况下应用 基于
- class 的装饰器应用括号
PyCharm 用户注意事项:
看看彩色的 @
标志:
- blue -> 基于函数的装饰器
- red -> class-based decorator