【问题标题】:Why is __init__() always called after __new__()?为什么 __init__() 总是在 __new__() 之后调用?
【发布时间】:2010-10-15 00:42:38
【问题描述】:

我只是想简化我的一个类,并引入了一些与flyweight design pattern 风格相同的功能。

但是,我有点困惑为什么__init__ 总是在__new__ 之后调用。我没想到会这样。谁能告诉我为什么会发生这种情况以及如何实现此功能? (除了将实现放到__new__ 中,感觉很hacky。)

这是一个例子:

class A(object):
    _dict = dict()

    def __new__(cls):
        if 'key' in A._dict:
            print "EXISTS"
            return A._dict['key']
        else:
            print "NEW"
            return super(A, cls).__new__(cls)

    def __init__(self):
        print "INIT"
        A._dict['key'] = self
        print ""

a1 = A()
a2 = A()
a3 = A()

输出:

NEW
INIT

EXISTS
INIT

EXISTS
INIT

为什么?

【问题讨论】:

  • 也在尝试理解设计模式,并且第一次听说 :flyweight 设计模式.. 以及在几乎所有流行语言中都有示例的非常好的链接。
  • 不是单例吗?

标签: python design-patterns class-design


【解决方案1】:

需要控制时使用__new__ 创建一个新实例。

使用 __init__ 当您需要控制新实例的初始化时。

__new__ 是创建实例的第一步。它首先被调用,并且是 负责退回新的 你的类的实例。

相比之下, __init__ 不返回任何内容;它只负责初始化 创建后的实例。

一般来说,您不需要 覆盖 __new__ 除非你是 子类化不可变类型,例如 str、int、unicode 或元组。

自 2008 年 4 月起发帖:When to use __new__ vs. __init__?@mail.python.org。

您应该考虑到您正在尝试做的事情通常是使用Factory 完成的,这是最好的方法。使用 __new__ 不是一个好的清洁解决方案,因此请考虑使用工厂。这是一个很好的例子:ActiveState Fᴀᴄᴛᴏʀʏ ᴘᴀᴛᴛᴇʀɴ Recipe

【讨论】:

  • 最终在 Factory 类中使​​用了 __new__,该类变得非常干净,因此感谢您的意见。
  • 抱歉,我不同意 __new__ 的使用应严格限于所述情况。我发现它对于实现可扩展的泛型类工厂非常有用——请参阅my answer 问题不正确地使用__new__ 在Python 中生成类? 以获取这样做的示例。跨度>
  • 我强烈反对 __new__ 在这里是一个糟糕的解决方案。工厂模式在限制构造函数充当初始化器的语言中是必需的(防止您返回现有对象);像大多数设计模式一样(尤其是早期专门为 Java 发明的大量设计模式,由于语言不灵活),它是一种以一致的方式解决语言限制的方法。 Python 没有这个限制。对于这种情况,您使用 __new__,并使用 @classmethod 替代构造函数从变体参数构造。
  • 工厂模式配方的链接不起作用。
【解决方案2】:

__new__ 是静态类方法,而 __init__ 是实例方法。 __new__ 必须先创建实例,所以 __init__ 可以初始化它。请注意,__init__self 作为参数。在您创建实例之前,没有 self

现在,我推测您正在尝试在 Python 中实现 singleton pattern。有几种方法可以做到这一点。

此外,从 Python 2.6 开始,您可以使用类 decorators

def singleton(cls):
    instances = {}
    def getinstance():
        if cls not in instances:
            instances[cls] = cls()
        return instances[cls]
    return getinstance

@singleton
class MyClass:
  ...

【讨论】:

  • @Tyler Long,我不太明白“@singleton”是如何工作的?因为装饰器返回一个函数,而 MyClass 是一个类。
  • 为什么是字典?由于 cls 将始终相同,并且您会为每个单身人士创建一个新字典,例如,您正在创建一个只有一个项目的字典。
  • @Alcott:不需要意见——the docs同意你的看法。
  • @Alcott。是的,装饰器返回一个函数。但类和函数都是可调用的。我认为instances = {} 应该是一个全局变量。
  • @TylerLong Alcott 有一个很好的观点。这导致名称 MyClass 绑定到返回原始类实例的函数。但是现在根本没有办法引用原始类,并且MyClass作为一个函数打破了isinstance/issubclass检查,直接以MyClass.something访问类属性/方法,将类命名为super调用,等等等等。这是否是一个问题取决于你应用它的类(以及程序的其余部分)。
