Python - 元类装饰器 - 如何使用@classmethod
Python - Metaclass decorator - How to use @classmethod
我有以下 Python metaclass,它为每个 class 添加了一个 deco_with_args
装饰器:
def deco_with_args(baz):
def decorator(func):
...
return func
return decorator
class Foo(type):
def __prepare__(name, bases):
return {'deco_with_args': deco_with_args}
这让我可以像这样使用装饰器:
class Bar(metaclass=Foo):
@deco_with_args('baz')
def some_function(self):
...
如何使 deco_with_args
装饰器的行为类似于 @classmethod
,以便我可以访问 Bar
class(或任何其他 class)来自 decorator
函数?
我曾尝试在 deco_with_args
函数上使用 @classmethod
,但没有成功。
您可以使用 descriptor protocol 捕获对方法的调用并即时添加 class 作为参数:
def another_classmethod(baz):
class decorator:
def __init__(self, func):
self.func = func
def __get__(self, instance, owner):
def new_call(*args, **kwargs):
print(baz, self.func(owner, *args, **kwargs))
return new_call
return decorator
class Bar():
@another_classmethod('baz')
def some_function(cls):
return f"test {cls.__name__}"
Bar.some_function()
这会打印:
baz test Bar
这里的主要"trick"是调用Bar.some_function()
时的协议是在__get__
返回的函数上先调用__get__
然后__call__
。
请注意,当您执行 Bar.some_function
时也会调用 __get__
,这就是 @property
等装饰器中使用的内容。
注意一点,当使用 class 方法时,您不应该将第一个参数命名为 self
,因为这会造成混淆(这会让人们认为第一个参数是一个实例而不是一个 class object/type).
@classmethod
对您的装饰器没有任何用处,因为它不是通过 class 或实例调用的。 classmethod
是一个descriptor,描述符只对属性访问生效。换句话说,只有像 @Bar.deco_with_args('baz')
.
这样调用装饰器才会有帮助
下一个问题是class 在装饰器执行时还不存在。 Python 在 创建 class 之前执行函数体 中的所有代码。所以无法访问 deco_with_args
或 decorator
.
中的 class
你的问题有两种解释——如果你需要 cls
在你的例子中调用名为 decorator
的函数时可用(即你需要你的装饰方法成为 class methods), 自身转化为classmethod:
即可
def deco_with_args(baz):
def decorator(func):
...
return classmethod(func)
return decorator
第二个是如果您需要 cls
在 deco_with_args
本身被调用时可用,当创建修饰函数本身时,在 class 创建时。现在被列为已接受的答案列出了一个直接的问题:当 class 正文为 运行 时,class 尚不存在,因此,在解析 class 正文结束时,您无法使用已知 class 本身的方法。
但是,与该答案试图暗示的不同,这不是真正的交易。您所要做的就是 运行 您的装饰器代码(需要 cls
的代码)在 class 创建过程结束时懒惰地进行。你已经有了一个 metaclass 设置,所以这样做几乎是微不足道的,只需在你的装饰器代码周围添加另一个可调用层:
def deco_with_args(baz):
def outter_decorator(func):
def decorator(cls):
# Code that needs cls at class creation time goes here
...
return func
return decorator
outter_decorator._deco_with_args = True
return outter_decorator
class Foo(type):
def __prepare__(name, bases):
return {'deco_with_args': deco_with_args}
def __init__(cls, cls_name, bases, namespace, **kwds):
for name, method in cls.__dict__.items():
if getattr(method, '_deco_with_args', False):
cls.__dict__[name] = method(cls)
super().__init__(cls_name, bases, namespace, **kwds)
这将是 运行,当然,在 class 主体执行完成之后,但在 class
之后的任何其他 Python 语句之前 运行.
如果您的装饰器会影响在 class 主体内部执行的其他元素,您需要做的就是将它们包裹起来以保证延迟执行。
我有以下 Python metaclass,它为每个 class 添加了一个 deco_with_args
装饰器:
def deco_with_args(baz):
def decorator(func):
...
return func
return decorator
class Foo(type):
def __prepare__(name, bases):
return {'deco_with_args': deco_with_args}
这让我可以像这样使用装饰器:
class Bar(metaclass=Foo):
@deco_with_args('baz')
def some_function(self):
...
如何使 deco_with_args
装饰器的行为类似于 @classmethod
,以便我可以访问 Bar
class(或任何其他 class)来自 decorator
函数?
我曾尝试在 deco_with_args
函数上使用 @classmethod
,但没有成功。
您可以使用 descriptor protocol 捕获对方法的调用并即时添加 class 作为参数:
def another_classmethod(baz):
class decorator:
def __init__(self, func):
self.func = func
def __get__(self, instance, owner):
def new_call(*args, **kwargs):
print(baz, self.func(owner, *args, **kwargs))
return new_call
return decorator
class Bar():
@another_classmethod('baz')
def some_function(cls):
return f"test {cls.__name__}"
Bar.some_function()
这会打印:
baz test Bar
这里的主要"trick"是调用Bar.some_function()
时的协议是在__get__
返回的函数上先调用__get__
然后__call__
。
请注意,当您执行 Bar.some_function
时也会调用 __get__
,这就是 @property
等装饰器中使用的内容。
注意一点,当使用 class 方法时,您不应该将第一个参数命名为 self
,因为这会造成混淆(这会让人们认为第一个参数是一个实例而不是一个 class object/type).
@classmethod
对您的装饰器没有任何用处,因为它不是通过 class 或实例调用的。 classmethod
是一个descriptor,描述符只对属性访问生效。换句话说,只有像 @Bar.deco_with_args('baz')
.
下一个问题是class 在装饰器执行时还不存在。 Python 在 创建 class 之前执行函数体 中的所有代码。所以无法访问 deco_with_args
或 decorator
.
你的问题有两种解释——如果你需要 cls
在你的例子中调用名为 decorator
的函数时可用(即你需要你的装饰方法成为 class methods), 自身转化为classmethod:
def deco_with_args(baz):
def decorator(func):
...
return classmethod(func)
return decorator
第二个是如果您需要 cls
在 deco_with_args
本身被调用时可用,当创建修饰函数本身时,在 class 创建时。现在被列为已接受的答案列出了一个直接的问题:当 class 正文为 运行 时,class 尚不存在,因此,在解析 class 正文结束时,您无法使用已知 class 本身的方法。
但是,与该答案试图暗示的不同,这不是真正的交易。您所要做的就是 运行 您的装饰器代码(需要 cls
的代码)在 class 创建过程结束时懒惰地进行。你已经有了一个 metaclass 设置,所以这样做几乎是微不足道的,只需在你的装饰器代码周围添加另一个可调用层:
def deco_with_args(baz):
def outter_decorator(func):
def decorator(cls):
# Code that needs cls at class creation time goes here
...
return func
return decorator
outter_decorator._deco_with_args = True
return outter_decorator
class Foo(type):
def __prepare__(name, bases):
return {'deco_with_args': deco_with_args}
def __init__(cls, cls_name, bases, namespace, **kwds):
for name, method in cls.__dict__.items():
if getattr(method, '_deco_with_args', False):
cls.__dict__[name] = method(cls)
super().__init__(cls_name, bases, namespace, **kwds)
这将是 运行,当然,在 class 主体执行完成之后,但在 class
之后的任何其他 Python 语句之前 运行.
如果您的装饰器会影响在 class 主体内部执行的其他元素,您需要做的就是将它们包裹起来以保证延迟执行。