【问题标题】:Does Python's builtin type.__init__(name,bases,dct) do anything?Python 的内置 type.__init__(name,bases,dct) 有什么作用吗?
【发布时间】:2019-12-22 06:00:06
【问题描述】:

我已经看到一些 Python 元类的示例使用 super() 调用 type.__init__()。这是做什么的?

例子:

class Meta(type):
    def __new__(cls, name, bases, dct):
        dct['a']='a'
        cls_obj = super(Meta, cls).__new__(cls, name, bases, dct)
        return cls_obj

    def __init__(cls_obj, name, bases, dct):
        cls_obj.b = 'b'
        dct['c'] = 'c'
        #what does this do
        super(Meta, cls_obj).__init__(name, bases, dct)

class Meta2(Meta):
    def __init__(cls_obj, name, bases, dct):
        cls_obj.b = 'b'

class Klass(metaclass=Meta):
    pass

class Klass2(metaclass=Meta2):
    pass


if __name__ == '__main__':
    print(Klass.a)
    print(Klass.b)
    print(Klass.c)

输出:

a
b
<...my system traceback...>
AttributeError: type object 'Klass' has no attribute 'c'

显然dct 不用于更新Klass.__dict__。据我所知,这没有任何作用。它有什么作用吗?您是否有理由想要包含它? Klass 和 Klass2 之间有什么有效区别吗?

注意:我说的是Meta 继承自type 的情况,而不是某个自定义超类。

【问题讨论】:

    标签: python metaclass python-internals


    【解决方案1】:

    type.__init__() implementation 确实什么都不做,除了验证参数计数和调用 object.__init__(cls)(除了一些健全性检查之外什么都不做)。

    但是,对于继承自其他 mixin 元类的元类,使用 super().__init__(name, bases, namespace) 可确保参考 MRO 中的所有元类。

    例如当使用多个元类来建立一个新的元类时,通过super().__init__() 调用的内容会发生变化:

    >>> class MetaFoo(type):
    ...     def __init__(cls, name, bases, namespace):
    ...         print(f"MetaFoo.__init__({cls!r}, {name!r}, {bases!r}, {namespace!r})")
    ...         super().__init__(name, bases, namespace)
    ...
    >>> class MetaMixin(type):
    ...     def __init__(cls, name, bases, namespace):
    ...         print(f"MetaMixin.__init__({cls!r}, {name!r}, {bases!r}, {namespace!r})")
    ...         super().__init__(name, bases, namespace)
    ...
    >>> class MetaBar(MetaFoo, MetaMixin):
    ...     def __init__(cls, name, bases, namespace):
    ...         print(f"MetaBar.__init__({cls!r}, {name!r}, {bases!r}, {namespace!r})")
    ...         super().__init__(name, bases, namespace)
    ...
    >>> class Foo(metaclass=MetaFoo): pass
    ...
    MetaFoo.__init__(<class '__main__.Foo'>, 'Foo', (), {'__module__': '__main__', '__qualname__': 'Foo'})
    >>> class Bar(metaclass=MetaBar): pass
    ...
    MetaBar.__init__(<class '__main__.Bar'>, 'Bar', (), {'__module__': '__main__', '__qualname__': 'Bar'})
    MetaFoo.__init__(<class '__main__.Bar'>, 'Bar', (), {'__module__': '__main__', '__qualname__': 'Bar'})
    MetaMixin.__init__(<class '__main__.Bar'>, 'Bar', (), {'__module__': '__main__', '__qualname__': 'Bar'})
    

    注意MetaMixin() 是如何被最后调用的(在type.__init__() 被调用之前)。如果MetaMixin.__init__() 查询namespace 字典以供自己使用,那么在MetaFoo.__init__() 中更改namespace 将改变MetaMixin.__init__() 在该字典中找到的内容。

    因此,对于在元类的 __init__ 方法中使用 super() 的情况,您可能需要检查更复杂的元类层次结构。或者该项目只是为了安全起见,并确保在更复杂的场景中可以继承它们的元类。

    namespace 字典参数(您使用名称 dct)在将其用作类属性字典之前已复制,因此在 __init__ 中添加新键实际上不会, 修改类字典。

    【讨论】:

      【解决方案2】:

      type.__init__ 仅确保:

      • 没有保留关键字参数如果恰好存在 1 个位置参数,
      • 提供了 1 个或 3 个位置参数,并且
      • object.__init__ 被调用,它
        • 检查它是否被正确调用。

      name、bases 或 dct 都没有被 __init__ 方法实际使用。除了初始调用已经完成的操作(例如检查参数的有效性)之外,没有任何副作用。

      因此,跳过对super().__init__(name, bases, dct) 的调用不会产生任何负面影响如果超类是type。然而,与继承自 object 的类类似,调用 super().__init__ 是保持面向未来的正确设计 - 既适用于继承层次结构的更改,也适用于 type。

      【讨论】:

        猜你喜欢
        • 2018-06-03
        • 1970-01-01
        • 2010-10-31
        • 2020-05-13
        • 2010-12-22
        • 2016-03-21
        • 2023-01-16
        • 2017-09-02
        • 1970-01-01
        相关资源
        最近更新 更多