具有属性构建的不可变 class

Immutable class with attribute build-up

我有一个 class DifferentialExtension:

class DifferentialExtension(object):
    __slots__ = ('f', 'x', 'D', 'T')
    def __init__(self, f=None, x=None):
        /*
        # some code that builds up list 'self.D'
        */
        self.D = tuple(self.D)
        return None

我要使 class "Immutable" 即使用 DifferentialExtension 创建的对象不允许更改属性 "D"(在 __init__ 之后已完成),这些属性都不会分配给新对象。 D不一定是list,最后返回的时候可以是tuple

In [1]: DE = DifferentialExtension(log(x), x)
In [2]: DE.D
Out[2]: ((Poly(1, x, domain='ZZ'), Poly(1/x, t0, domain='ZZ(x)'))
In [3]: DE.D = (1, 5, 5)  # raises Error.

是这样的吗?

 class DifferentialExtension(object):
     _frozen = set()
     __slots__ = ('f', 'x', 'D', 'T')
     def __init__(self, f=None, x=None):
         self.D = 'something'
         self.D = tuple(self.D)
         self._frozen.add(id(self))

     def __setattr__(self, attr, value):
         if id(self) in self._frozen:
            raise TypeError('object is frozen')
         object.__setattr__(self, attr, value)

测试:

In [29]: a = DifferentialExtension('eh', 'oh')

In [30]: a.D
Out[30]: ('s', 'o', 'm', 'e', 't', 'h', 'i', 'n', 'g')

In [31]: a.D = 'something else'
...
TypeError: object is frozen

编辑。 正如另一个答案中提到的,命名元组是执行此操作的自然方法,但由于您在构造过程中进行了一些计算,因此请使用 classmethod 作为备用构造函数:

class DifferentialExtension(namedtuple('DifferentialExtension', 'f, x, D, T')):
    @classmethod
    def build_me(cls, f=None, x=None):
        # a bunch of code that works on D and sets D and T (if you need T)
        T = 'something T'
        D = 'something' ##D = tuple(D) works
        return cls(f, x, D, T)

正在测试命名元组:

In [41]: DE = DifferentialExtension.build_me(f='some f value', x='some x value')

In [42]: DE.D
Out[42]: 'something'

In [43]: DE.D = 'some other thing'
...

AttributeError: can't set attribute

通常在 Python 中,您必须假设使用您的 class 的人不会试图做一些恶意的事情。因此,如果他 真的 想要更改 D 的值,他可能有充分的理由。因此,您可能不希望无法更改 D,因为那不是 Pythonic。但是,您可能希望帮助用户避免意外更改 D。最好使用只读 属性 来完成,如:

class DifferentialExtension(object):
    def __init__(self, f=None, x=None):
        self._D = 'immutable value'
        return None

    @property
    def D(self):
        return self._D

运行 这给出:

>>> d = DifferentialExtension()
>>> d.D = 'mutate'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: can't set attribute

如果用户 真的 想要更改值,他可以通过直接访问 self._D 但是你必须假设他知道他在做什么,因为他弄乱了你内部的一个带下划线的变量。

使用 namedtuple 作为不可变对象的基础。如果您愿意,您可以扩展它们以满足您的需要。

from collections import namedtuple

class DifferentialExtension(namedtuple('DifferentialExtension', 'f x')):
    def another_method(self):
        print self.x

x = DifferentialExtension(1, 2)

x.f = 2.2
# AttributeError: can't set attribute