我正在设计metaclass来覆盖类__call__
函数,并在其之前递归执行其超类__call__
。这个想法能够为下面的代码获得以下结果:
Abstract
Base
Super
Child
class Abstract(metaclass=Meta):
def __call__(self):
print("Abstract")
class Base(Abstract):
def __call__(self):
print("Base")
class Super(Abstract):
def __call__(self):
print("Super")
class Parent:
def __call__(self):
print("Parent")
class Child(Base, Super, Parent):
def __call__(self):
print("Child")
到目前为止,我将我的Meta.new
编写如下:
def __new__(meta, name, bases, attr):
__call__ = attr['__call__']
def recursive_call(self):
for cls in [base for base in self.__class__.__bases__ if type(base) is Meta]:
cls.__call__(super(cls, self))
__call__(self)
attr['__call__'] = recursive_call
return super(Meta, meta).__new__(
meta, name, bases, attr
)
而且它实际上适用于一级继承类,但不适用于多层继承。
如何解决此代码以实现我的目标?还是会有更简单的方法来实现它,而不是元类?
嗯,我可以找到一个利用class method resolution order(即__mro__
属性)并跟随supports Monica's suggestion(谢谢!)的解决方案。
我的元类是这样的:
class MetaComposition(type):
def __new__(meta, name, bases, attr, __func__='__call__'):
def __call__(self, *args, **kwargs):
for cls in self.__class__.__compound__:
cls.__run__(self, *args, **kwargs)
attr['__run__'] = attr[__func__]
attr[__func__] = __call__
return super(MetaComposition, meta).__new__(meta, name, bases, attr)
@property
def __compound__(cls):
return [
element
for element in
cls.mro()[::-1]
if type(element)
is type(cls)
]
并且这样可以实现所需的行为
阻止您获得预期结果的原因是您正在遍历类'__bases__
-这些仅列出直接的超类。如果您更改元信息以遍历__mro__
(Python的一个类的祖先的线性化序列),它将起作用:
In [14]: class Abstract(metaclass=Meta):
...: def __call__(self):
...: print("Abstract")
...:
...: class Base(Abstract):
...: def __call__(self):
...: print("Base")
...:
...: class Super(Abstract):
...: def __call__(self):
...: print("Super")
...:
...: class Parent:
...: def __call__(self):
...: print("Parent")
...:
...: class Child(Parent):
...: def __call__(self):
...: print("Child")
...:
In [15]: Child.__mro__
Out[15]: (__main__.Child, __main__.Parent, object)
[无论如何,这看起来比乍一看要复杂一些-有一些极端的情况-例如,如果您的某个符合条件的课程没有__call__
怎么办?如果其中一种方法do包含普通的“ super()”调用该怎么办?好的,添加一个标记,以避免在确实放入“ super()”的情况下避免不必要的重入-如果它在多线程环境中运行并且正在有两个实例,该怎么办同时创建?
总而言之,必须使用Python的属性进行正确的组合提取机制-在正确的实例中选择方法。我选择了将原始__call__
方法复制到类本身中的anothr方法中,因此它不仅可以存储原始方法,而且还可以作为合格类的标记。
[另外,请注意,此方法对__call__
的工作原理与对任何其他方法的工作原理相同-因此,我将名称"__call__"
分解为常量以确保(并且可以将其制成方法列表,或者名称具有特定前缀的所有方法,依此类推。)
from functools import wraps from threading import local as threading_local MARKER_METHOD = "_auto_super_original" AUTO_SUPER = "__call__" class Meta(type): def __new__(meta, name, bases, attr): original_call = attr.pop(AUTO_SUPER, None) avoid_rentrancy = threading_local() avoid_rentrancy.running = False @wraps(original_call) def recursive_call(self, *args, _wrap_call_mro=None, **kwargs): if getattr(avoid_rentrancy, "running", False): return avoid_rentrancy.running = True mro = _wrap_call_mro if _wrap_call_mro is not None else self.__class__.__mro__ try: for index, supercls in enumerate(mro[1:], 1): if MARKER_METHOD in supercls.__dict__: supercls.__call__(self, *args, _wrap_call_mro=mro[index:], **kwargs) break getattr(mro[0], MARKER_METHOD)(self, *args, **kwargs) finally: avoid_rentrancy.running = False if original_call: attr[MARKER_METHOD] = original_call attr[AUTO_SUPER] = recursive_call return super().__new__( meta, name, bases, attr )
并在控制台上正常工作-我又添加了一些涵盖极端情况的中级课程:
class Abstract(metaclass=Meta): def __call__(self): print("Abstract") class Base1(Abstract): def __call__(self): print("Base1") class Base2(Abstract): def __call__(self): print("Base2") class Super(Base1): def __call__(self): print("Super") class NonColaborativeParent(): def __call__(self): print("Parent") class ForgotAndCalledSuper(Super): def __call__(self): super().__call__() print("Forgot and called super") class NoCallParent(Super): pass class Child(NoCallParent, ForgotAndCalledSuper, Parent, Base2): def __call__(self): print("Child")
结果:
In [96]: Child()()
Abstract
Base2
Base1
Super
Child
Forgot and called super
Child