【问题标题】:python metaclass inheritance issuepython元类继承问题
【发布时间】:2016-07-19 02:12:53
【问题描述】:

我有一个有点奇怪的元类问题。我正在使用元类动态创建一个继承自另一个超类的“兄弟”类,并将其分配为原始类的属性。下面是一个最小的设置:

class Meta(type):
def __new__(cls, name, parents, dct):
    sdct = dct.copy()
    dct['sibling'] = type(name+'Sibling', (Mom,), sdct)
    return super().__new__(cls, name, (Dad,), dct)

class Mom:
     def __init__(self):
         self.x = 3

class Dad:
     def __init__(self):
         self.x = 4

class Child(metaclass=Meta):
     def __init__(self):
         super().__init__()
         self.y = 1  # <<< added from feedback
print(Child().x) # 4
print(Child().sibling) # <class '__main__.Child'> | should be ChildSibling
print(Child().sibling().x) # should be 3 instead throws:
    # TypeError: super(type, obj): obj must be an instance or subtype of type
print(Child().sibling().y) # should print 4

上面的“兄弟”类的创建似乎出了点问题,但我不太确定是什么。例如,我知道这会起作用:

class ChildAbstract:
    def __init__(self):
        super().__init__()

ChildSibling = type('ChildSibling', (ChildAbstract, Mom), {})
Child = type('Child', (ChildAbstract, Dad), {'sibling': ChildSibling})
print(Child().sibling().x) # 3

不过,我看不出这两种情况之间的区别。

【问题讨论】:

    标签: python inheritance metaclass


    【解决方案1】:

    传递给type的字典sdct包括__qualname__,根据这个PEPreprstr 现在使用。

    尝试添加

    print(Child is Child.sibling)  # False
    print(Child.sibling.__name__)  # "ChildSibling"
    

    你会发现它确实是兄弟姐妹。

    至于为什么sibling().x 抛出,同样的sdct 也已经包含Child.__init__,它最终是您动态创建的新类型ChildSibling__init__。在调用sibling() 期间,super() 调用将类解析为Child 并获得ChildSibling 的实例:

    还要注意,除了零参数形式之外,super() 不限于使用内部方法。两个参数形式精确地指定参数并进行适当的引用。零参数形式仅适用于类定义,编译器填写必要的详细信息以正确检索正在定义的类,以及访问普通方法的当前实例。

    https://docs.python.org/3/library/functions.html#super

    通过将第一个参数作为实例传递给方法来访问当前实例。

    super() -&gt; same as super(__class__, &lt;first argument&gt;)

    line 7210 of Object/typeobject.c 引发错误。

    尝试删除__new__ 中的错误__init__

    del sdct['__init__']
    

    现在

    print(Child().sibling().x)
    

    将打印 3。

    “通用”继承和元编程友好的解决方案 __init__ 是使用 super() 的 2 参数形式:

    def __init__(self):
        super(self.__class__, self).__init__()
    

    【讨论】:

    • 有趣的一点,从字典中删除 init 是可行的,但这只是因为在我的玩具示例中,我在 Child.__init__ 中没有做任何事情,所以这并不能完全解决问题(更新代码来说明)。我会看看 qualname 不过
    • 啊,关于 super 的注释说明了这一点
    • @Buck 您的第二个示例有效的原因是这两种动态类型的基础包括ChildAbstract,因此super() 很高兴。
    • 所以super().__init__() -&gt; super(self.__class__, self).__init__()解决了这个问题,谢谢@Ilja
    • @Buck 也将您的解决方案作为答案的一部分,因此更容易找到
    猜你喜欢
    • 2010-10-30
    • 2020-07-26
    • 1970-01-01
    • 2016-07-24
    • 1970-01-01
    • 2011-08-25
    相关资源
    最近更新 更多