我只是想简化我的一个类,并引入了一些与 flyweight design pattern 风格相同的功能。
但是,我有点困惑为什么总是在 __new__
之后调用 __init__
。我没想到会这样。谁能告诉我为什么会发生这种情况以及我如何才能实现此功能? (除了把实现放到 __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
为什么?
当您需要控制新实例的创建时,请使用 __new__。
当您需要控制新实例的初始化时,请使用 __init__。 __new__ 是实例创建的第一步。它首先被调用,并负责返回你的类的一个新实例。
相反, __init__ 不返回任何东西。它只负责在创建实例后对其进行初始化。一般来说,你不需要重写 __new__ ,除非你继承了一个不可变类型,比如 str、int、unicode 或 tuple。
从 2008 年 4 月的帖子开始:mail.python.org 上的 When to use __new__
vs. __init__
?。
您应该考虑到您尝试执行的操作通常是使用 Factory 完成的,这是最好的方法。使用 __new__
不是一个好的清洁解决方案,因此请考虑使用工厂。这是一个很好的例子:ActiveState Fᴀᴄᴛᴏʀʏ ᴘᴀᴛᴛᴇʀɴ Recipe。
__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:
...
MyClass
绑定到返回原始类实例的函数。但是现在根本无法引用原始类,并且 MyClass
作为一个函数会破坏 isinstance
/issubclass
检查,直接以 MyClass.something
访问类属性/方法,将类命名为 super
调用,等等,等等。这是否是一个问题取决于您将其应用到的类(以及程序的其余部分)。
在大多数著名的 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__
完全,并且可以将 any< /em> 只需添加 __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
然而,这可能是比这种情况真正需要的更深层次的魔法!
引用 documentation:
典型的实现通过调用超类的 __new__() 方法使用“super(currentclass, cls).__new__(cls[, ...])”和适当的参数来创建类的新实例,然后根据需要修改新创建的实例在返回之前。 ... 如果 __new__() 没有返回 cls 的实例,则不会调用新实例的 __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__
。
__new__
的返回对象的类时,该方法会抛出错误,而不是让失败的检查静默通过,因为我不明白你为什么想要返回除对象之外的任何东西类的。
我意识到这个问题已经很老了,但我有一个类似的问题。以下做了我想要的:
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__
将在返回的对象上运行。即您不能使用 __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__
的另一种方法是使用类方法:
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
的任何真实实例。
我认为这个问题的简单答案是,如果 __new__
返回一个与类相同类型的值,则 __init__
函数会执行,否则不会。在这种情况下,您的代码返回与 cls
相同的类的 A._dict('key')
,因此将执行 __init__
。
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)。
@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__
步骤,则需要标记对象。
应该将 __init__
视为传统 OO 语言中的简单构造函数。例如,如果您熟悉 Java 或 C++,则构造函数会隐式传递一个指向其自身实例的指针。对于 Java,它是 this
变量。如果要检查为 Java 生成的字节码,会注意到两个调用。第一次调用是“new”方法,然后下一次调用是 init 方法(这是对用户定义的构造函数的实际调用)。这两个步骤过程允许在调用类的构造方法之前创建实际实例,该构造方法只是该实例的另一种方法。
现在,对于 Python,__new__
是用户可以访问的附加工具。 Java 不提供这种灵活性,因为它是类型化的。如果一种语言提供了这种功能,那么 __new__
的实现者可以在返回实例之前在该方法中做很多事情,包括在某些情况下创建一个不相关对象的全新实例。而且,这种方法也非常适用于 Python 的不可变类型。
当继承不可变的内置类型(如数字和字符串)时,有时在其他情况下,静态方法 __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:
...
__new__
调用一个混蛋形式的 init
听起来真的很老套。这就是元类的用途。
__new__ 应该返回一个新的、空白的类实例。然后调用 __init__ 来初始化该实例。您没有在 __new__ 的“新”案例中调用 __init__,所以它正在为您调用。调用 __new__
的代码不会跟踪 __init__ 是否已在特定实例上被调用,也不应该跟踪,因为您在这里做了一些非常不寻常的事情。
您可以在 __init__ 函数中为对象添加一个属性,以表明它已被初始化。检查该属性是否存在作为 __init__ 中的第一件事,如果已经存在,则不要继续进行。
再深入一点!
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
只返回绑定到您的变量的对象。
但是,我对为什么总是在 __new__ 之后调用 __init__ 感到有些困惑。
我认为 C++ 类比在这里很有用:
__new__ 只是为对象分配内存。对象的实例变量需要内存来保存它,这就是步骤 __new__ 会做的事情。 __init__ 将对象的内部变量初始化为特定值(可以是默认值)。
__init__
在 __new__
之后调用,因此当您在子类中覆盖它时,您添加的代码仍会被调用。
如果您尝试对已经具有 __new__
的类进行子类化,则不知道这一点的人可能会首先调整 __init__
并将调用转发到子类 __init__
。这种在 __new__
之后调用 __init__
的约定有助于按预期工作。
__init__
仍然需要允许超类 __new__
所需的任何参数,但不这样做通常会产生明显的运行时错误。 __new__
应该明确允许 *args
和 '**kw',以明确扩展是可以的。
由于原始海报描述的行为,在同一继承级别的同一类中同时具有 __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,它会与其他类发生冲突 - 除非您在每个孩子中定义单独的缓存,否则这不是我们想要的。
警告:似乎以父母为祖父母的班级表现得很奇怪。 [未验证]
但是,我对为什么总是在 __new__ 之后调用 __init__ 感到有些困惑。
除了它只是这样做之外没有太多的原因。 __new__
不负责初始化类,其他方法负责(__call__
,可能——我不确定)。
我没想到会这样。谁能告诉我为什么会发生这种情况以及我如何实现此功能? (除了将实现放入感觉很hacky的 __new__ 中)。
如果 __init__
已经初始化,您可以让它什么都不做,或者您可以编写一个新的元类,其中包含一个新的 __call__
,它只在新实例上调用 __init__
,否则只返回 __new__(...)
。
原因很简单,new 用于创建实例,而 init 用于初始化实例。在初始化之前,应该先创建实例。这就是为什么 new 应该在 init 之前调用。
__new__
,该类变得非常干净,因此感谢您的输入。__new__
的使用应严格限于所述情况。我发现它对于实现可扩展的泛型类工厂非常有用——请参阅问题中的 my answer不正确地使用__new__
在 Python 中生成类? 以获取这样做的示例。__new__
在这里是一个糟糕的解决方案。工厂模式在限制构造函数充当初始化程序的语言中是必需的(防止您返回现有对象);像大多数设计模式(尤其是早期专门为 Java 发明的大量设计模式,由于语言不灵活)一样,它是一种以一致的方式解决语言限制的方法。 Python 没有这个限制。对于这种情况,您使用__new__
,并使用@classmethod
替代构造函数从变体参数构造。