__new__、__init__ 和元类(和超类)

Posted

技术标签:

【中文标题】__new__、__init__ 和元类(和超类)【英文标题】:__new__, __init__, and metaclasses (and superclasses) 【发布时间】:2018-11-11 03:15:46 【问题描述】:

我花了一天的时间试图理解 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()

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'

由此,我有几个问题:

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

为什么调用 Triple() 会先调用元类 new?如果这是正常的,这是否意味着这是任何具有元类的类的典型情况?这种行为是否与超类相似?

我预计 call 会出现在此列表中的某个位置。在对象的创建例程(例如 [prepare]、new、init)期间不会调用它吗?

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

【问题讨论】:

您似乎认为__new__ 将新实例作为其第一个参数。它没有;这个论点就是类。 __new__ 应该创建新实例,通常通过调用super().__new__ 我认为你的大部分问题都是这种误解造成的。 调用是运行新的和初始化的。无论如何,执行X() 都会调用对象X__call__ 方法,因为这正是操作员所做的。其实是type(X).__call__(X) @user2357112 我不知道。谢谢!我将牢记这一点来修复我的代码,通过调用 super,并可能返回它返回的内容。 @MadPhysicist,这就是我的想法,但是我无法解释为什么在打印的调用方法中没有打印。 【参考方案1】:

元类'__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__ 的行为与任何其他类没有什么不同。它在您尝试调用元类的instance 时调用,而__new____init__ 在您调用元类 创建实例(类)。

当然,调用类应该返回一个实例,所以不要忘记调用super().__call__并返回它的结果,否则你会短路实例创建,因为它是type.__call__调用@ 987654353@和__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()

【讨论】:

元类__new__ 也应该采用**kwargs,即使只是将它们转发给super().__new__。此外,如果您编写元类 __init__(通常是个坏主意 - 元类应该在 __new__ 中执行初始化),它也应该包含名称、基数、命名空间和 kwargs。 至于__init__,为什么会这样?在上面的例子中,它是在__new__ 之后调用的,正如预期的那样 用于将 kwargs 传递给元类:super().__new__(metacls, name, bases, namespace, **kwargs)。对于 __init__,请参阅 __init__ 的 this demo 需要比您声明的参数更多的参数。 @user2357112 你是对的,我忘记了__init__ 签名中的参数。虽然,我认为没有理由在__new__ 内调用__init__。至于 kwargs,我不同意 type.__new__ 不接受关键字参数。 @OlivierMelançon。我同意这样的讨论太过分了。但是,是的,在 Python 3 中,metaclass 本身就是这些参数之一,但它得到了特殊处理。即使它不接受 kwargs,它也不会使您的 meta-new 崩溃。

以上是关于__new__、__init__ 和元类(和超类)的主要内容,如果未能解决你的问题,请参考以下文章

Python基础入门自学——12--new方法和元类

自定义元类 __call__,__init__,__new__总结

描述符get/set/delete,init/new/call,元类

__init__和__new__

自定义元类和元类的用途

旧式类、新式类和元类