为什么 Python 不可变类型(如 int、str 或 tuple)需要使用 `__new__()` 而不仅仅是 `__init__()`?

2023-12-13

这个问题与以下内容相关,但不重复:this, this, this, and this。这些链接并不能回答我的问题。这虽然,几乎回答了我的问题,但没有回答,因为答案中的代码不能在 Python 3.6 中运行,并且无论如何,这里的问题并不是专门关于我在这里问的内容。 (请参阅下面我自己的回答。

From Python 文档页面,我找到以下文字。

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

But why?为什么我们不能直接覆盖__init__()而不必覆盖__new__()?显然,frozenset,例如,甚至没有实现__init__(); 这是为什么?我的理解是从here在一些罕见的情况下,__new__() and __init__()需要做不同的事情,但据我所知,这只是在酸洗和取消酸洗期间。这是关于什么的特别是不可变类型这需要使用__new__()代替__init__()?


我是问题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.")

让我们尝试使用它。

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

Output:

AttributeError: LOL nope.

“但是什么!?”,我听到你问,“我在创建它后没有设置它的任何属性!”啊但是是的,你做到了, 在里面__init__(). Since __init__()叫做after对象被创建,线条self.a = a and self.b = b正在设置属性a and b after的创造im。你真正想要的是设置属性a and b before创建不可变对象。一个明显的方法是创建一个mutable首先输入(您的属性被允许设置在__init__()),然后使不可变的 type a subclass并确保您实施__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=", ")

Output:

AttributeError: LOL nope srsly.

“WTF!?什么时候的?__setattr__()这次接到电话了吗?”问题是,ActuallyImmutable是一个子类Mutable,并且没有明确实施其__init__(), 父类的__init__()被自动调用after的创建ActuallyImmutable对象,所以父母的总数__init__()被调用两次,一次在创建之前im(没关系)并且一次after(这是not OK)。所以让我们再试一次,这次覆盖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=", ")

Output:

2, 3

很好,成功了。哦,不用担心# noinspection PyMissingConstructor,这只是 PyCharm 的 hack,目的是阻止 PyCharm 抱怨我没有给父母打电话__init__(),这显然是我们的意图。最后只是为了检查一下im确实是不可变的,验证一下im.a = 42会给你AttributeError: LOL nope srsly..

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

为什么 Python 不可变类型(如 int、str 或 tuple)需要使用 `__new__()` 而不仅仅是 `__init__()`? 的相关文章

随机推荐