虽然我从来不需要这个,但让我感到震惊的是,在 Python 中创建一个不可变对象可能有点棘手。你不能只是覆盖__setattr__,因为那样你甚至不能在__init__. 对元组进行子类化是一个有效的技巧:
__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]
a
b
self[0]``self[1]
这在纯 Python 中可能吗?如果没有,我将如何使用 C 扩展来做到这一点?
(仅适用于 Python 3 的答案是可以接受的)。
更新:
从 Python 3.7 开始,要走的路是使用@dataclass装饰器,请参阅新接受的答案。
@dataclass
对于 Python 3.7+,您可以使用带有选项的 数据类 ,这是一种非常 Python 且可维护的方式来做您想做的事情。frozen=True
frozen=True
它看起来像这样:
from dataclasses import dataclass @dataclass(frozen=True) class Immutable: a: Any b: Any
由于数据类的字段 需要类型提示 ,因此我使用了模块中的 Anytyping。
typing
在 Python 3.7 之前,经常看到命名元组被用作不可变对象。它在很多方面都可能很棘手,其中之一是__eq__namedtuples 之间的方法不考虑对象的类。例如:
__eq__
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__方法是元组的方法,它只比较给定位置的字段的值。这可能是一个巨大的错误来源,特别是如果您对这些类进行子类化。
obj1
obj2
obj1 == obj2
True