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

时间:2017-08-17 11:32:43

标签: python python-3.x constructor subclass

此问题与thisthisthisthis有关,但并非重复。这些链接在这里没有回答我的问题。 This though,几乎回答了我的问题,但没有,因为答案中的代码不能在Python 3.6中运行,而且在任何情况下,问题都不是我在这里问的具体问题。 (见下面我自己的答案。

the Python documentation page,我找到以下文字。

  

__new__()主要用于允许不可变类型的子类(如int,str或tuple)自定义实例创建。也是   通常在自定义元类中重写以自定义类   创建

为什么?为什么我们不能覆盖__init__()而不必覆盖__new__()?显然,frozenset例如甚至没有实现__init__(); 为什么?我从here了解到,在极少数情况下,__new__()__init__()需要做不同的事情,但据我所知,这只是在酸洗和去除油漆时。什么是不可变类型需要使用__new__()而不是__init__()

1 个答案:

答案 0 :(得分:6)

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

This question here特别相关,但问题与这个问题不一样,虽然答案非常有启发性(尽管这些评论变成了关于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.")

让我们尝试使用它。

im = Immutable(2, 3)
print(im.a, im.b, sep=", ")

输出:

AttributeError: LOL nope.

“但是什么!?”,我听到你问道,“创建之后我没有设置任何属性!”啊,但是的,你做了,在__init__()。由于在创建对象后__init__()被称为,因此行self.a = aself.b = b正在设置属性ab < em>在创建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.")

现在让我们试试吧。

im = ActuallyImmutable(2, 3)
print(im.a, im.b, sep=", ")

输出:

AttributeError: LOL nope srsly.

“WTF !?这次是什么时候召唤__setattr__()?”问题是,ActuallyImmutableMutable的子类,如果没有明确实现其__init__(),则父类__init__()会在后自动调用创建ActuallyImmutable对象,因此父母的__init__()总共被调用两次,一次在创建im之前(可以),一次之后(这是不行)。所以让我们再试一次,这次重写AcutallyImmutable.__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.")

现在应该可以了。

im = ActuallyImmutable(2, 3)
print(im.a, im.b, sep=", ")

输出:

2, 3

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