Python - 元类装饰器 - 如何使用 @classmethod
Posted
技术标签:
【中文标题】Python - 元类装饰器 - 如何使用 @classmethod【英文标题】:Python - Metaclass decorator - How to use @classmethod 【发布时间】:2019-08-08 00:30:54 【问题描述】:我有以下 Python 元类,它为每个类添加了一个 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
,以便我可以从decorator
函数中访问Bar
类(或任何其他类)?
我尝试在deco_with_args
函数上使用@classmethod
,但没有成功。
【问题讨论】:
您确定要使用元类来定义装饰器吗? @DeepSpace 是有原因的。这是this question的后续行动。 Welp,我刚刚意识到这是不可能的。是时候重写我完成了 60% 的答案了…… 澄清一下:您需要访问test
或decorator
中的课程吗?
就在decorator
里面。
【参考方案1】:
您可以使用descriptor protocol 来捕获您对方法的调用并将类作为参数添加:
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
这里的主要“技巧”是调用Bar.some_function()
时的协议是先调用__get__
,然后在__get__
返回的函数上调用__call__
。
请注意,__get__
也会在您执行 Bar.some_function
时被调用,这就是在像 @property
这样的装饰器中使用的。
有一点需要注意,在使用 classmethod 时,您不应该将第一个参数命名为 self
,因为它会造成混淆(这会使人们认为第一个参数是实例而不是类对象/类型)。
【讨论】:
我不认为 OP 想把some_function
变成一个类方法。那很容易。只需拍一下@classmethod
就可以了。为此实现自己的描述符对我来说似乎有点过头了。
这就是问题所说的,我什至没有在字里行间阅读“如何使 deco_with_args 装饰器表现得像@classmethod”
我的观点完全正确。它没有说“我如何让some_function
表现得像一个类方法”。
我不明白你的意思,这里的目标是有一个装饰器,其行为类似于类方法,并在装饰器中添加参数。
这并不过分,因为classmethod
装饰器不带参数,即使它这样做也不会调用类似print(baz, func())
的东西。【参考方案2】:
@classmethod
对装饰器没有任何用处,因为它不是通过类或实例调用的。 classmethod
是descriptor,描述符只对属性访问有效。换句话说,只有像 @Bar.deco_with_args('baz')
这样调用装饰器才会有帮助。
下一个问题是在执行装饰器时类还不存在。 Python 在创建类之前 执行函数体中的所有代码。所以无法访问deco_with_args
或decorator
中的类。
【讨论】:
“不可能”的事情正是元类旨在克服的问题。 @jsbueno 好吧,确实,OP 试图实现的任何目标都可以通过元类来完成。但不可否认的是,没有办法在这个装饰器中访问类。我不确定我是否要扩展我的答案以包含一个通用的“您可以编写一个标记函数的装饰器,然后在元类的__init__
中对标记的函数进行后处理”黑客可能会用来创建可怕的代码. 10 次中有 9 次,没有真正需要这样的东西。
@Aran-Fey 我实际上想访问该类以标记函数:cls.marked_functions.append(func)
。没有课你会怎么做?
@DavidCallanan 我只需设置一个属性,如func.marked = True
。【参考方案3】:
您的问题有两种解释 - 如果您需要 cls
在您的示例中名为 decorator
的函数被调用时可用(即您需要您的修饰方法成为类方法),它本身就足够了转化为类方法:
def deco_with_args(baz):
def decorator(func):
...
return classmethod(func)
return decorator
第二个是如果你需要cls
在deco_with_args
本身被调用时可用,在创建装饰函数本身时,在类创建时。现在被列为接受的答案列出了一个简单的问题:运行类主体时该类还不存在,因此,在解析类结束时没有办法你可以拥有知道类本身的方法。
但是,与该答案试图暗示的不同,这不是真正的交易。您所要做的就是在类创建过程结束时懒惰地运行您的装饰器代码(需要cls
的代码)。你已经有了一个元类设置,所以这样做几乎是微不足道的,只需在你的装饰器代码周围添加另一个可调用层:
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
之后的任何其他 Python 语句之前。
如果您的装饰器会影响在类主体内部执行的其他元素,您需要做的就是将它们包装起来以保证延迟执行。
【讨论】:
还是坏了。decorator
永远不会被执行。 outter_decorator._deco_with_args = True
需要改为decorator._deco_with_args = True
,cls.__dict__[name] = method(cls)
需要改为setattr(cls, name, method(cls))
。
这正是我想要的。我是否可以更改已接受的答案?
我认为是这样 - 我什至与原始答案的作者评论了这种可能性,他表示他更愿意保持这种方式。因此,如果这种方法效果更好,那么接受它是有意义的。
我知道我现在可能让你很紧张,但你为什么不修复最后一个错误?
这是关于有两个“名称”变量,对吧?当你发现它时我没有修复它,但是是的,它现在已经修复了。以上是关于Python - 元类装饰器 - 如何使用 @classmethod的主要内容,如果未能解决你的问题,请参考以下文章
Python 中的纯静态类 - 使用元类、类装饰器还是其他东西?