在Python中更新“常量”属性时引发异常
因为Python没有常量的概念,所以如果一个“常量”属性被更新,能否抛出一个异常呢?怎么做呢?
class MyClass():
CLASS_CONSTANT = 'This is a constant'
var = 'This is a not a constant, can be updated'
#this should raise an exception
MyClass.CLASS_CONSTANT = 'No, this cannot be updated, will raise an exception'
#this should not raise an exception
MyClass.var = 'updating this is fine'
#this also should raise an exception
MyClass().CLASS_CONSTANT = 'No, this cannot be updated, will raise an exception'
#this should not raise an exception
MyClass().var = 'updating this is fine'
任何试图改变CLASS_CONSTANT这个类属性或者实例属性的操作都应该抛出一个异常。
而改变var这个属性,无论是作为类属性还是实例属性,都不应该抛出异常。
5 个回答
2
你可以使用元类来实现这个功能:
class ImmutableConstants(type):
def __init__(cls, name, bases, dct):
type.__init__(cls, name, bases, dct)
old_setattr = cls.__setattr__
def __setattr__(self, key, value):
cls.assert_attribute_mutable(key)
old_setattr(self, key, value)
cls.__setattr__ = __setattr__
def __setattr__(self, key, value):
self.assert_attribute_mutable(key)
type.__setattr__(self, key, value)
def assert_attribute_mutable(self, name):
if name.isupper():
raise AttributeError('Attribute %s is constant' % name)
class Foo(object):
__metaclass__ = ImmutableConstants
CONST = 5
class_var = 'foobar'
Foo.class_var = 'new value'
Foo.CONST = 42 # raises
不过,你确定这真的是个问题吗?你真的在到处不小心设置常量吗?其实你可以通过一个命令很容易找到这些常量,命令是 grep -r '\.[A-Z][A-Z0-9_]*\s*=' src/
。
2
你可以这样做:
(来自 http://www.siafoo.net/snippet/108)
class Constants:
# A constant variable
foo = 1337
def __setattr__(self, attr, value):
if hasattr(self, attr):
raise ValueError, 'Attribute %s already has a value and so cannot be written to' % attr
self.__dict__[attr] = value
然后像这样使用它:
>>> const = Constants()
>>> const.test1 = 42
>>> const.test1
42
>>> const.test1 = 43
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in __setattr__
ValueError: Attribute test1 already has a value and so cannot be written to
>>> const.test1
42
4
在每个类中自定义 __setattr__
(比如我之前提到的那个例子,还有@ainab的回答和其他一些回答),只能阻止对实例属性的赋值,而不能阻止对类属性的赋值。所以,现有的回答其实都不能完全满足你的需求。
如果你问的确实是你想要的东西,那你可以考虑使用一些自定义的元类和描述符的组合,比如:
class const(object):
def __init__(self, val): self.val = val
def __get__(self, *_): return self.val
def __set__(self, *_): raise TypeError("Can't reset const!")
class mcl(type):
def __init__(cls, *a, **k):
mkl = cls.__class__
class spec(mkl): pass
for n, v in vars(cls).items():
if isinstance(v, const):
setattr(spec, n, v)
spec.__name__ = mkl.__name__
cls.__class__ = spec
class with_const:
__metaclass__ = mcl
class foo(with_const):
CLASS_CONSTANT = const('this is a constant')
print foo().CLASS_CONSTANT
print foo.CLASS_CONSTANT
foo.CLASS_CONSTANT = 'Oops!'
print foo.CLASS_CONSTANT
这些内容比较复杂,所以你可能会更喜欢其他回答中提到的简单的 __setattr__
方法,尽管它并不能完全满足你的需求(也就是说,你可能会合理地选择放宽一些要求,以便让事情变得简单一些;-)。不过这里的技术还是挺有意思的:自定义的描述符类型 const
是另一种方法(在我看来比在每个需要常量的类中重写 __setattr__
要好得多,而且可以避免把所有属性都变成常量,而是可以选择性地设置...)来阻止对实例属性的赋值;其余的代码是关于一个自定义元类创建独特的每类子元类,以便充分利用这个自定义描述符,达到你特别要求的功能。