【解决方案3】:

在大多数著名的 OO 语言中,像 SomeClass(arg1, arg2) 这样的表达式将分配一个新实例,初始化该实例的属性,然后返回它。

在大多数著名的 OO 语言中,“初始化实例的属性”部分可以通过定义一个 constructor 来为每个类定制,这基本上只是在新的代码块上运行的代码块实例(使用提供给构造函数表达式的参数)来设置所需的任何初始条件。在 Python 中,这对应于类的 __init__ 方法。

Python 的__new__ 只不过是“分配新实例”部分的类似的每个类定制。这当然允许您执行不寻常的事情,例如返回现有实例而不是分配新实例。所以在 Python 中,我们不应该真的认为这部分必然涉及分配;我们只需要__new__ 从某个地方找到一个合适的实例。

但这仍然只是工作的一半,Python 系统无法知道有时你想在之后运行另一半工作 (__init__),有时你不想。如果你想要这种行为,你必须明确地说出来。

通常,您可以重构以便只需要__new__,或者不需要__new__,或者__init__ 在已经初始化的对象上表现不同。但如果你真的想这么做,Python 确实允许你重新定义“工作”,所以SomeClass(arg1, arg2) 不一定会调用__new__ 后跟__init__。为此,您需要创建一个元类,并定义其__call__ 方法。

元类只是一个类的类。一个类的__call__ 方法控制调用类实例时发生的情况。所以 metaclass' __call__ 方法控制了当你调用一个类时会发生什么;即它允许您从头到尾重新定义实例创建机制。这是您可以最优雅地实现完全非标准的实例创建过程(例如单例模式)的级别。事实上,只需不到 10 行代码,您就可以实现一个 Singleton 元类,然后甚至不需要您使用 __new__ 完全,并且可以任意 只需添加 __metaclass__ = Singleton!

class Singleton(type):
    def __init__(self, *args, **kwargs):
        super(Singleton, self).__init__(*args, **kwargs)
        self.__instance = None
    def __call__(self, *args, **kwargs):
        if self.__instance is None:
            self.__instance = super(Singleton, self).__call__(*args, **kwargs)
        return self.__instance

然而,这可能是比这种情况真正需要的更深层次的魔法!

【讨论】:

  • 太棒了:o 简单,不会像装饰那样让课堂上的工作变得烦人。
【解决方案4】:

引用documentation

典型的实现通过调用来创建类的新实例 超类的 __new__() 方法使用“super(currentclass, cls).__new__(cls[, ...])"带有适当的参数,然后 在返回之前根据需要修改新创建的实例。

...

如果 __new__() 没有返回 cls 的实例,那么 new 实例的 __init__() 方法不会被调用。

__new__() 主要是为了允许不可变的子类 用于自定义实例创建的类型(如 int、str 或 tuple)。

【讨论】:

  • If __new__() does not return an instance of cls, then the new instance's __init__() method will not be invoked. 这很重要。如果您返回不同的实例,则永远不会调用原始的 __init__
  • @tgray 我知道您的答案来自文档,但我很好奇您是否知道任何不返回 cls 实例的用例。似乎当检查 __new__ 的返回对象的类时,该方法会抛出错误,而不是让失败的检查静默通过,因为我不明白为什么你想返回除对象之外的任何东西类的。
  • @soporific312 我还没有看到任何用例。 This answer 讨论了设计的一些原因,尽管他们也没有看到任何利用该“功能”的代码。
【解决方案5】:

我意识到这个问题已经很老了,但我遇到了类似的问题。 以下做了我想要的:

class Agent(object):
    _agents = dict()

    def __new__(cls, *p):
        number = p[0]
        if not number in cls._agents:
            cls._agents[number] = object.__new__(cls)
        return cls._agents[number]

    def __init__(self, number):
        self.number = number

    def __eq__(self, rhs):
        return self.number == rhs.number

