【问题标题】:Why do Python immutable types (like int, str, or tuple) need to use `__new__()` instead of just `__init__()`?为什么 Python 不可变类型(如 int、str 或 tuple)需要使用 `__new__()` 而不仅仅是 `__init__()`?
【发布时间】:2018-01-25 19:16:22
【问题描述】:

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

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

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

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

【问题讨论】:

    标签: python python-3.x constructor subclass


    【解决方案1】:

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

    This question here 特别相关,但问题与这个问题不一样,虽然答案非常有启发性(尽管 cmets 变成了关于 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创建之后im。您真正想要的是在创建不可变对象之前设置属性ab。一个明显的方法是首先创建一个可变类型(你允许__init__()中设置它的属性),然后使不可变 键入它的一个子类,并确保你实现了不可变子类的__new__() 方法,以先构造一个可变版本,然后使其不可变,如下所示。 p>

    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 hack,它可以阻止 PyCharm 抱怨我没有给父母的 __init__() 打电话,这显然是我们在这里的意图。最后只是为了检查im 是否真的是不可变的,验证im.a = 42 会给你AttributeError: LOL nope srsly.

    【讨论】:

    • 请注意,它并不是真正不可变的,它只是“难以”改变。您始终可以使用 object.__setattr__: object.__setattr__(im, 'a', 10) 或在您的情况下使用 Mutable.__setattr__(im, 'a', 10) 覆盖“不可变纯 Python 类”的属性。
    猜你喜欢
    • 2014-03-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多