【问题标题】:What can `__init__` do that `__new__` cannot?`__init__` 能做什么而 `__new__` 不能?
【发布时间】:2016-05-28 21:21:56
【问题描述】:

在 Python 中,__new__ 用于初始化不可变类型,__init__ 通常用于初始化可变类型。如果从语言中删除__init__,什么不能再(轻松地)完成?

例如,

class A:

    def __init__(self, *, x, **kwargs):
        super().__init__(**kwargs)
        self.x = x


class B(A):

    def __init__(self, y=2, **kwargs):
        super().__init__(**kwargs)
        self.y = y

可以像这样使用__new__ 重写:

class A_N:

    def __new__(cls, *, x, **kwargs):
        obj = super().__new__(cls, **kwargs)
        obj.x = x
        return obj


class B_N(A_N):

    def __new__(cls, y=2, **kwargs):
        obj = super().__new__(cls, **kwargs)
        obj.y = y
        return obj

澄清问题范围:这不是关于如何使用__init____new__ 或它们之间有什么区别的问题。这是一个关于如果从语言中删除__init__ 会发生什么的问题。有什么会坏吗?有什么事情会变得更难或不可能吗?

【问题讨论】:

  • @matino were 是虚拟语气; was 是指示性的。
  • @MokonaModoki 这不是重复的。你看我的说明了吗?
  • @PM2Ring: "__new__ 是构造函数,__init__ 是初始化函数" - 为什么人们一直这么说?在什么奇怪的定义中,__init__ 不是构造函数?它接受一个已分配的、未初始化的对象并将其带入一个已初始化的、可用的状态。这就是构造函数在几乎所有具有构造函数的语言中所做的事情。我不知道构造函数负责分配的任何定义,例如__new__。 Python 在__new__ 中有它奇怪的分配器/辅助构造器的东西,因为事实证明第一个构造器设计不够灵活。
  • @user2357112:我猜人们一直在说,因为__new__ 返回一个实例,__init__ 初始化它,并且有一个清晰的方法来区分这两个进程很方便。没错,__new__ 可以进行初始化,它可能会返回一个现有实例而不是分配一个新实例(但当这种情况发生时,__init__ 不会自动调用)。但是__init__ 不应该创建一个实例:它期望传递一个它可以初始化的实例。如果您希望将这两种方法都称为构造函数,请随意,但这不是通常的 Python 约定。

标签: python initialization


【解决方案1】:

注意 __new__ 和 __init__ 的区别

在解释缺少的功能之前,让我们回到 __new____init__ 的定义:

__new__ 是创建实例的第一步。它首先被调用,并负责返回您的类的新实例

但是,__init__ 不会返回任何内容;它只负责 创建实例后对其进行初始化。

将 __init__ 替换为 __new__ 的后果

主要是你会失去灵活性。你会遇到很多语义上的麻烦,初始化和构造的松散分离(通过加入__new__ andinit,我们将把构造和初始化合并为一个步骤......)。 下面我们来看看sn-p:

class A(object):
    some_property = 'some_value'

    def __new__(cls, *args, **kwargs):
        obj = object.__new__(cls, *args, **kwargs)
        obj.some_property = cls.some_property
        return obj


class B(A):
    some_property = 2

    def __new__(cls, *args, **kwargs):
        obj = super(B, cls).__new__(cls)
        return obj

__init__ 操作移动到__new__ 的后果:

  1. A 之前初始化B:当您使用__new__ 方法而不是__init__ 时,创建B 的新实例的第一步是调用A.__new__ 作为副作用,您无法初始化@987654333 @ 在 A 被初始化之前(访问并将一些属性分配给新的 B 实例)。使用 __init__ 可为您提供如此灵活的方式。

  2. 对初始化顺序的松散控制:假设你有B_N继承自两个类(A_N1A_N2),现在你会错过初始化B_N(你要初始化实例的顺序是什么?这可能很重要......这很奇怪。)

  3. 属性和方法一团糟:在实例化 B 的新实例时,您将错过对 A.some_property 的访问(cls 将等于 B。但是直接访问 A.some_property 是可能的,但是我猜通过类名而不是使用classmethods访问类中的属性至少很奇怪。

  4. 如果不为此创建新实例或实现特殊逻辑,则无法重新初始化现有实例(感谢 @platinhom 的想法)

__init__ 可以做什么__new__ 不能?

__new__ 中没有不能在__init__ 中执行的操作,因为__init__ 执行的操作__new__ 可以执行的操作的子集 .

Python Docs, Pickling and unpickling normal class instances#object.getinitargs 的一个有趣时刻,关于 __init__ 何时有用:

当一个 pickle 类实例被 unpickled 时,它的 init() 方法通常不会被调用。

【讨论】:

  • 我知道他们在做什么,但这根本不能回答我的问题。
  • 1.如果cls=B,A 的__new__ 将创建B 的实例。 2. 顺序与__init__一样是继承顺序。 3.可以通过传入的created对象来访问属性。
  • 为什么无法访问A.some_property
  • @PadraicCunningham 你可以。这个答案几乎是完全错误的。
  • @NeilG,我不得不说我也是这么想的,我很惊讶这得到了多少支持。
【解决方案2】:

您可以在__init__ 中进行的所有操作也可以在__new__ 中进行。

那么,为什么要使用__init__
因为您不必将实例存储在变量中(示例代码中的obj),然后再返回它。你可以专注于你真正想做的事情——初始化可变对象。

【讨论】:

  • 我至少要等一天才能接受这个,因为我还不确定!
【解决方案3】:

When to use __new__ vs. __init__

__new__ 是创建实例的第一步。它首先被调用, 并负责返回你的类的一个新实例。在 相比之下,__init__ 不返回任何内容;它只负责 在创建实例后对其进行初始化。

类的类是typetype.__call__()的实现如下参考上面的描述:

def __call__(cls, *args, **kwargs):
    obj = cls.__new__(cls, *args, **kwargs)
    if isinstance(obj, cls):
        obj.__init__(*args, **kwargs)
    return obj

我们知道__init__() 只需执行__new__() 的一部分就可以在对象返回之前对该对象执行任何操作。

一般来说,您不需要重写 __new__,除非您将不可变类型(如 str、int、unicode 或 tuple)子类化。

所以从语言中删除__init__是不好的,总是使用__init__()比使用__new__()更好。

这是Low-level constructors and __new__()的一段历史。

【讨论】:

  • 好吧,那么__init__唯一的好处就是不返回构造对象的方便?
  • @NeilG:是的,不必将实例存储在变量中。
猜你喜欢
  • 2014-03-01
  • 1970-01-01
  • 2011-08-23
  • 2018-05-30
  • 2010-11-24
  • 1970-01-01
  • 1970-01-01
  • 2016-11-20
  • 1970-01-01
相关资源
最近更新 更多