Python(和 Python C API):__new__ 与 __init__
Posted
技术标签:
【中文标题】Python(和 Python C API):__new__ 与 __init__【英文标题】:Python (and Python C API): __new__ versus __init__ 【发布时间】:2011-06-19 01:12:46 【问题描述】:我要问的问题似乎与Python's use of __new__ and __init__? 重复,但无论如何,我仍然不清楚__new__
和__init__
之间的实际区别是什么。
在你急于告诉我__new__
用于创建对象而__init__
用于初始化对象之前,让我明确一点:我明白了。 事实上,这种区别是很自然的对我来说,因为我有 C++ 经验,我们有 placement new,它同样将对象分配与初始化分开。
Python C API tutorial 是这样解释的:
新成员负责 创建(相对于初始化) 类型的对象。它暴露在 Python 作为
__new__()
方法。 ... 实施新方法的一个原因是确保初始值 实例变量。
所以,是的 - 我了解 __new__
做了什么,但尽管如此,我仍然不明白为什么它在 Python 中有用。给出的示例表明 __new__
如果您想“确保实例变量的初始值”可能会很有用。好吧,这不正是__init__
会做的吗?
在 C API 教程中,显示了一个示例,其中创建了一个新类型(称为“Noddy”),并定义了该类型的 __new__
函数。 Noddy 类型包含一个名为first
的字符串成员,并且这个字符串成员被初始化为一个空字符串,如下所示:
static PyObject * Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
.....
self->first = PyString_FromString("");
if (self->first == NULL)
Py_DECREF(self);
return NULL;
.....
请注意,如果没有在此处定义 __new__
方法,我们将不得不使用 PyType_GenericNew
,它只是将所有实例变量成员初始化为 NULL。所以__new__
方法的唯一好处是实例变量将以空字符串开始,而不是NULL。 但是为什么这很有用,因为如果我们关心确保我们的实例变量被初始化为某个默认值,我们可以在 __init__
方法中做到这一点?
【问题讨论】:
【参考方案1】:区别主要在于可变类型和不可变类型。
__new__
接受 type 作为第一个参数,并且(通常)返回该类型的新实例。因此它适用于可变类型和不可变类型。
__init__
接受 instance 作为第一个参数并修改该实例的属性。这对于不可变类型是不合适的,因为它允许在创建后通过调用 obj.__init__(*args)
对其进行修改。
比较tuple
和list
的行为:
>>> x = (1, 2)
>>> x
(1, 2)
>>> x.__init__([3, 4])
>>> x # tuple.__init__ does nothing
(1, 2)
>>> y = [1, 2]
>>> y
[1, 2]
>>> y.__init__([3, 4])
>>> y # list.__init__ reinitialises the object
[3, 4]
至于为什么它们是分开的(除了简单的历史原因):__new__
方法需要一堆样板才能正确(初始对象创建,然后记住在最后返回对象)。相比之下,__init__
方法非常简单,因为您只需设置需要设置的任何属性。
除了__init__
方法更容易编写,以及上面提到的可变与不可变区别之外,还可以通过在__new__
。不过,这通常是一种可疑的做法——根据需要调用父类__init__
方法通常更清楚。
【讨论】:
您在__new__
中称为“样板”的代码不是样板,因为样板永远不会改变。有时您需要用不同的代码替换该特定代码。
创建或以其他方式获取实例(通常使用super
调用)并返回实例是任何__new__
实现的必要部分,也是我所指的“样板”。相比之下,pass
是 __init__
的有效实现 - 没有任何必需的行为。【参考方案2】:
__new__
可能还有其他用途,但有一个非常明显的用途:如果不使用 __new__
,就不能子类化不可变类型。例如,假设你想创建一个元组的子类,它只能包含 0 到 size
之间的整数值。
class ModularTuple(tuple):
def __new__(cls, tup, size=100):
tup = (int(x) % size for x in tup)
return super(ModularTuple, cls).__new__(cls, tup)
您根本无法使用 __init__
执行此操作 - 如果您尝试在 __init__
中修改 self
,解释器会抱怨您正在尝试修改不可变对象。
【讨论】:
我不明白为什么要使用super?我的意思是为什么要 new 返回超类的实例?此外,正如您所说,我们为什么要明确地将 cls 传递给 new? super(ModularTuple, cls) 不返回绑定方法? @Alcott,我认为您误解了__new__
的行为。我们将cls
显式传递给__new__
,因为您可以阅读here __new__
总是 需要一个类型作为其第一个参数。然后它返回该类型的实例。所以我们没有返回超类的实例——我们返回的是cls
的实例。在这种情况下,就像我们说tuple.__new__(ModularTuple, tup)
一样。
@senderle。我们可以说super(ModularTuple, cls).__new__(cls, tup)
和super().__new__(cls, tup)
相同
@sakeesh 啊,这是一个非常古老的答案,并没有考虑到 Python 3 的处理方式。在 Python 3 中,我认为您是对的,但我必须查看详细信息才能确定。【参考方案3】:
__new__()
可以返回其绑定的类以外的类型的对象。 __init__()
仅初始化该类的现有实例。
>>> class C(object):
... def __new__(cls):
... return 5
...
>>> c = C()
>>> print type(c)
<type 'int'>
>>> print c
5
【讨论】:
这是迄今为止最简洁的解释。 不完全正确。我有__init__
方法,其中包含看起来像 self.__class__ = type(...)
的代码。这会导致对象与您认为正在创建的对象属于不同的类。我实际上无法像您那样将其更改为int
...我收到有关堆类型或其他内容的错误...但是我将其分配给动态创建的类的示例有效。
我也对何时调用 __init__()
感到困惑。例如,在lonetwin's answer 中,Triangle.__init__()
或Square.__init__()
会根据__new__()
返回的类型自动被调用。根据您在回答中所说的内容(我已经在其他地方阅读过),似乎它们中的任何一个都不应该是因为 Shape.__new__()
不是返回 cls
的实例(也不是它的子类之一)。
@martineau:lonetwin 的答案中的__init__()
方法在单个对象被实例化时被调用(即当他们的 __new__()
方法返回时),而不是在Shape.__new__()
时返回。
啊,对,Shape.__init__()
(如果有的话)不会被调用。现在一切都变得更有意义了……:¬)
【参考方案4】:
不是一个完整的答案,但也许可以说明差异。
__new__
在必须创建对象时总是会被调用。在某些情况下,__init__
不会被调用。一个例子是,当您从 pickle 文件中解开对象时,它们将被分配 (__new__
) 但不会被初始化 (__init__
)。
【讨论】:
如果我想分配内存并初始化数据,我会从 new 调用 init 吗?为什么在创建实例时 init 被调用时如果 new 不存在?__new__
方法的工作是创建(这意味着内存分配)类的一个实例并返回它。初始化是一个单独的步骤,它通常对用户可见。如果您遇到特定问题,请提出单独的问题。【参考方案5】:
只想添加一个关于定义__new__
与__init__
的意图(相对于行为)的词。
当我试图了解定义类工厂的最佳方法时,我遇到了这个问题(以及其他问题)。我意识到__new__
在概念上与__init__
不同的一种方式是__new__
的好处正是问题中所述:
所以 __new__ 方法的唯一好处是实例变量将以空字符串开始,而不是 NULL。但是为什么这很有用,因为如果我们关心确保我们的实例变量被初始化为某个默认值,我们可以在 __init__ 方法中做到这一点?
考虑到上述场景,当 instance 实际上是一个类本身时,我们关心实例变量的初始值。因此,如果我们在运行时动态创建一个类对象,并且我们需要定义/控制正在创建的该类的后续实例的特殊内容,我们将在元类的 __new__
方法中定义这些条件/属性。
我对此感到困惑,直到我真正想到了这个概念的应用,而不仅仅是它的含义。这是一个希望能说明区别的示例:
a = Shape(sides=3, base=2, height=12)
b = Shape(sides=4, length=2)
print(a.area())
print(b.area())
# I want `a` and `b` to be an instances of either of 'Square' or 'Triangle'
# depending on number of sides and also the `.area()` method to do the right
# thing. How do I do that without creating a Shape class with all the
# methods having a bunch of `if`s ? Here is one possibility
class Shape:
def __new__(cls, sides, *args, **kwargs):
if sides == 3:
return Triangle(*args, **kwargs)
else:
return Square(*args, **kwargs)
class Triangle:
def __init__(self, base, height):
self.base = base
self.height = height
def area(self):
return (self.base * self.height) / 2
class Square:
def __init__(self, length):
self.length = length
def area(self):
return self.length*self.length
请注意,这只是一个示范性示例。有多种方法可以在不使用上述类工厂方法的情况下获得解决方案,即使我们确实选择以这种方式实现解决方案,为了简洁起见,也有一些注意事项(例如,显式声明元类) )
如果您正在创建一个常规类(也称为非元类),那么 __new__
并没有真正意义,除非它是特殊情况,例如 ncoghlan's answer 答案中的可变与不可变场景(这本质上是一个更定义通过__new__
创建的类/类型的初始值/属性的概念的具体示例,然后通过__init__
进行初始化)。
【讨论】:
这应该会导致父类'__new__()
被调用两次吗?
nvm,我没有意识到这个例子中的类不是从Shape
继承的。【参考方案6】:
__new__
的一个特殊用途是使类成为单例:
class SingletonClass(object):
def __new__(cls):
if not hasattr(cls, 'instance'):
cls.instance = super(SingletonClass, cls).__new__(cls)
return cls.instance
(来源:Singleton Pattern in Python - A Complete Guide - GeeksforGeeks)
【讨论】:
对上述内容只有一个警告:初始化应该在__new__
函数内完成。如果你有单独的__init__
函数,每次调用构造函数时都会执行,这可能不是你想要的。以上是关于Python(和 Python C API):__new__ 与 __init__的主要内容,如果未能解决你的问题,请参考以下文章