__new__、__init__和元类(以及超类)

10
我一整天都在努力理解Python类模型的复杂性,尝试使用装饰器、元类和超类等。目前,我正在试图弄清楚某些标记函数的作用,即new(背景故事在此处:Metaclasses and when/how functions are called)。
我已经制作了一个新的模拟模块来运行测试,这里是它的链接:
#! /usr/bin/env python3

import sys as system
import os  as operating_system

from functools import partial
from time      import perf_counter as counter

class Meta(type):

    @classmethod
    def __prepare__(instance, name, supers, *list, **map):
        print('{} in meta prepare'.format(name))
        return {}

    def __new__(instance, name, supers, attributes, *list, **map):
        print('{} in meta new'.format(name))
        return instance

    def __init__(self, name, supers, attributes, *list, **map):
            print('{} in meta init'.format(self))

    def __call__(self, *list, **map):
        print('{} in meta call'.format(self))
        return type.__call__(self)
        print('after call')

class Super(object):

    def __new__(instance, *list, **map):
        print('{} in Super new'.format(instance))
        return instance

    def __init__(self, *list, **map):
        print('{} in Super init'.format(self))

    def __call__(self, *list, **map):
        print('{} in Super call'.format(self))
        return object.__call__(self)

class Other(object):

    def __new__(instance, *list, **map):
        print('{} in Other new'.format(instance))
        return instance

    def __init__(self, *list, **map):
        print('{} in Other init'.format(self))

    def __call__(self, *list, **map):
        print('{} in Other call'.format(self))
        return object.__call__(self)

class MetaSuper(object, metaclass = Meta):

    def __new__(instance, *list, **map):
        print('{} in MetaSuper new'.format(instance))
        return instance

    def __init__(self, *list, **map):
        print('{} in MetaSuper init'.format(self))

    def __call__(self, *list, **map):
        print('{} in MetaSuper call'.format(self))
        return object.__call__(self)

class DoubleSuper(Super, MetaSuper):

    def __new__(instance, *list, **map):
        print('{} in DoubleSuper new'.format(instance))
        return instance

    def __init__(self, *list, **map):
        print('{} in DoubleSuper init'.format(self))
        Super.__init__(self, *list, **map)
        MetaSuper.__init__(self, *list, **map)

    def __call__(self, *list, **map):
        print('{} in DoubleSuper call'.format(self))
        return object.__call__(self)

class SuperThenMeta(Super, metaclass = Meta):

    def __new__(instance, *list, **map):
        print('{} in SuperThenMeta new'.format(instance))
        return instance

    def __init__(self, *list, **map):
        print('{} in SuperThenMeta init'.format(self))
        Super.__init__(self, *list, **map)

    def __call__(self, *list, **map):
        print('{} in SuperThenMeta call'.format(self))
        return object.__call__(self)

class Triple(Super, Other, metaclass = Meta):

    def __new__(instance, *list, **map):
        print('{} in Triple new'.format(instance))
        return instance

    def __init__(self, *list, **map):
        print('{} in Triple init'.format(self))
        Super.__init__(self, *list, **map)
        Other.__init__(self, *list, **map)

    def __call__(self, *list, **map):
        print('{} in Triple call'.format(self))
        return object.__call__(self)

class Simple(Super):

    def __new__(instance, *list, **map):
        print('{} in Simple new'.format(instance))
        return instance.__init__(instance, *list, **map)

    def __init__(self, *list, **map):
        print('{} in Simple init'.format(self))
        Super.__init__(self, *list, **map)
        Other.__init__(self, *list, **map)

    def __call__(self, *list, **map):
        print('{} in Simple call'.format(self))
        return object.__call__(self)    

def main():
    #thing = SuperThenMeta()
    #other = DoubleSuper()
    last  = Super()
    simp  = Simple()
    trip  = Triple()

if __name__ == '__main__':
    main()

简而言之,我在这些工作部件之间尝试了几种不同的设置。

如果我运行这个,这就是输出结果:

MetaSuper in meta prepare
MetaSuper in meta new
SuperThenMeta in meta prepare
SuperThenMeta in meta new
Triple in meta prepare
Triple in meta new
<class '__main__.Super'> in Super new
<class '__main__.Simple'> in Simple new
<class '__main__.Simple'> in Simple init
<class '__main__.Simple'> in Super init
<class '__main__.Simple'> in Other init
Traceback (most recent call last):
File "./metaprogramming.py", line 134, in <module>
  main()
File "./metaprogramming.py", line 131, in main
  trip = Triple()
TypeError: __new__() missing 3 required positional arguments: 'name', 'supers', and 'attributes'

从这里,我有几个问题:
  • 我是否应该在new函数的结尾处调用instance.init(instance, *list, **map)?我认为不需要,但是将其添加到“Simple”示例中似乎起作用了,而“Super”从未达到其init。我认为通过在自己的调用方法中调用object.call,这将由其默认实现处理,但整个程序中没有进行__call__。

  • 为什么调用Triple()会首先调用元类的new?如果这是正常的,那么这是否是具有元类的任何类的典型特征?这与超类的类似行为吗?

  • 我希望在这个列表中找到call。它在对象的创建过程中(例如[prepare]、new、init)不会被调用吗?

