为什么总是在__new __()之后调用__init __()?


568

我只是想简化我的一个类,并以与flyweight设计模式相同的样式介绍了一些功能。

但是,对于为什么__init__总是被称为after ,我有点困惑__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设计模式..很好的链接在几乎所有流行语言中都有示例。
EmmaYang

Answers:


598

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

使用 __init__时,你需要一个新的实例的控件初始化。

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

相反, __init__什么也不返回;创建实例后,它仅负责初始化实例。

通常,__new__除非您要继承不可变类型(例如str,int,unicode或tuple),否则无需重写。

从2008年4月发布:何时使用__new__vs __init__在mail.python.org上。

您应该考虑要尝试做的事通常是通过Factory完成的,这是最好的方法。使用__new__不是一个好的清洁解决方案,因此请考虑使用工厂。在这里,您有一个很好的工厂示例


1
最终__new__在Factory类中使用,该类已经很干净了,因此感谢您的输入。

11
抱歉,我不同意将的使用__new__严格限制在所述情况下。我发现这对于实现可扩展的泛型类工厂非常有用-请参阅使用__new__Python生成类的使用不当的问题的回答举一个例子。
martineau'2

170

__new__是静态类方法,__init__而是实例方法。 __new__必须先创建实例,因此__init__可以对其进行初始化。注意,__init__将其self作为参数。在创建实例之前,没有任何实例self

现在,我知道您正在尝试在Python中实现单例模式。有几种方法可以做到这一点。

另外,从Python 2.6开始,您可以使用类装饰器

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

@singleton
class MyClass:
  ...

8
@Tyler Long,我不太了解“ @singleton”如何工作?因为装饰器返回一个函数,但MyClass是一个类。
奥尔科特

11
为什么要字典?由于cls将始终相同,并且例如对于每个单例您都会获得一个新的字典,因此您将创建其中仅包含一项的字典。
Winston Ewert

7
@Alcott:不需要意见- 文档与您同意。
伊桑·弗曼

2
@奥尔科特 是的,装饰器返回一个函数。但是类和函数都是可调用的。我认为instance = {}应该是全局变量。
泰勒·朗

4
@TylerLong Alcott说的很对。这导致名称MyClass绑定到一个函数,该函数返回原始类的实例。但是现在根本没有办法引用原始类,而MyClass作为一个函数会中断isinstance/ issubclass检查,直接以类的形式访问类属性/方法MyClass.something,在super调用中命名该类等等,等等。这是否是一个问题取决于您正在将其应用到的类(以及程序的其余部分)。
2012年

148

在大多数众所周知的OO语言中,类似的表达式SomeClass(arg1, arg2)将分配一个新实例,初始化该实例的属性,然后返回它。

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

Python的__new__功能无非就是“分配新实例”部分的类似的按类自定义。当然,这允许您执行不同寻常的操作,例如返回现有实例而不是分配新实例。因此,在Python中,我们不应该真的认为这部分必然涉及分配。我们所需要的只是__new__从某个地方提出一个合适的实例。

但这仍然只是工作的一半,Python系统无法知道有时您希望__init__稍后再执行另一部分工作(),而有时又不想。如果您想要这种行为,则必须明确地说出。

通常,您可以重构,因此只需要__new__,或者不需要__new__,或者这样__init__就可以在已初始化的对象上表现不同。但是,如果你真的想,Python不竟让你重新定义“工作”,所以SomeClass(arg1, arg2)不一定需要__new__后面__init__。为此,您需要创建一个元类,并定义其__call__方法。

元类只是类的类。而类的__call__方法控制了当您调用类的实例时会发生什么。因此,metaclass__call__方法控制了您调用类时发生的事情。即,它允许您从头到尾重新定义实例创建机制。在此级别上,您可以最优雅地实现完全非标准的实例创建过程,例如单例模式。事实上,用了不到10行代码就可以实现一个Singleton元类是那么甚至不要求你与futz __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

但是,这可能比这种情况下真正应具有的魔力还要深!


25

引用文档

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

...

如果__new __()不返回cls的实例,则将不会调用新实例的__init __()方法。

__new __()主要用于允许不可变类型的子类(例如int,str或tuple)自定义实例创建。


