为什么Python不可变类型(例如int,str或tuple)需要使用__new __()而不是__init __()?

Ray*_*Ray 2 python constructor subclass python-3.x

这个问题与thisthisthisthis有关,但不是重复的。这些链接在这里没有回答我的问题。但是几乎可以回答我的问题,但不能回答,因为答案中的代码无法在Python 3.6中运行,而且无论如何该问题都不是我在这里问的具体问题。(请参阅下面的我自己的答案。

Python的文档页面,我发现下面的文字。

__new__()主要用于允许不可变类型的子类(例如int,str或tuple)自定义实例创建。为了自定义类的创建,它通常也被自定义元类覆盖。

但是为什么呢?为什么我们不能仅覆盖__init__()而不是必须覆盖__new__()?显然,frozenset例如,甚至没有实现__init__()为什么呢?我从了解这里,在某些罕见的情况下,__new__()并且__init__()需要做不同的事情,但据我可以看到,封装状态中只有。是什么样的不可变的类型,特别需要使用的__new__(),而不是__init__()

Ray*_*Ray 6

我是OP的问题,我将回答我自己的问题,因为我认为我在键入它的过程中发现了答案。在其他人确认它正确之前,我不会将其标记为正确。

这个问题在这里特别相关,但是这个问题与这个问题并不相同,尽管答案很有启发性(尽管注释变成了关于C和Python以及“ pythonic”的启蒙但深奥的论点),但应该设置在这里更清楚地说明这个问题。希望这对以后的读者有所帮助。此答案中的代码已在Python 3.6.1中进行了验证。

关于不可变对象的事情是,很明显,您不希望在创建对象后就对其进行设置。在Python中执行此操作的方法是将__setattr__()特殊方法覆盖为raise错误(AttributeError),以使人们无法执行my_immutable_object.x = 3。以下面的自定义不可变类为例。

class Immutable(object):
    def __init__(self, a, b):
        self.a = a
        self.b = b

    def __setattr__(self, key, value):
        raise AttributeError("LOL nope.")
Run Code Online (Sandbox Code Playgroud)

让我们尝试使用它。

im = Immutable(2, 3)
print(im.a, im.b, sep=", ")
Run Code Online (Sandbox Code Playgroud)

输出:

AttributeError: LOL nope.
Run Code Online (Sandbox Code Playgroud)

“但是什么!?”,我听到你问,“创建之后,我没有设置任何属性!” 啊但是是的,你在里面__init__()。由于__init__()被称为创建对象,这些线self.a = aself.b = b在设置的属性ab 创建的im。你真正想要的是设置属性ab 之前创建的不可变对象。一种明显的方法是先创建一个可变类型(允许在其中设置其属性__init__()),然后将不可变类型作为其子类,并确保实现__new__() 不可变子类的方法首先构造一个可变版本,然后使其变为不可变,如下所示。

class Mutable(object):
    def __init__(self, a, b):
        self.a = a
        self.b = b


class ActuallyImmutable(Mutable):
    def __new__(cls, a, b):
        thing = Mutable(a, b)
        thing.__class__ = cls
        return thing

    def __setattr__(self, key, value):
        raise AttributeError("LOL nope srsly.")
Run Code Online (Sandbox Code Playgroud)

现在,让我们尝试运行它。

im = ActuallyImmutable(2, 3)
print(im.a, im.b, sep=", ")
Run Code Online (Sandbox Code Playgroud)

输出:

AttributeError: LOL nope srsly.
Run Code Online (Sandbox Code Playgroud)

“ WTF !?__setattr__()这次什么时候接到电话?” 事情是,ActuallyImmutable是的子类Mutable,并且在没有显式实现它的情况下__init__(),在创建对象__init__()会自动调用父类的,因此总共调用了父类两次,一次是在创建之前(确定),一次是在创建之后(这不行)。因此,让我们再试一次,这次覆盖。ActuallyImmutable__init__()imAcutallyImmutable.__init__()

class Mutable(object):
    def __init__(self, a, b):
        print("Mutable.__init__() called.")
        self.a = a
        self.b = b


class ActuallyImmutable(Mutable):
    def __new__(cls, a, b):
        thing = Mutable(a, b)
        thing.__class__ = cls
        return thing

    # noinspection PyMissingConstructor
    def __init__(self, *args, **kwargs):
        # Do nothing, to prevent it from calling parent's __init__().
        pass

    def __setattr__(self, key, value):
        raise AttributeError("LOL nope srsly.")
Run Code Online (Sandbox Code Playgroud)

现在应该可以了。

im = ActuallyImmutable(2, 3)
print(im.a, im.b, sep=", ")
Run Code Online (Sandbox Code Playgroud)

输出:

2, 3
Run Code Online (Sandbox Code Playgroud)

很好,很有效。哦,不用担心# noinspection PyMissingConstructor,这只是一个PyCharm骇客,可以阻止PyCharm抱怨我没有打电话给父母的__init__(),这显然是我们打算在这里做的。最后,只是要检查它是否im确实是不变的,请验证是否im.a = 42可以给您AttributeError: LOL nope srsly.

  • 请注意,它并不是真正不变的,只是“很难”进行突变。您始终可以使用`object .__ setattr__`:`object .__ setattr __(im,'a',10)`来覆盖“不可变的纯Python类”的属性,或者在您的情况下甚至可以使用`Mutable .__ setattr __(im,'a' ,10)`。 (2认同)