是的,使用元类强制装饰器使用特定方法,正如您输入自己的答案是正确的。通过一些更改,可以使要装饰的方法不固定 - 例如,装饰函数中设置的属性可以用作“标记”,应在重写方法上强制使用此类装饰器。
除此之外,从Python 3.6开始,还有一个新的类级别机制——特殊方法__init_subclass__
,其具体目标是减少对元类的需求。元类可能很复杂,如果您的类层次结构需要组合多个元类,您可能会感到头疼。
The __init_subclass__
方法放在基类上,每次创建子类时都会调用一次。包装逻辑可以放在那里。
基本上,您可以修改装饰器以放置我上面提到的标记,并将此类添加到您的继承层次结构中 - 它可以作为多重继承中的 mixin 类放置,因此如果需要,它可以重用于各种类树:
def simple_decorator(func):
def wrapper(*args, **kwargs):
print("check cache")
rt = func(*args, **kwargs)
print("set cache")
return rt
wrapper.inherit_decorator = simple_decorator
return wrapper
class InheritDecoratorsMixin:
def __init_subclass__(cls, *args, **kwargs):
super().__init_subclass__(*args, **kwargs)
decorator_registry = getattr(cls, "_decorator_registry", {}).copy()
cls._decorator_registry = decorator_registry
# Check for decorated objects in the mixin itself- optional:
for name, obj in __class__.__dict__.items():
if getattr(obj, "inherit_decorator", False) and not name in decorator_registry:
decorator_registry[name] = obj.inherit_decorator
# annotate newly decorated methods in the current subclass:
for name, obj in cls.__dict__.items():
if getattr(obj, "inherit_decorator", False) and not name in decorator_registry:
decorator_registry[name] = obj.inherit_decorator
# finally, decorate all methods anottated in the registry:
for name, decorator in decorator_registry.items():
if name in cls.__dict__ and getattr(getattr(cls, name), "inherit_decorator", None) != decorator:
setattr(cls, name, decorator(cls.__dict__[name]))
所以,就是这样 - 每个新的子类都会有自己的_decorator_registry
属性,其中所有祖先中修饰方法的名称,以及which要应用的装饰器已注释。
如果装饰器应该为该方法使用一次,并且在被重写的方法执行该方法时不重复使用super()
调用它的祖先(不是在装饰缓存时的情况,因为不会调用超级方法),这会变得更棘手 - 但可以完成。
然而,这样做很棘手 - 因为超类中的装饰器实例将是子类上的装饰器之外的其他实例 - 将信息传递给“此方法的装饰器代码已经在此链调用中运行”的一种方法是使用实例级标记 - 如果代码要支持并行性,则该标记应该是线程局部变量。
所有这些检查将导致相当复杂的样板文件放入一个简单的装饰器中 - 因此我们可以为我们想要运行一次的“装饰器”创建一个“装饰器”。换句话说,decoratos 装饰有childmost
波纹管将仅在“最子级”类上运行,但在超类中调用时不会在超类中的相应方法上运行super()
import threading
def childmost(decorator_func):
def inheritable_decorator_that_runs_once(func):
decorated_func = decorator_func(func)
name = func.__name__
def wrapper(self, *args, **kw):
if not hasattr(self, f"_running_{name}"):
setattr(self, f"_running_{name}", threading.local())
running_registry = getattr(self, f"_running_{name}")
try:
if not getattr(running_registry, "running", False):
running_registry.running = True
rt = decorated_func(self, *args, **kw)
else:
rt = func(self, *args, **kw)
finally:
running_registry.running = False
return rt
wrapper.inherit_decorator = inheritable_decorator_that_runs_once
return wrapper
return inheritable_decorator_that_runs_once
使用第一个列表的示例:
class A: pass
class B(A, InheritDecoratorsMixin):
@simple_decorator
def method(self):
print(__class__, "method called")
class C(B):
def method(self):
print(__class__, "method called")
super().method()
将清单 1 和这些 A=B-C 类粘贴到
解释器,结果是这样的:
In [9]: C().method()
check cache
<class '__main__.C'> method called
check cache
<class '__main__.B'> method called
set cache
set cache
(这里的“A”类完全是可选的,可以省略)
使用第二个清单的示例:
# Decorating the same decorator above:
@childmost
def simple_decorator2(func):
def wrapper(*args, **kwargs):
print("check cache")
rt = func(*args, **kwargs)
print("set cache")
return rt
return wrapper
class D: pass
class E(D, InheritDecoratorsMixin):
@simple_decorator2
def method(self):
print(__class__, "method called")
class F(E):
def method(self):
print(__class__, "method called")
super().method()
结果:
In [19]: F().method()
check cache
<class '__main__.F'> method called
<class '__main__.E'> method called
set cache