__setattr__ 方法确保类中的对象不可变

1 投票
2 回答
1918 浏览
提问于 2025-04-18 04:20

我定义了一个类(这里面有很多内容被简化了),但我主要想问的是,一旦这个类的对象被创建并且它的变量被初始化后,我希望这些变量在类外部是不能被改变的。如果有人尝试去改变这些变量,我就需要抛出一个AssertionError(断言错误)。我已经定义了setattr方法来抛出这个异常,但在加入这个方法后,之前我对这个类进行的实例和测试都被完全破坏了,每当尝试创建这个类的对象时,都会抛出同样的AssertionError。因此,这里是我类的一个大幅简化版本。

class Interval:
    compare_mode = None 

    def __init__(self, min, max):
        self.min = min 
        self.max = max 

    def min_max(min, max = None)-> 'Interval Object':
    .
    .
    .
    return Interval(min, max)

其他类的方法...

def __setattr__(self, name, value):

    raise AssertionError('Objects in this class are immutable')

所以当我尝试测试这个类的对象是否可以被修改时,我得到了错误的异常,同时也破坏了我在这个类中其他方法的测试。

p.min = 0;  raised wrong exception(NameError)
p.max = 0;  raised wrong exception(NameError)
p.HeyImChangingYourMethodandThereIsNothingYouCanDoToStopME  raised wrong exception(NameError)

2 个回答

0

你的 __setattr__ 方法会阻止对象自己设置属性,比如在 __init__ 方法中设置的 self.minself.max

如果你想避免这种情况,可以在 __init__ 方法的最后设置一个标志,告诉程序这个对象是否已经初始化,然后让 __setattr__ 方法检查这个标志,如果标志已经设置,就不允许再设置属性。或者,你可以在 __init__ 方法中直接修改 self.__dict__ 来设置属性(例如,self.__dict__['min'] = min)。

如果你只需要初始化几个这样的属性,使用 property 来定义只读属性可能会更清晰。不过,和使用 __setattr__ 不同,这样做并不会阻止用户给你的类添加新的属性。

2

你的问题有点难懂,不过在 __init__ 里,你可以做类似下面的事情:

self.max = 0

这会调用 __setattr__,并立即抛出你的异常。

如果你真的想要不可变性,可以考虑使用 collections.namedtuple 加上 __slots__

class Interval(collections.namedtuple('IntervalBase', 'min,max')):
    __slots__ = ()
    def method1(self):
        print self.min
        print self.max

下面是一个示例:

>>> class Interval(collections.namedtuple('IntervalBase', 'min,max')):
...     __slots__ = ()
...     def method1(self):
...         print self.min
...         print self.max
... 
>>> Interval(1,2)
IntervalBase(min=1, max=2)
>>> Interval(1,2).method1
<bound method Interval.method1 of IntervalBase(min=1, max=2)>
>>> Interval(1,2).method1()
1
2
>>> Interval(1,2).foo = 1
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Interval' object has no attribute 'foo'

需要注意的是,用户仍然可以在 Interval 的子类中添加属性,除非那个子类也使用了 __slots__……


另外,通常你不需要担心用户在你的实例上添加临时属性。如果没有任何提供的 API 方法可以改变(你可以通过 property 来强制执行),那么在所有实际意义上,你的类就是 不可变 的,这样你就可以享受不可变性带来的好处(比如合理定义的 __hash__)。

撰写回答