Agent("a") is Agent("a") == True

我将此页面用作资源http://infohost.nmt.edu/tcc/help/pubs/python/web/new-new-method.html

【讨论】:

  • 注意:__new__ 总是返回一个合适的对象,所以 __init__ 总是被调用——即使实例已经存在。
【解决方案6】:

__new__ 返回同一类的实例时,__init__ 会在返回的对象上运行。 IE。您不能使用__new__ 来阻止__init__ 运行。即使您从__new__ 返回先前创建的对象,它也会被__init__ 一次又一次地初始化为两倍(三倍等)。

这是单例模式的通用方法,它扩展了上面的 vartec 答案并对其进行了修复:

def SingletonClass(cls):
    class Single(cls):
        __doc__ = cls.__doc__
        _initialized = False
        _instance = None

        def __new__(cls, *args, **kwargs):
            if not cls._instance:
                cls._instance = super(Single, cls).__new__(cls, *args, **kwargs)
            return cls._instance

        def __init__(self, *args, **kwargs):
            if self._initialized:
                return
            super(Single, self).__init__(*args, **kwargs)
            self.__class__._initialized = True  # Its crucial to set this variable on the class!
    return Single

全文是here

实际上涉及__new__的另一种方法是使用classmethods:

class Singleton(object):
    __initialized = False

    def __new__(cls, *args, **kwargs):
        if not cls.__initialized:
            cls.__init__(*args, **kwargs)
            cls.__initialized = True
        return cls


class MyClass(Singleton):
    @classmethod
    def __init__(cls, x, y):
        print "init is here"

    @classmethod
    def do(cls):
        print "doing stuff"

请注意,使用这种方法,您需要用@classmethod 装饰所有方法,因为您永远不会使用MyClass 的任何真实实例。

