在Python中使类不可变的方法

20 投票
4 回答
13968 浏览
提问于 2025-04-16 11:49

我正在进行一些分布式计算,几台机器之间需要沟通,而它们都假设自己拥有相同版本的各种类。因此,设计这些类为不可变的似乎是个不错的主意;这并不是说要防止有坏意图的用户,而是要确保这些类不会被意外修改。

我该怎么做呢?比如,我想实现一个元类,让使用它的类在定义后变得不可变。

>>> class A(object):
...     __metaclass__ = ImmutableMetaclass
>>> A.something = SomethingElse # Don't want this
>>> a = A()
>>> a.something = Whatever # obviously, this is still perfectly fine.

其他方法也可以,比如一个装饰器或函数,接收一个类并返回一个不可变的类。

4 个回答

1

如果你不介意使用别人的作品:

http://packages.python.org/pysistence/

这是一些不可变的持久数据结构(这里的“持久”是指功能上的,而不是写入磁盘的意思)。

即使你不直接使用它们,源代码也应该能给你一些灵感。例如,他们的扩展类(expando class)在构造时接收一个对象,然后返回这个对象的不可变版本。

7

别浪费时间在不可变类上。

其实,有很多事情比搞定不可变对象简单得多。

这里有五种不同的方法,你可以根据需要选择其中的任何一种,或者组合使用它们。任何一种方法都能奏效。

  1. 文档。其实,他们不会忘记这些的,给他们点信用。

  2. 单元测试。用一个简单的模拟对象来模拟你的应用对象,处理 __setattr__ 时抛出异常。对象状态的任何变化在单元测试中都是失败。这很简单,不需要复杂的编程。

  3. 重写 __setattr__ 方法,让它在每次尝试写入时抛出异常。

  4. collections.namedtuple。这个是开箱即用的不可变对象。

  5. collections.Mapping。这个也是不可变的,但你需要实现一些方法才能让它工作。

10

如果你觉得使用 __slots__ 这个老办法不适合你,那么你可以试试这个方法,或者它的变种:简单地在你的元类中写一个 __setattr__ 方法来作为你的保护措施。在这个例子中,我阻止了新属性的赋值,但允许修改已有的属性:

def immutable_meta(name, bases, dct):
    class Meta(type):
        def __init__(cls, name, bases, dct):
            type.__setattr__(cls,"attr",set(dct.keys()))
            type.__init__(cls, name, bases, dct)

        def __setattr__(cls, attr, value):
            if attr not in cls.attr:
                raise AttributeError ("Cannot assign attributes to this class")
            return type.__setattr__(cls, attr, value)
    return Meta(name, bases, dct)


class A:
    __metaclass__ = immutable_meta
    b = "test"

a = A()
a.c = 10 # this works
A.c = 20 # raises valueError

撰写回答