小编典典

如何在 Python 中创建不可变对象?

all

虽然我从来不需要这个,但让我感到震惊的是,在 Python
中创建一个不可变对象可能有点棘手。你不能只是覆盖__setattr__,因为那样你甚至不能在__init__.
对元组进行子类化是一个有效的技巧:

class Immutable(tuple):

    def __new__(cls, a, b):
        return tuple.__new__(cls, (a, b))

    @property
    def a(self):
        return self[0]

    @property
    def b(self):
        return self[1]

    def __str__(self):
        return "<Immutable {0}, {1}>".format(self.a, self.b)

    def __setattr__(self, *ignored):
        raise NotImplementedError

    def __delattr__(self, *ignored):
        raise NotImplementedError

但是您可以通过and访问aandb变量,这很烦人。self[0]``self[1]

这在纯 Python 中可能吗?如果没有,我将如何使用 C 扩展来做到这一点?

(仅适用于 Python 3 的答案是可以接受的)。

更新:

从 Python 3.7
开始,要走的路是使用@dataclass装饰器,请参阅新接受的答案。


阅读 61

收藏
2022-06-13

共1个答案

小编典典

使用冻结的数据类

对于 Python
3.7+,您可以使用带有选项的 数据类
,这是一种非常 Python
且可维护的方式来做您想做的事情。frozen=True

它看起来像这样:

from dataclasses import dataclass

@dataclass(frozen=True)
class Immutable:
    a: Any
    b: Any

由于数据类的字段 需要类型提示
,因此我使用了模块中
Anytyping

不使用 Namedtuple 的原因

在 Python 3.7 之前,经常看到命名元组被用作不可变对象。它在很多方面都可能很棘手,其中之一是__eq__namedtuples
之间的方法不考虑对象的类。例如:

from collections import namedtuple

ImmutableTuple = namedtuple("ImmutableTuple", ["a", "b"])
ImmutableTuple2 = namedtuple("ImmutableTuple2", ["a", "c"])

obj1 = ImmutableTuple(a=1, b=2)
obj2 = ImmutableTuple2(a=1, c=2)

obj1 == obj2  # will be True

如您所见,即使obj1and的类型obj2不同,即使它们的字段名称不同,obj1 == obj2仍然给出True.
那是因为使用的__eq__方法是元组的方法,它只比较给定位置的字段的值。这可能是一个巨大的错误来源,特别是如果您对这些类进行子类化。

2022-06-13