【问题标题】:Is there any reason to choose __new__ over __init__ when defining a metaclass?定义元类时是否有任何理由选择 __new__ 而不是 __init__ ?
【发布时间】:2010-12-22 20:42:35
【问题描述】:

我总是像这样设置元类:

class SomeMetaClass(type):
    def __new__(cls, name, bases, dict):
        #do stuff here

但我刚刚遇到了一个这样定义的元类:

class SomeMetaClass(type):
    def __init__(self, name, bases, dict):
        #do stuff here

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

更新:请记住,我问的是在元类中使用 __new____init__。我已经了解他们在另一个班级中的区别。但是在元类中,我不能使用__new__ 来实现缓存,因为__new__ 仅在元类中创建类时调用。

【问题讨论】:

    标签: python constructor new-operator metaclass init


    【解决方案1】:

    实际上有几个不同。

    一方面,__new____init__ 中的第一个参数不一样,这对每个人都没有帮助,cls。有人指出了这一点,这是理解差异的核心:

    • __new__ 在我的示例中获得 元类 - MyType(请记住尚未创建应用程序级类)。您可以在此处更改bases(如果您不小心,可能会导致 MRO 解析错误)。

    • __init__ 获取新创建的应用程序级BarFoo,到那时,该类的命名空间已被填充,请参阅cls_attrib在下面的示例中。

    示例代码:

    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"
    

    输出:

     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
    

    【讨论】:

      【解决方案2】:

      如前所述,如果您打算更改基类或属性等内容,则必须在 __new__ 中进行。班级的name 也是如此,但它似乎有一个特殊性。当您更改name 时,它不会传播到__init__,即使例如attr 是。

      所以你会有:

      class Meta(type):
          def __new__(cls, name, bases, attr):
              name = "A_class_named_" + name
              return type.__new__(cls, name, bases, attr)
      
          def __init__(cls, name, bases, attr):
              print "I am still called '" + name + "' in init"
              return super(Meta, cls).__init__(name, bases, attr)
      
      class A(object):
          __metaclass__ = Meta
      
      print "Now I'm", A.__name__
      

      打印

      I am still called 'A' in init
      Now I'm A_class_named_A
      

      知道这一点很重要,如果__init__ 调用了一个超级元类,它会产生一些额外的魔力。在这种情况下,在调用super.__init__ 之前必须再次更改名称。

      【讨论】:

      • __init__ 被调用,参数 name 设置为原始名称,因此使用与 __new__ 相同的参数。不过cls.__name__当时应该是新的。
      • 即,使用print "I am still called '" + name + "' in init but have '" + cls.__name__ + "'" 你会得到I am still called 'A' in init but have 'A_class_named_A'
      【解决方案3】:

      如果你想在创建类之前改变属性字典,或者改变基础元组,你必须使用__new__。当__init__ 看到参数时,类对象已经存在。此外,如果您想要返回的不是新创建的相关类型的类,则必须使用 __new__

      另一方面,当__init__ 运行时,该类确实存在。因此,您可以执行以下操作,例如将刚刚创建的类引用到其成员对象之一。

      编辑:更改了措辞,更清楚地表明“对象”是指类对象。

      【讨论】:

      • 更清晰。这是有道理的。不过有什么理由使用__init__ 吗?
      • __new__ 可以做所有__init__ 可以做的事情,但反过来不行。 __init__ 可以更方便地使用,就像 __new____init__ 在常规类实例化中相关。例如,您必须记住要从 __new__ 返回一些东西,但对于 __init__,您不需要。
      • @JasonBaker 我意识到这是一个已有 5 年历史的线程,我不希望得到任何回复。当您需要创建相关类的实例化时,您不想使用 init 吗?就像flyweight pattern 一样。
      【解决方案4】:

      您可以在the official docs 中看到完整的文章,但基本上,__new__ 被称为之前新对象被创建(为了创建它)并且__init__ 被称为在新对象被创建之后(为了初始化它)。

      使用__new__ 可以实现对象缓存(总是为相同的参数返回相同的对象而不是创建新的对象)或生成与请求不同的类的对象(有时用于返回请求的类的更具体的子类)。一般来说,除非你在做一些非常奇怪的事情,否则__new__ 的效用有限。如果您不需要调用此类技巧,请坚持使用__init__

      【讨论】:

      • 我了解__new____init__ 对于普通课程的区别。我在问我为什么要将它们用于元类。
      • @JasonBaker 机制完全一样。类是其元类的对象,因此它们的创建方式相同。
      【解决方案5】:

      您可以实现缓存。 Person("Jack") 在第二个示例中始终返回一个新对象,而您可以在第一个示例中使用 __new__ 查找现有实例(或者如果您愿意,则不返回任何内容)。

      【讨论】:

        猜你喜欢
        • 2011-03-09
        • 2014-03-01
        • 2014-02-12
        • 1970-01-01
        • 2010-12-31
        • 1970-01-01
        • 1970-01-01
        • 2015-12-04
        • 2018-11-11
        相关资源
        最近更新 更多