为什么__init__()总是在__new__()之后调用?

我只是想简化我的一个类,并引入了一些与Flyweight设计模式相同风格的功能。

然而,我有点困惑,为什么__init__总是在__new__之后被调用。我没想到会这样。有人能告诉我为什么会发生这种情况,我如何才能实现这个功能吗?(除了把实现放在__new__中,这感觉很俗气。)

这里有一个例子:

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

为什么?

343217 次浏览
__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:
...
当你需要控制时使用__new__

< p >使用 __init__当你需要控制一个新实例的初始化时。

__new__是创建实例的第一步。它叫做first,并且是 负责退货 你的类的实例

相比之下,< p > __init__不返回任何东西;它只负责初始化

一般来说,你不需要这样做 重写__new__,除非你 继承一个不可变类型的子类

. Str, int, unicode或元组

从2008年4月的帖子:什么时候使用#EYZ0和__init__?在mail.python.org。

你应该考虑到你正在尝试做的事情通常是用工厂完成的,这是最好的方法。使用__new__不是一个好的干净的解决方案,所以请考虑使用工厂。这里有一个很好的例子:ActiveState Fᴀᴄᴛᴏʀʏᴘᴀᴛᴛᴇʀɴ配方

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

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

我没想到会这样。有人能告诉我为什么会发生这种情况,以及我如何实现这个功能吗?(除了把实现放在__new__中,这感觉很俗气)。

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

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

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

引用文档:

典型实现通过调用创建类的新实例 父类的__new__()方法使用"super(currentclass, cls)。__new__ (cls[…])用适当的理由,然后

.返回前根据需要修改新创建的实例

...

如果__new__()没有返回cls的实例,则new . __new__()没有返回cls的实例 实例的__init__()方法将不会被调用

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

在大多数知名的OO语言中,像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任何变成一个单例类!

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

然而,这可能比在这种情况下真正被保证的更深层的魔法!

__init____new__之后调用,这样当你在子类中重写它时,你添加的代码仍然会被调用。

如果您试图子类化一个已经有__new__的类,不知道这一点的人可能会从调整__init__开始,并将调用转发到子类__init__。在__new__之后调用__init__的约定有助于按预期工作。

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

__new____init__同时存在于同一个类的同一继承级别中通常是一种糟糕的形式,因为原来的海报所描述的行为。

我知道这个问题很老了,但我也有过类似的问题。 下面是我想要的:

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__函数,否则不会执行。在这种情况下,您的代码返回A._dict('key'),它与cls是同一个类,因此将执行__init__

# EYZ0:

当子类化不可变内置类型(如数字和字符串)时, 偶尔在其他情况下,会出现静态方法__new__ 派上了用场。__new__是实例构造的第一步,被调用 在# EYZ0。< / p >
__new__方法是用类来调用的 第一个参数;它的职责是返回该函数的一个新实例 类。< / p >
__init__比较:__init__是通过实例调用的 作为它的第一个参数,它不返回任何东西;它的

有些情况 其中创建新实例时不调用__init__(例如 当实例从pickle加载时)。没有办法去创造 不调用__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:
...

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

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

__new__返回同一个类的实例时,__init__随后在返回的对象上运行。也就是说,你不能使用__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

完整的故事是在这里

另一种方法,实际上涉及到__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的任何实际实例。

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,所以注意不要顺便覆盖它。

再深入一点!

CPython中的泛型类的类型是type,它的基类是Object(除非你显式地定义了另一个基类,比如元类)。低级调用的序列可以在在这里中找到。第一个调用的方法是type_call,然后调用tp_newtp_init

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

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

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

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

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

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

主文件

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作为祖父母的类行为很奇怪。(未经证实的)

<一个href = " https://tio.run/ # # nZNPSyQxEMXP5lMU4yUtMrDsTfAwriLeZNmbSMikq@1omw5JNbPt4mcfk / QfezLiYQMDQ7rye69eKranujU / 9 / sSKxCyIV51RhUXDMKKe9po4h6b6hzObPidvYwf49IV1NJLIjeWrKx0aEjEU1o2@g3L1aI@LofUOTNvYePxsCCS1sccuIQ / rsOD0ug1M8fYp0gyzxhTjfQe7hNy0DqFm1dLPZRakW6NdH3aHlWVVDUGvX / vbM5BCIM7Ibhq / DmYoCXdk0@Su / gvNDk7O4VfNaoXH@Npt8@oCHaaaqBae7i7Bm6KGBtsEQ0oh5KwXEYafUPQWS / tLPiDxl2gyuBgapW@SPmI8mAeWRZ9hk3Wowud@PDalrrSIf5tPzQQM4@ZHZyLrluahyGFNM3CRMgHYdDblGXgIsRzetsRHtUsepjNfDELWWl@h5mqtU2fZzYRRBo7IcLp9CKWe0We1y1S8q@NJ2nUoadw@wHiO4uOF@vFCB1h7jtfx8ipheScfddWuMWADfCc8vu7WUj141vY8OE1TGM7TPjQ4vigzOK6rNOG@Gozx7CK32fa1X/RrnLartYNpou9YCf4FxXXxnbEi2K/3/AfxQc" rel="nofollow noreferrer" title="Python 3 -试一下在线">试一下在线!< / >

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

我认为c++的类比在这里是有用的:

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

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

当实例化一个类时,首先调用< >强__new__() < / >强来创建类的实例,然后调用< >强__init__() < / >强来初始化实例

# EYZ0:

调用它来创建类cls. ...的新实例

如果在对象构造期间调用__new__(),它返回一个 实例,则新实例的__init__()方法将为 如__init__(self[,…])调用,…

# EYZ0:

在实例创建后调用(通过__new__()),…

因为__new__()和__init__()一起构建对象 (__new__()来创建它,__init__()来定制它),…< / p >

例如,当实例化# EYZ0类时,首先调用< >强__new__() < / >强来创建Teacher类的实例,然后调用< >强__init__() < / >强来初始化实例,如下所示:

class Teacher:
def __init__(self, name):
self.name = name
        

class Student:
def __init__(self, name):
self.name = name


obj = Teacher("John") # Instantiation


print(obj.name)

输出如下:

<class '__main__.Teacher'>
John

并且,使用Teacher类的实例__new__(),我们可以创建Student类的实例,如下所示:

# ...


obj = Teacher("John")
print(type(obj))
print(obj.name)


obj = obj.__new__(Student) # Creates the instance of "Student" class
print(type(obj))

现在,Student类的实例被创建,如下所示:

<class '__main__.Teacher'>
<__main__.Teacher object at 0x7f4e3950bf10>
<class '__main__.Student'> # Here

接下来,如果我们尝试从# EYZ1类的**实例中获取name变量的值,如下所示:

obj = Teacher("John")
print(type(obj))
print(obj.name)


obj = obj.__new__(Student)
print(type(obj))
print(obj.name) # Tries to get the value of "name" variable

出现以下错误是因为Student类的实例还没有被__init__()初始化:

AttributeError: 'Student'对象没有属性'name'

因此,我们初始化Student类的实例,如下所示:

obj = Teacher("John")
print(type(obj))
print(obj.name)


obj = obj.__new__(Student)
print(type(obj))
obj.__init__("Tom") # Initializes the instance of "Student" class
print(obj.name)

然后,我们可以从Student类的实例得到name变量的值,如下所示:

<class '__main__.Teacher'>
John
<class '__main__.Student'>
Tom # Here