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) 对其进行修改。

比较tuplelist的行为:

>>> 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__的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Python C-API 中动态创建派生类型

如何在 C#/Python 中从 DLL 调用函数

python 斗图图片爬虫

python魔法方法__reduce__()的妙用

python_day16_pythom-mysql-API

Python + Selenium WebDriver Api 知识回顾