我知道这是很多信息,所以感谢您阅读到这里;任何指导都将不胜感激。

3
你似乎认为__new__方法的第一个参数是新实例。实际上,它的第一个参数是类本身。__new__方法的作用是创建新实例,通常通过调用super().__new__来完成。 - user2357112
2
我认为你的大部分问题都是这种误解的后果。 - user2357112
调用是运行新的和初始化的过程。执行 X() 会调用对象 X__call__ 方法,因为这就是该操作符的作用。实际上,它是 type(X).__call__(X) - Mad Physicist
@user2357112 我不知道。谢谢!我会记住这个,通过调用super并可能返回它的返回值来修复我的代码。 - Anthony
@MadPhysicist,这就是我想的,但我无法解释为什么调用方法中没有任何打印输出。 - Anthony
1个回答

11

元类的__new__

__new__方法被调用来创建一个新实例。因此,它的第一个参数不是实例,因为尚未创建任何实例,而是类本身。

对于一个元类,__new__应该返回一个元类的实例,也就是一个。其签名如下:

class Meta(type):
    def __new__(metacls, name, bases, namespace, **kwargs):
        ...
  • metacls 是元类本身。

  • name 是表示正在实例化的类的名称的字符串。

  • bases 是一个包含该类将继承的类的元组。

  • namespace 是类的名称空间,这是由__prepare__返回的对象,现在已经填充了类属性。

  • **kwargs 是传递给类的关键字参数。

要实例化一个类,您需要调用 type.__new__,它是默认的元类。通常通过调用 super().__new__ 来完成此操作。

class Meta(type):
    def __new__(metacls, name, bases, namespace, **kwargs):
        print('You can do stuff here')

        cls = super().__new__(metacls, name, bases, namespace, **kwargs)

        # You must return the generated class
        return cls

元类的__init__

__init__ 方法的行为与任何其他类相同。如果 __new__ 返回了预期类型的实例,则会将创建的实例(在这里是一个类)作为参数传递给它。 在您的示例中,__new__ 并没有返回Meta 类型的对象。 它返回的是 Meta 本身,其类型为 type

以下的__init__方法在实例化时不会被调用。

class Meta(type):
    def __new__(metacls, name, bases, namespace, **kwargs):
        return None # or anything such that type(obj) is not Meta
    
    def __init__(self, name, bases, namespace, **kwargs):
        # This will never be called because the return type of `__new__` is wrong
        pass
        
下面的内容在实例化时调用,因为Meta.__new__正确地返回了类型为Meta的对象。
class Meta(type):
        def __new__(metacls, name, bases, namespace, **kwargs):
            return super().__new__(metacls, name, bases, namespace, **kwargs)
        
        def __init__(self, name, bases, namespace, **kwargs):
            print('__init__ was called')

元类的 __call__

__call__ 的行为与任何其他类没有区别。当你尝试调用元类的实例时,它会被调用,而在调用元类创建一个实例(即一个类)时,会调用__new____init__

当然,调用一个类预期会返回一个实例,所以不要忘记调用 super().__call__ 并返回其结果,否则你将短路了实例的创建过程,因为是type.__call__ 调用了 __new____init__

class Meta(type):
    def __call__(self, *args, **kwargs):
        print(f'An instance was called with {args}')
        return super().__call__(self, *args, **kwargs)

# This declaration if what calls __new__ and __init__ of the metaclass
class Klass(metaclass=Meta):
    pass

# This calls the __call__ method of the metaclass
instance = Klass()

2
元类 __new__ 应该接受 **kwargs,即使只是将它们转发给 super().__new__。此外,如果您编写一个元类 __init__(通常不是一个好主意 - 元类应该在 __new__ 中执行初始化),它也应该接受名称、基类、命名空间和 kwargs。 - user2357112
1
传递kwargs给元类:super().__new__(metacls, name, bases, namespace, **kwargs)。对于__init__,请参见此演示,其中__init__需要比您声明的参数更多的参数。 - user2357112
@user2357112 您是正确的,我在__init__签名中忘记了参数。虽然我看不出为什么应该在__new__内部调用__init__。至于kwargs,我不同意,因为type.__new__不接受关键字参数。 - Olivier Melançon
@OlivierMelançon。你可以通过类的参数列表传递kwargs:class A(base1, ..., baseN, kwarg1=value1, ..., kwargN=valueN) - Mad Physicist
1
@OlivierMelançon。我同意这对于这个讨论来说有些过头了。但是在Python 3中,metaclass本身就是其中一个参数,但它会得到特殊处理。即使它不接受kwargs,它也不会崩溃你的元-新建函数。 - Mad Physicist
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接