【问题标题】:Python (and Python C API): __new__ versus __init__Python(和 Python C API):__new__ 与 __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__ 方法中做到这一点?

【问题讨论】:

    标签: python c python-c-api


    【解决方案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__函数,每次调用构造函数时都会执行,这可能不是你想要的。
    猜你喜欢
    • 2017-03-25
    • 2019-02-06
    • 2011-04-04
    • 2014-03-01
    • 1970-01-01
    • 2011-03-09
    • 2012-08-01
    • 2015-10-24
    • 2021-03-23
    相关资源
    最近更新 更多