如何在Python中创建不可变类?

5 投票
2 回答
3894 浏览
提问于 2025-04-17 23:12

我在这里看了很多关于这个话题的内容,但还是找不到合适的答案。
我有一个这样的类:

class A(object):

    def __init__(self, first, second):
        self.first = first
        self.second = second

    def __eq__(self, other):
        return ****

    def __str__(self):
        return *****

    def __repr__(self):
        return **** 

a = A("a", "b")

我该怎么做才能禁止像 a.first = "c" 这样的操作呢?

2 个回答

6

你可以在初始化对象的最后一步禁用 __setattr__

class A(object):

    def __init__(self, first, second):
        self.first = first
        self.second = second
        self.frozen = True

    def __setattr__(self, name, value):
        if getattr(self, 'frozen', False):
            raise AttributeError('Attempt to modify immutable object')
        super(A, self).__setattr__(name, value)

>>> a = A(1, 2)
>>> a.first, a.second
(1, 2)
>>> a.first = 3

Traceback (most recent call last):
  File "<pyshell#46>", line 1, in <module>
    a.first = 3
  File "<pyshell#41>", line 10, in __setattr__
    raise AttributeError('Attempt to modify immutable object')
AttributeError: Attempt to modify immutable object

编辑:这个回答有个缺陷,我相信其他的解决方案也会有同样的问题:如果对象的成员本身是可变的,那就没有什么能保护它们了。例如,如果你的对象里面有一个列表,那就完蛋了。这和C++不同,在C++中声明一个对象为 const 会递归地扩展到它的所有成员。

5

你可以重写 __setattr__ 方法,这样可以做到不允许 任何 修改:

def __setattr__(self, name, value):
    raise AttributeError('''Can't set attribute "{0}"'''.format(name))

或者你可以防止添加新的属性:

def __setattr__(self, name, value):
    if not hasattr(self, name):
        raise AttributeError('''Can't set attribute "{0}"'''.format(name))
    # Or whatever the base class is, if not object.
    # You can use super(), if appropriate.
    object.__setattr__(self, name, value)

你还可以用一个允许的属性列表来替代 hasattr 方法进行检查:

if name not in list_of_allowed_attributes_to_change:
    raise AttributeError('''Can't set attribute "{0}"'''.format(name))

另一种方法是使用属性(properties),而不是普通的属性:

class A(object):

    def __init__(self, first, second):
        self._first = first
        self._second = second

    @property
    def first(self):
        return self._first

    @property
    def second(self):
        return self._second

撰写回答