【讨论】:

    【解决方案7】:

    我认为这个问题的简单答案是,如果__new__ 返回一个与类相同类型的值,则__init__ 函数会执行,否则不会。在这种情况下,您的代码返回 A._dict('key'),它与 cls 是同一类,因此将执行 __init__

    【讨论】:

      【解决方案8】:
      class M(type):
          _dict = {}
      
          def __call__(cls, key):
              if key in cls._dict:
                  print 'EXISTS'
                  return cls._dict[key]
              else:
                  print 'NEW'
                  instance = super(M, cls).__call__(key)
                  cls._dict[key] = instance
                  return instance
      
      class A(object):
          __metaclass__ = M
      
          def __init__(self, key):
              print 'INIT'
              self.key = key
              print
      
      a1 = A('aaa')
      a2 = A('bbb')
      a3 = A('aaa')
      

      输出:

      NEW
      INIT
      
      NEW
      INIT
      
      EXISTS
      

      NB 作为副作用,M._dict 属性会自动从A 访问为A._dict,因此请注意不要意外覆盖它。

      【讨论】:

      • 您缺少设置cls._dict = {}__init__ 方法。您可能不希望此元类型的所有类共享字典(但为这个想法 +1)。
      【解决方案9】:

      Referring to this doc

      当子类化不可变的内置类型(如数字和字符串)时, 偶尔在其他情况下,静态方法 new 来了 派上用场。 new 是实例构造的第一步,被调用 初始化之前。

      new 方法以类为调用 第一个论点;它的职责是返回一个新的实例 班级。

      将此与 init 进行比较:init 是用实例调用的 作为它的第一个参数,它不返回任何东西;它的 职责是初始化实例。

      有些情况 在不调用 init 的情况下创建新实例(例如 当实例从泡菜加载时)。没有办法创建 一个不调用 new 的新实例(尽管在某些情况下你可以 摆脱调用基类的new)。

      关于您希望达到的目标,在相同的文档信息中也有关于单例模式的信息

      class Singleton(object):
              def __new__(cls, *args, **kwds):
                  it = cls.__dict__.get("__it__")
                  if it is not None:
                      return it
                  cls.__it__ = it = object.__new__(cls)
                  it.init(*args, **kwds)
                  return it
              def init(self, *args, **kwds):
                  pass
      

      你也可以使用 PEP 318 中的这个实现,使用装饰器

      def singleton(cls):
          instances = {}
          def getinstance():
              if cls not in instances:
                  instances[cls] = cls()
              return instances[cls]
          return getinstance
      
      @singleton
      class MyClass:
      ...
      

      【讨论】:

      • __new__ 调用init 的混蛋形式听起来真的很老套。这就是元类的用途。
      【解决方案10】:

      @AntonyHatchkins 答案的更新,您可能希望元类型的每个类都有一个单独的实例字典,这意味着您应该在元类中有一个 __init__ 方法来使用该字典初始化您的类对象,而不是制作它全局所有类。

      class MetaQuasiSingleton(type):
          def __init__(cls, name, bases, attibutes):
              cls._dict = {}
      
          def __call__(cls, key):
              if key in cls._dict:
                  print('EXISTS')
                  instance = cls._dict[key]
              else:
                  print('NEW')
                  instance = super().__call__(key)
                  cls._dict[key] = instance
              return instance
      
      class A(metaclass=MetaQuasiSingleton):
          def __init__(self, key):
              print 'INIT'
              self.key = key
              print()
      

      我已经使用__init__ 方法更新了原始代码,并将语法更改为 Python 3 表示法(无参数调用 super 和类参数中的元类,而不是作为属性)。

      无论哪种方式,这里的重点是,如果找到密钥,您的类初始化程序(__call__ 方法)将不会执行__new____init__。这比使用__new__ 干净得多,如果您想跳过默认的__init__ 步骤,则需要标记对象。

      【讨论】:

        【解决方案11】:

        __new__ 应该返回一个新的空白类实例。然后调用 __init__ 来初始化该实例。你没有在 __new__ 的“新”案例中调用 __init__,所以它是为你调用的。调用__new__ 的代码不会跟踪是否在特定实例上调用了__init__,也不应该跟踪它,因为您在这里做了一些非常不寻常的事情。

        您可以在 __init__ 函数中为对象添加一个属性,以表明它已被初始化。在 __init__ 中首先检查该属性是否存在,如果已经存在,则不要继续进行。

        【讨论】:

          【解决方案12】:

          再深入一点!

          CPython 中泛型类的类型为type,其基类为Object(除非您明确定义另一个基类,如元类)。低级调用序列可以在here 找到。第一个调用的方法是type_call,然后调用tp_new,然后是tp_init

          这里有趣的部分是tp_new 将调用Object 的(基类)新方法object_new 执行tp_alloc (PyType_GenericAlloc) 为对象分配内存:)

          此时在内存中创建对象,然后调用__init__ 方法。如果__init__ 没有在你的类中实现,那么object_init 被调用并且它什么都不做:)

          然后type_call 只返回绑定到您的变量的对象。

          【讨论】:

            【解决方案13】:

            应该将__init__ 视为传统OO 语言中的简单构造函数。例如,如果您熟悉 Java 或 C++,则构造函数会隐式传递一个指向其自身实例的指针。对于 Java,它是 this 变量。如果要检查为 Java 生成的字节码,会注意到两个调用。第一次调用是“new”方法,然后下一次调用是 init 方法(这是对用户定义的构造函数的实际调用)。这两个步骤可以在调用类的构造方法之前创建实际实例,而构造方法只是该实例的另一种方法。

            现在,对于 Python,__new__ 是用户可以访问的附加工具。由于其类型化的性质,Java 不提供这种灵活性。如果一种语言提供了这种功能,那么__new__ 的实现者可以在返回实例之前在该方法中做很多事情,包括在某些情况下创建一个不相关对象的全新实例。而且,这种方法也非常适用于 Python 的不可变类型。

            【讨论】:

              【解决方案14】:

              但是,我有点困惑为什么 __init__ 总是在 __new__ 之后被调用。

              我认为 C++ 类比在这里会很有用:

              1. __new__ 只是为对象分配内存。对象的实例变量需要内存来保存它,这就是__new__ 的步骤。

              2. __init__将对象的内部变量初始化为特定值(可以是默认值)。

              【讨论】:

                【解决方案15】:

                __init____new__ 之后调用,因此当您在子类中覆盖它时,您添加的代码仍会被调用。

                如果您尝试对已经具有 __new__ 的类进行子类化,则不知道这一点的人可能会首先调整 __init__ 并将调用转发到子类 __init__。这种在__new__ 之后调用__init__ 的约定有助于按预期工作。

                __init__ 仍然需要允许超类__new__ 所需的任何参数,但如果不这样做通常会产生明显的运行时错误。 __new__ 应该明确允许 *args 和 '**kw',以明确扩展是可以的。

                由于原始发帖人所描述的行为,将__new____init__ 放在同一继承级别的同一类中通常是不好的形式。

                【讨论】:

                  【解决方案16】:

                  现在我遇到了同样的问题,出于某些原因,我决定避免使用装饰器、工厂和元类。我是这样做的:

                  主文件

                  def _alt(func):
                      import functools
                      @functools.wraps(func)
                      def init(self, *p, **k):
                          if hasattr(self, "parent_initialized"):
                              return
                          else:
                              self.parent_initialized = True
                              func(self, *p, **k)
                  
                      return init
                  
                  
                  class Parent:
                      # Empty dictionary, shouldn't ever be filled with anything else
                      parent_cache = {}
                  
                      def __new__(cls, n, *args, **kwargs):
                  
                          # Checks if object with this ID (n) has been created
                          if n in cls.parent_cache:
                  
                              # It was, return it
                              return cls.parent_cache[n]
                  
                          else:
                  
                              # Check if it was modified by this function
                              if not hasattr(cls, "parent_modified"):
                                  # Add the attribute
                                  cls.parent_modified = True
                                  cls.parent_cache = {}
                  
                                  # Apply it
                                  cls.__init__ = _alt(cls.__init__)
                  
                              # Get the instance
                              obj = super().__new__(cls)
                  
                              # Push it to cache
                              cls.parent_cache[n] = obj
                  
                              # Return it
                              return obj
                  

                  示例类

                  class A(Parent):
                  
                      def __init__(self, n):
                          print("A.__init__", n)
                  
                  
                  class B(Parent):
                  
                      def __init__(self, n):
                          print("B.__init__", n)
                  

                  使用中

                  >>> A(1)
                  A.__init__ 1  # First A(1) initialized 
                  <__main__.A object at 0x000001A73A4A2E48>
                  >>> A(1)      # Returned previous A(1)
                  <__main__.A object at 0x000001A73A4A2E48>
                  >>> A(2)
                  A.__init__ 2  # First A(2) initialized
                  <__main__.A object at 0x000001A7395D9C88>
                  >>> B(2)
                  B.__init__ 2  # B class doesn't collide with A, thanks to separate cache
                  <__main__.B object at 0x000001A73951B080>
                  
                  • 警告:您不应该初始化 Parent,它与其他类发生冲突 - 除非您在每个孩子中定义单独的缓存,否则这不是我们想要的。
                  • 警告: 以 Parent 作为祖父母的班级似乎表现得很奇怪。 [未验证]

                  Try it online!

                  【讨论】:

                    【解决方案17】:

                    但是,我有点困惑为什么 __init__ 总是在 __new__ 之后被调用。

                    除了它只是这样做之外没有太多原因。 __new__ 不负责初始化类,其他方法负责(__call__,可能——我不确定)。

                    我没想到会这样。谁能告诉我为什么会发生这种情况以及我如何实现此功能? (除了将实现放到__new__ 中,感觉很hacky)。

                    如果 __init__ 已经被初始化,你可以让它什么也不做,或者你可以用一个新的 __call__ 编写一个新的元类,它只在新实例上调用 __init__,否则只返回 __new__(...)

                    【讨论】:

                      【解决方案18】:

                      原因很简单,new是用来创建实例的,而init是用来初始化实例的。在初始化之前,应该先创建实例。这就是为什么应该在 init 之前调用 new

                      【讨论】:

                        猜你喜欢
                        • 1970-01-01
                        • 2014-03-01
                        • 1970-01-01
                        • 2011-03-09
                        • 1970-01-01
                        • 2016-05-28
                        相关资源
                        最近更新 更多