如何在python中使类不可变?

时间:2014-03-20 20:42:01

标签: python class immutability

我在这里已经阅读了很多关于这个主题但我仍然找不到合适的答案。 我有一个类:

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 个答案:

答案 0 :(得分:4)

您可以禁用__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以递归方式扩展到其所有成员。

答案 1 :(得分:3)

您可以覆盖__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))

另一种方法是使用属性而不是普通属性:

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