虽然我从来都不需要这样做,但我突然意识到用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

但是你可以通过self[0]和self[1]访问a和b变量,这很烦人。

这在Pure Python中可行吗?如果不是,我该如何用C扩展来做呢?

(只能在python3中工作的答案是可以接受的)。

更新:

从Python 3.7开始,要使用的方法是使用@dataclass装饰器,参见最新接受的答案。


当前回答

另一种方法是创建一个使实例不可变的包装器。

class Immutable(object):

    def __init__(self, wrapped):
        super(Immutable, self).__init__()
        object.__setattr__(self, '_wrapped', wrapped)

    def __getattribute__(self, item):
        return object.__getattribute__(self, '_wrapped').__getattribute__(item)

    def __setattr__(self, key, value):
        raise ImmutableError('Object {0} is immutable.'.format(self._wrapped))

    __delattr__ = __setattr__

    def __iter__(self):
        return object.__getattribute__(self, '_wrapped').__iter__()

    def next(self):
        return object.__getattribute__(self, '_wrapped').next()

    def __getitem__(self, item):
        return object.__getattribute__(self, '_wrapped').__getitem__(item)

immutable_instance = Immutable(my_instance)

这在只有一些实例必须是不可变的情况下很有用(比如函数调用的默认参数)。

也可以用于不可变工厂,如:

@classmethod
def immutable_factory(cls, *args, **kwargs):
    return Immutable(cls.__init__(*args, **kwargs))

也保护对象。__setattr__,但由于Python的动态特性,可能会被其他技巧所绊倒。

其他回答

除了其他优秀的答案之外,我喜欢为python 3.4(或者可能是3.3)添加一个方法。这个答案建立在之前对这个问题的几个答案的基础上。

在python 3.4中,可以使用不带设置符的属性来创建不可修改的类成员。(在早期版本中,可以不使用setter为属性赋值。)

class A:
    __slots__=['_A__a']
    def __init__(self, aValue):
      self.__a=aValue
    @property
    def a(self):
        return self.__a

你可以这样使用它:

instance=A("constant")
print (instance.a)

它会输出constant

而是调用实例。A =10会导致:

AttributeError: can't set attribute

解释:不带设置符的属性是python 3.4(我认为是3.3)的最新特性。如果您尝试给这样的属性赋值,则会引发Error。 使用插槽,我将成员变量限制为__A_a(即__a)。

问题:赋值给_aa仍然是可能的(instance. _aa =2)。但是如果你给一个私有变量赋值,那是你自己的错…

然而,这个答案不鼓励使用__slots__。使用其他方法来阻止属性创建可能更可取。

..如何在C中“正确地”做这件事?

你可以使用Cython为Python创建一个扩展类型:

cdef class Immutable:
    cdef readonly object a, b
    cdef object __weakref__ # enable weak referencing support

    def __init__(self, a, b):
        self.a, self.b = a, b

它既适用于Python 2。X和3。

测试

# compile on-the-fly
import pyximport; pyximport.install() # $ pip install cython
from immutable import Immutable

o = Immutable(1, 2)
assert o.a == 1, str(o.a)
assert o.b == 2

try: o.a = 3
except AttributeError:
    pass
else:
    assert 0, 'attribute must be readonly'

try: o[1]
except TypeError:
    pass
else:
    assert 0, 'indexing must not be supported'

try: o.c = 1
except AttributeError:
    pass
else:
    assert 0, 'no new attributes are allowed'

o = Immutable('a', [])
assert o.a == 'a'
assert o.b == []

o.b.append(3) # attribute may contain mutable object
assert o.b == [3]

try: o.c
except AttributeError:
    pass
else:
    assert 0, 'no c attribute'

o = Immutable(b=3,a=1)
assert o.a == 1 and o.b == 3

try: del o.b
except AttributeError:
    pass
else:
    assert 0, "can't delete attribute"

d = dict(b=3, a=1)
o = Immutable(**d)
assert o.a == d['a'] and o.b == d['b']

o = Immutable(1,b=3)
assert o.a == 1 and o.b == 3

try: object.__setattr__(o, 'a', 1)
except AttributeError:
    pass
