如何在多级继承中迭代超类?
How to Iterate over superclasses in multilevel inheritance?
我正在设计一个 metaclass 来覆盖 class __call__
函数,在它之前递归地执行它的 superclasses __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
)
它实际上适用于单级继承 classes,但它不适用于多级继承。
我该如何修复此代码以实现我的目标?
或者会有更简单的方法来击中它,而不是 metaclasses?
好吧,我可以找到一个利用 class method resolution order (i.e its __mro__
attribute) and also following 的解决方案(谢谢!)。
我的元类是这样的:
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)
]
这样就实现了预期的行为
阻止您获得预期结果的原因是您正在迭代 class' __bases__
- 这些仅列出直接的超级 classes。如果您更改您的 metacass 以迭代 __mro__
,Python 的所有 class 祖先的线性化序列,它将起作用:
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)
无论如何,事实证明这比乍一看要棘手一些 - 有极端情况 - 例如,如果你符合条件的 classes 没有 __call__
怎么办?如果其中一种方法 do 包含一个普通的 "super()" 调用怎么办?好的,添加一个标记以避免在确实放置 "super()" 的情况下不必要的重新进入 - 如果它在多线程环境中是 运行 并且两个实例正在运行怎么办
同时创建?
总而言之,必须正确组合使用Python的属性
获取机制——在正确的实例中选择方法。我选择将原来的__call__
方法复制到class本身的另一个方法中,这样它不仅可以存储原始方法,还可以作为符合条件的标记class es。
此外,请注意,这对 __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 or 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
)
并且这在控制台上有效 - 我又添加了一些
中间 classes 以涵盖极端情况:
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
我正在设计一个 metaclass 来覆盖 class __call__
函数,在它之前递归地执行它的 superclasses __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
)
它实际上适用于单级继承 classes,但它不适用于多级继承。
我该如何修复此代码以实现我的目标? 或者会有更简单的方法来击中它,而不是 metaclasses?
好吧,我可以找到一个利用 class method resolution order (i.e its __mro__
attribute) and also following
我的元类是这样的:
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)
]
这样就实现了预期的行为
阻止您获得预期结果的原因是您正在迭代 class' __bases__
- 这些仅列出直接的超级 classes。如果您更改您的 metacass 以迭代 __mro__
,Python 的所有 class 祖先的线性化序列,它将起作用:
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)
无论如何,事实证明这比乍一看要棘手一些 - 有极端情况 - 例如,如果你符合条件的 classes 没有 __call__
怎么办?如果其中一种方法 do 包含一个普通的 "super()" 调用怎么办?好的,添加一个标记以避免在确实放置 "super()" 的情况下不必要的重新进入 - 如果它在多线程环境中是 运行 并且两个实例正在运行怎么办
同时创建?
总而言之,必须正确组合使用Python的属性
获取机制——在正确的实例中选择方法。我选择将原来的__call__
方法复制到class本身的另一个方法中,这样它不仅可以存储原始方法,还可以作为符合条件的标记class es。
此外,请注意,这对 __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 or 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
)
并且这在控制台上有效 - 我又添加了一些 中间 classes 以涵盖极端情况:
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