__new__、__init__ 和元类(和超类)

Ant*_*osa 8 inheritance metaprogramming metaclass class python-3.x

我花了一天的时间试图理解 python 类模型的复杂性,弄乱了装饰器、元类和超类。

目前,我正在尝试弄清楚某些令牌函数的作用,即新的(这里的背景故事元类以及何时/如何调用函数

我制作了一个新的模型模块来运行测试,在这里:

#! /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()
Run Code Online (Sandbox Code Playgroud)

TL;DR,我在这些工件之间尝试了一些不同的设置。

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

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'
Run Code Online (Sandbox Code Playgroud)

由此,我有几个问题:

  • 我应该调用实例吗?init (instance, *list, **map) 在函数的末尾?我不这么认为,但是将其添加到“简单”示例中似乎有效,而“超级”从未达到其init。我的印象是通过调用 object. 电话在我自己的调用方法,这会被它的缺省实现来处理,但没有__call__s是整个计划期间进行。

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

  • 我希望电话会出现在此列表中的某个地方。它不会在对象的创建过程中被调用(例如 [prepare]、new、init)?

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

Oli*_*çon 7

Metaclass' __new__

The method __new__ is what is called to create a new instance. Thus its first argument is not an instance, since none has been created yet, but rather the class itself.

In the case of a metaclass, __new__ is expected to return an instance of your metaclass, that is a class. It's signature goes like this:

class Meta(type):
    def __new__(metacls, name, bases, namespace, **kwargs):
        ...
Run Code Online (Sandbox Code Playgroud)
  • metacls is the metaclass itself.

  • name is a string representing the name of the class being instantiated.

  • bases is a tuple of classes from which the class will inherit.

  • namespace is the namespace of the class, this is the object returned by __prepare__, now populated with the class attributes.

  • **kwargs are any keyword arguments passed to the class at instantiation

To instantiate a class, you need to call type.__new__, which is the default metaclass. You usually do that by calling 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
Run Code Online (Sandbox Code Playgroud)

Metaclass' __init__

The __init__ method behaves the same as for any other class. It receives the created instance, here a class, as argument if __new__ returned an instance of the expected type. In your example, __new__ does not return an object of type Meta. It return Meta itself which is of type type.

The following __init__ method is never called on instantiation.

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
        
Run Code Online (Sandbox Code Playgroud)

The following is called on instantiation, because Meta.__new__ correctly returns an object of type 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')
Run Code Online (Sandbox Code Playgroud)

Metaclass' __call__

Again, the behaviour of __call__ is no different than for any other class. It is called when you try to call an instance of the metaclass, while __new__ and __init__ are called when you called the metaclass to create an instance (a class).

Of course, calling a class is expected to return an instance, so do not forget to call super().__call__ and return its result, otherwise you would be short-circuiting the instance creation, as it is type.__call__ which calls __new__ and __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()
Run Code Online (Sandbox Code Playgroud)

  • 元类 `__new__` 也应该采用 `**kwargs`,即使只是将它们转发到 `super().__new__`。另外,如果你编写一个元类 `__init__` (通常是一个坏主意 - 元类应该在 `__new__` 中执行初始化),它也应该采用名称、基数、命名空间和 kwargs。 (2认同)