else:
    assert 0, 'attributes are readonly'

try: object.__setattr__(o, 'c', 1)
except AttributeError:
    pass
else:
    assert 0, 'no new attributes'

try: Immutable(1,c=3)
except TypeError:
    pass
else:
    assert 0, 'accept only a,b keywords'

for kwd in [dict(a=1), dict(b=2)]:
    try: Immutable(**kwd)
    except TypeError:
        pass
    else:
        assert 0, 'Immutable requires exactly 2 arguments'

如果你不介意索引支持,那么@Sven Marnach建议的collections.namedtuple是更可取的:

Immutable = collections.namedtuple("Immutable", "a b")

我找到了一种方法,不用子类化tuple, namedtuple等。你所需要做的就是在初始化后禁用setattr和delattr(如果你想让一个集合成为不可变的,也要禁用setitem和delitem):

def __init__(self, *args, **kwargs):
    # something here

    self.lock()

其中lock可以是这样的:

@classmethod
def lock(cls):
    def raiser(*a):
        raise TypeError('this instance is immutable')

    cls.__setattr__ = raiser
    cls.__delattr__ = raiser
    if hasattr(cls, '__setitem__'):
        cls.__setitem__ = raiser
        cls.__delitem__ = raiser

你可以用这个方法创建类Immutable,并像我展示的那样使用它。

如果你不想在每个init中都写self.lock(),你可以用元类自动实现:

class ImmutableType(type):
    @classmethod
    def change_init(mcs, original_init_method):
        def __new_init__(self, *args, **kwargs):
            if callable(original_init_method):
                original_init_method(self, *args, **kwargs)

            cls = self.__class__

            def raiser(*a):
                raise TypeError('this instance is immutable')

            cls.__setattr__ = raiser
            cls.__delattr__ = raiser
            if hasattr(cls, '__setitem__'):
                cls.__setitem__ = raiser
                cls.__delitem__ = raiser

        return __new_init__

    def __new__(mcs, name, parents, kwargs):
        kwargs['__init__'] = mcs.change_init(kwargs.get('__init__'))
        return type.__new__(mcs, name, parents, kwargs)


class Immutable(metaclass=ImmutableType):
    pass

Test

class SomeImmutableClass(Immutable):
    def __init__(self, some_value: int):
        self.important_attr = some_value

    def some_method(self):
        return 2 * self.important_attr


ins = SomeImmutableClass(3)
print(ins.some_method())  # 6
ins.important_attr += 1  # TypeError
ins.another_attr = 2  # TypeError

我刚刚想到的另一个解决方案是:获得与原始代码相同行为的最简单方法是

Immutable = collections.namedtuple("Immutable", ["a", "b"])

它并没有解决属性可以通过[0]等访问的问题,但至少它相当简短,并提供了与pickle和copy兼容的额外优势。

namedtuple创建了一个类似于我在这个答案中描述的类型,即从tuple派生并使用__slots__。它在Python 2.6或更高版本中可用。

另一种方法是创建一个使实例不可变的包装器。

class Immutable(object):

    def __init__(self, wrapped):
        super(Immutable, self).__init__()
        object.__setattr__(self, '_wrapped', wrapped)

    def __getattribute__(self, item):
        return object.__getattribute__(self, '_wrapped').__getattribute__(item)

    def __setattr__(self, key, value):
        raise ImmutableError('Object {0} is immutable.'.format(self._wrapped))

    __delattr__ = __setattr__

    def __iter__(self):
        return object.__getattribute__(self, '_wrapped').__iter__()

    def next(self):
        return object.__getattribute__(self, '_wrapped').next()

    def __getitem__(self, item):
        return object.__getattribute__(self, '_wrapped').__getitem__(item)

immutable_instance = Immutable(my_instance)

这在只有一些实例必须是不可变的情况下很有用(比如函数调用的默认参数)。

也可以用于不可变工厂,如:

@classmethod
def immutable_factory(cls, *args, **kwargs):
    return Immutable(cls.__init__(*args, **kwargs))

也保护对象。__setattr__,但由于Python的动态特性,可能会被其他技巧所绊倒。