18
If __new__() does not return an instance of cls, then the new instance's __init__() method will not be invoked.这很重要。如果返回其他实例,__init__则永远不会调用original 。
杰弗里·何塞

@tgray我知道您的回答来自文档,但是如果您知道不返回cls实例的任何用例,我很好奇。似乎在__new__检查返回的对象的类时,该方法将引发错误,而不是允许失败的检查静默地通过,因为我不明白为什么除了类的对象之外,您都不想要返回任何内容。
soporific312

@ soporific312我还没有看到任何用例。 尽管他们还没有看到任何利用该“功能”的代码,但该答案讨论了设计的一些理由。
tgray

12

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

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


7
注意:__new__总是返回适当的对象,因此__init__总是被调用-即使实例已经存在。
2014年

10

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


10

__new__返回相同类的实例时,__init__随后在返回的对象上运行。即您不能使用它__new__来阻止__init__运行。即使您从中返回先前创建的对象__new__,也将__init__一次又一次地将其初始化为double(三重,等等)。

这是Singleton模式的通用方法,它在上面扩展了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

全文在这里

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

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


6

参考此文档

当对不可变的内置类型(例如数字和字符串)进行子类化时,有时在其他情况下,可以使用new静态方法。new是实例构造的第一步,在init之前调用。

方法被称为与类作为第一个参数; 它的责任是返回该类的新实例。

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

在某些情况下,无需调用init即可创建新实例(例如,从泡菜中加载实例时)。如果不调用new,就无法创建新实例(尽管在某些情况下,可以通过调用基类的new来摆脱困境)。

关于您希望实现的目标,在有关Singleton模式的相同文档信息中也有

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:
...

1
init__new__声音中调用混蛋形式确实很不客气。这就是元类的用途。
疯狂物理学家

6
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属性会自动变成可触及AA._dict所以要小心不要顺带覆盖它。


您缺少设置的__init__方法cls._dict = {}。您可能不希望该元类型的所有类都共享一个字典(但建议为+1)。
疯狂物理学家

5

__new__应该返回一个类的新的空白实例。然后调用__init__初始化该实例。您不是在__new__的“ NEW”情况下调用__init__,因此正在为您调用它。所调用的代码__new__无法跟踪是否已在特定实例上调用__init__,也不会跟踪它,因为您在这里做的事情很不寻常。

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


5

对@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类参数中的no-arg调用和metaclass而不是作为属性)。

无论哪种方式,最重要的一点是,你的类初始化函数(__call__方法)将不会执行任何__new__或者__init__如果键被找到。这比使用干净得多__new__,如果要跳过默认__init__步骤,使用标记您需要标记该对象。


4

深入了解这一点!

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

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

那时在内存中创建对象,然后__init__调用该方法。如果__init__未在您的课程中实现,则将object_init调用gets并且不执行任何操作:)

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


4

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

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


4

但是,对于为什么__init__总是被称为after ,我有点困惑__new__

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

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

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


2

__init__经过被称为__new__所以,当你在子类中重写它,你添加的代码仍然会被调用。

如果您尝试对已经具有a的类进行子类化,则__new__对此一无所知的人可能会先改编__init__并将调用向下转发到子类__init__。这种呼叫__init__后的约定__new__有助于按预期工作。

__init__仍然需要允许超任何参数__new__需要的,但不这样做通常会建立一个清晰的运行时错误。并且__new__可能应该明确允许*args和'** kw',以明确表示扩展名是可以的。

这是普遍不好的形式既有__new____init__在继承同级别在同一个班级,因为原来的海报中描述的行为。


1

但是,对于为什么__init__总是被称为after ,我有点困惑__new__

除了这样做是没有其他原因的。__new__没有初始化类的责任,其他方法有责任(__call__,可能-我不确定)。

我没想到这一点。谁能告诉我为什么会这样,否则我如何实现此功能?(除了将实现放入__new__hack之外)。

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


1

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


1

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

主文件

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,它与其他类发生冲突-除非您在每个子代中都定义了单独的缓存,否则这不是我们想要的。
  • 警告:父辈的祖父母类看起来很奇怪。[未验证]

在线尝试!

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.