我有以下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
,以便我可以从Bar
函数中访问decorator
类(或任何其他类)?
我尝试在@classmethod
函数上使用deco_with_args
没有运气。
你的问题有两种解释 - 如果在你的例子中调用名为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语句之前运行。如果你的装饰器会影响在类体内部执行的其他元素,那么你需要做的就是将它们包装起来以保证延迟执行。
@classmethod
对你的装饰器没什么用处,因为它不是通过类或实例调用的。 classmethod
是一个descriptor,描述符只对属性访问有效。换句话说,只有装饰器像@Bar.deco_with_args('baz')
一样被调用时才有用。
下一个问题是在执行装饰器时类仍然不存在。 Python在创建类之前执行函数体中的所有代码。因此,无法访问deco_with_args
或decorator
中的课程。
您可以使用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__
返回的函数上调用__call__
然后调用__get__
。
请注意,当您执行__get__
时,也会调用Bar.some_function
,这是@property
等装饰器中使用的。
一个小小的评论,当使用classmethod你不应该命名你的第一个参数self
因为它令人困惑(它会让人们认为第一个参数是实例而不是类对象/类型)。