【问题标题】:How to Iterate over superclasses in multilevel inheritance?如何在多级继承中迭代超类?
【发布时间】:2020-08-17 12:00:24
【问题描述】:

我正在设计一个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
    )

它实际上适用于单级继承类,但不适用于多级继承类。

如何修复此代码以实现我的目标? 或者是否有更简单的方法来实现它,而不是元类?

【问题讨论】:

  • 这似乎是一种非常有问题的元类设计方式。
  • 如果你真的希望__call__ 表现得像这样,最好有一个单独的_call 钩子并有一个带有__call__ 的基类,它调用所有_call方法,但是让__call__ 像这样工作本身就很奇怪。
  • 这是什么:cls.__call__(super(cls, self)) 应该做什么??
  • cls.__call__(super(cls, self)) 是我尝试将 超类对象 传递给它的__call__ 的方式,因为如果我传递了self基类列表 重复一遍,我收到了RecursionError: maximum recursion depth exceeded while calling a Python object
  • super(cls, self) 不会返回超类对象。它将返回一个super 对象。

标签: python python-3.x recursion metaclass


【解决方案1】:

阻止您获得所需结果的原因是您正在迭代类'__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__ 怎么办?如果其中一种方法确实包含一个普通的“super()”调用怎么办?好的,添加一个标记以避免在确实放置“super()”的情况下不必要的重新进入 - 如果它在多线程环境中运行并且正在运行两个实例怎么办? 同时创建?

总而言之,必须正确组合使用 Python 的属性 获取机制——在正确的实例中选择方法。我选择将原始的__call__ 方法复制到类本身的另一个方法中,这样它不仅可以存储原始方法,还可以作为符合条件的类的标记。

另外,请注意,这对 __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
        )

这是在控制台上运行的 - 我添加了更多 覆盖极端情况的中间类:


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

【讨论】:

  • 漂亮的 englighter 并且知道许多未经考虑的情况!
【解决方案2】:

好吧,我可以找到利用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)
        ]

这样就实现了预期的行为

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-05-03
    • 1970-01-01
    • 2021-01-29
    • 2016-10-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多