在定义元类时,有没有理由选择__new__而不是__init__?

Jas*_*ker 40 python constructor metaclass init new-operator

我总是设置类似这样的元类:

class SomeMetaClass(type):
    def __new__(cls, name, bases, dict):
        #do stuff here
Run Code Online (Sandbox Code Playgroud)

但我刚刚遇到了一个定义如下的元类:

class SomeMetaClass(type):
    def __init__(self, name, bases, dict):
        #do stuff here
Run Code Online (Sandbox Code Playgroud)

有什么理由比较喜欢一个吗?

更新:请记住,我正在询问有关使用__new____init__在元类中的问题.我已经理解了另一个班级中他们之间的区别.但是在元类中,我不能__new__用来实现缓存,因为__new__只在元类中创建类时调用它.

Mat*_*son 49

如果要在创建类之前更改属性dict,或者更改基本元组,则必须使用__new__.到__init__查看参数时,类对象已经存在.此外,__new__如果要返回除了新创建的类型之外的其他内容,则必须使用.

另一方面,通过时间__init__运行,类确实存在.因此,您可以执行诸如将刚创建的类的引用提供给其成员对象之一的操作.

编辑:改变措辞使其更清楚,"对象",我的意思是类对象.

  • `__new__`可以完成`__init__`可以做的所有事情,但不是相反.`__init__`可以更方便地使用,就像`__new__`和`__init__`在常规类实例化中相关.例如,你必须记住从`__new__`返回一些东西,但是你没有`__init__`. (6认同)

JL *_*ret 11

事实上,有几个区别。

一方面,__new__和中的第一个参数__init__是不同的,每个人都坚持只cls在这两种情况下使用(尽管变量名不具有任何特定含义),但并没有明确这一点。有人指出了这一点,这是理解差异的核心:

  • __new__获取元类-MyType在我的示例中(请记住应用程序级类尚未创建)。这是您可以更改的地方bases(如果您不小心,可能会导致 MRO 解析错误)。我将调用该变量mcls,以将其与通常cls引用的应用程序级别类区分开来。

  • __init__获取新创建的应用程序级Bar并且Foo此时该类的命名空间已被填充,请参见cls_attrib下面的示例。我将遵循cls通常的命名约定。

示例代码:

class Mixin:
    pass

class MyType(type):


    def __new__(mcls, name, bases, attrs, **kwargs):
        print("  MyType.__new__.mcls:%s" % (mcls))

        if not Mixin in bases:
            #could cause MRO resolution issues, but if you want to alter the bases
            #do it here
            bases += (Mixin,)

        #The call to super.__new__ can also modify behavior:
        #                                    classes Foo and Bar are instances of MyType
        return super(MyType, mcls).__new__(mcls, name, bases, attrs)

        #now we're back to the standard `type` 
        #doing this will neuter most of the metaclass behavior, __init__ wont
        #be called.                         
        #return super(MyType, mcls).__new__(type, name, bases, attrs)

    def __init__(cls, name, bases, attrs):
        print("  MyType.__init__.cls:%s." % (cls))
        
        #I can see attributes on Foo and Bar's namespaces
        print("    %s.cls_attrib:%s" % (cls.__name__, getattr(cls, "cls_attrib", None)))
        return super().__init__(name, bases, attrs)


print("\n Foo class creation:")
class Foo(metaclass=MyType):
    pass


print("\n bar class creation:")
class Bar(Foo):
    #MyType.__init__ will see this on Bar's namespace
    cls_attrib = "some class attribute"
Run Code Online (Sandbox Code Playgroud)

输出:

 Foo class creation:
  MyType.__new__.mcls:<class '__main__.test.<locals>.MyType'>
  MyType.__init__.cls:<class '__main__.test.<locals>.Foo'>.
    Foo.cls_attrib:None

 Bar class creation:
  MyType.__new__.mcls:<class '__main__.test.<locals>.MyType'>
  MyType.__init__.cls:<class '__main__.test.<locals>.Bar'>.
    Bar.cls_attrib:some class attribute
Run Code Online (Sandbox Code Playgroud)


Ben*_*ank 7

您可以在官方文档中看到完整的说明,但基本上,创建新对象之前__new__调用(为了创建它)并创建新对象之后调用(为了初始化它)。__init__

使用__new__允许诸如对象缓存(总是为相同的参数返回相同的对象而不是创建新的对象)或生成与请求的类不同的对象(有时用于返回所请求类的更具体的子类)之类的技巧。通常,除非您正在做一些非常奇怪的事情,否则__new__实用性有限。如果您不需要调用此类技巧,请坚持使用__init__.

  • @JasonBaker 机制完全一样。类是其元类的对象,因此它们的创建方式相同。 (5认同)