繁体   English   中英

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

[英]Why do Python immutable types (like int, str, or tuple) need to use `__new__()` instead of just `__init__()`?

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

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

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

但是为什么 为什么我们不能只覆盖__init__()而不必覆盖__new__() 显然,例如, frozenset甚至没有实现__init__() 为什么 我从这里了解到,在极少数情况下, __new__()__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=", ")

输出:

AttributeError: LOL nope.

“但是什么!?”,我听到你问,“创建之后,我没有设置任何属性!” 啊, 是的 ,但是__init__() 做到了。 由于创建对象之后调用__init__() ,因此self.a = aself.b = bself.b = b创建im 之后设置属性ab 您真正想要的是创建不可变对象之前设置属性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__() ,在创建ActuallyImmutable对象会自动调用父类的__init__() ,因此,总计父类的__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.

这与可变性无关,至少不是直接的。 您可以通过覆盖不带__new__ __setattr__

class Immutable:
    def __init__(self, x):
        self.x = x
        self._frozen = True
    
    def __setattr__(self, key, value):
        if hasattr(self, '_frozen'):
            raise AttributeError(key)
        super().__setattr__(key, value)

原因intstrtuple覆盖__new__实际上是构造函数不一定创建新实例。 在 REPL 中观察:

>>> x = 5
>>> int(x) is x
True
>>> s = 'hello'
>>> str(s) is s
True
>>> t = (1, 2)
>>> tuple(t) is t
True

也就是说,覆盖__new__允许 class 返回新实例以外的内容。 如果它已经是 class 的实例,则它可能是参数本身,或者它可能是缓存的 object(如小int值的缓存)以避免分配“相同”值的多个副本:

>>> x = 23
>>> int('23') is x
True

仅使用__init__而没有__new__无法实现相同的效果:仅在创建新实例后才调用__init__方法。

这与不可变性有关的原因是,只有当它们是不可变的时重用这样的相等值才真正有意义。 例如,通常需要制作一个列表的副本,以便您拥有一个“新鲜的” object,您可以安全地对其进行变异,而无需在程序中的其他位置变异列表; 相比之下,没有人需要“新鲜”的intstr ,因为无论如何你都不能改变它们,更不用说不安全地改变它们了。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM