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

时间:2009-03-23 17:13:01

标签: python design-patterns class-design

我只是想简化我的一个类,并以与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

为什么?

18 个答案:

答案 0 :(得分:520)

  

需要控制时,请使用 __ new __   创建一个新实例。使用   当您需要控制新实例的初始化时, __ init __

     

__ new __ 是实例创建的第一步。它首先被称为,而且是   负责归还新人   你的班级的实例。相反,    __ init __ 不返回任何内容;它只负责初始化   它被创建后的实例。

     

一般来说,你不应该这样做   除非你是,否则覆盖 __ new __   子类化不可变类型   str,int,unicode或tuple。

来自:http://mail.python.org/pipermail/tutor/2008-April/061426.html

您应该考虑到您尝试做的事情通常是使用Factory完成的,这是最好的方法。使用 __ new __ 不是一个很好的清洁解决方案,所以请考虑工厂的使用情况。这里有a good factory example

答案 1 :(得分:157)

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

答案 2 :(得分:133)

在大多数众所周知的面向对象语言中,像SomeClass(arg1, arg2)这样的表达式将分配一个新实例,初始化实例的属性,然后返回它。

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

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

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

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

元类只是类的类。类'__call__方法控制调用类实例时发生的情况。因此,元类'__call__方法控制调用类时会发生什么。即,它允许您从头到尾重新定义实例创建机制。这是您可以最优雅地实现完全非标准实例创建过程(如单例模式)的级别。实际上,只需少于10行代码,您就可以实现Singleton元类,然后甚至不需要使用__new__ 来完成,并且可以转为<通过简单地添加__metaclass__ = Singleton

将em>任何其他正常类放入单例中
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

然而,对于这种情况来说,这可能是更为神奇的理由!

答案 3 :(得分:21)

引用documentation

  

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

     

...

     

如果__new __()没有返回cls的实例,那么新的   实例的__init __()方法不会被调用。

     

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

答案 4 :(得分:10)

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

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

答案 5 :(得分:8)

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

答案 6 :(得分:8)

__new__返回同一个类的实例时,之后会在返回的对象上运行__init__。即您无法使用__new__来阻止__init__运行。即使您从__new__返回先前创建的对象,它也会被__init__一次又一次地初始化为double(triple等)。

这是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

全文是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 :(得分:5)

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

注意:由于副作用M._dict属性会自动从A作为A._dict进行访问,因此请注意不要随意覆盖它。

答案 8 :(得分:4)

对@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__步骤,则需要您标记对象。

答案 9 :(得分:4)

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

您可以在__init__函数中向对象添加属性,以指示它已初始化。检查是否存在该属性作为__init__中的第一件事,如果已经存在则不再继续。

答案 10 :(得分:3)

Referring to this doc

  

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

     

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

     

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

     

有些情况   在没有调用 init 的情况下创建新实例的地方(例如   当实例从pickle加载时)。没有办法创造   没有调用 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:
...

答案 11 :(得分:3)

深入挖掘它!

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

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

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

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

答案 12 :(得分:3)

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

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

答案 13 :(得分:1)

__init__之后调用__new__,这样当你在子类中覆盖它时,你的添加代码仍会被调用。

如果您尝试为已经拥有__new__的类创建子类,则可能会首先调整__init__并将调用转发到子类__init__,从而不知道这一点。在__init__之后调用__new__的惯例有助于按预期工作。

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

由于原始海报描述的行为,同一类中的__new____init__在同一级别的继承中通常是不好的形式。

答案 14 :(得分:1)

  

但是,对于为什么总是在__init__之后调用__new__,我有些困惑。

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

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

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

答案 15 :(得分:0)

  

但是,我对__init__之后始终调用__new__的原因感到有些困惑。

除此之外,没有其他原因只是以这种方式完成。 __new__没有责任初始化类,其他一些方法(__call__,可能 - 我不确定)。

  

我没想到这一点。谁能告诉我为什么会这样,以及我如何实现这个功能呢? (除了将实现放入感觉相当hacky的__new__之外)。

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

答案 16 :(得分:0)

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

答案 17 :(得分:0)

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

主文件

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

Try it online!