Python中的类级只读属性
有没有什么办法可以在Python中创建一个类级别的只读属性?比如说,如果我有一个类叫做Foo
,我想这样做:
x = Foo.CLASS_PROPERTY
但又想防止别人这样做:
Foo.CLASS_PROPERTY = y
编辑:我喜欢Alex Martelli的解决方案的简单性,但不太喜欢它所需要的语法。他和~unutbu的回答都启发了我下面的解决方案,这个方案更接近我想要的感觉:
class const_value (object):
def __init__(self, value):
self.__value = value
def make_property(self):
return property(lambda cls: self.__value)
class ROType(type):
def __new__(mcl,classname,bases,classdict):
class UniqeROType (mcl):
pass
for attr, value in classdict.items():
if isinstance(value, const_value):
setattr(UniqeROType, attr, value.make_property())
classdict[attr] = value.make_property()
return type.__new__(UniqeROType,classname,bases,classdict)
class Foo(object):
__metaclass__=ROType
BAR = const_value(1)
BAZ = 2
class Bit(object):
__metaclass__=ROType
BOO = const_value(3)
BAN = 4
现在,我得到了:
Foo.BAR
# 1
Foo.BAZ
# 2
Foo.BAR=2
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# AttributeError: can't set attribute
Foo.BAZ=3
#
我更喜欢这个解决方案,因为:
- 成员在声明时就定义好了,而不是像
type(X).foo = ...
那样事后再定义。 - 成员的值是在实际类的代码中设置的,而不是在类的元类代码中。
不过这仍然不是最理想的,因为:
- 我必须设置
__metaclass__
,才能让const_value
对象被正确理解。 - 这些
const_value
的表现方式和普通值不一样。例如,我不能把它用作类中方法参数的默认值。
4 个回答
在ActiveState上发现了这个:
# simple read only attributes with meta-class programming
# method factory for an attribute get method
def getmethod(attrname):
def _getmethod(self):
return self.__readonly__[attrname]
return _getmethod
class metaClass(type):
def __new__(cls,classname,bases,classdict):
readonly = classdict.get('__readonly__',{})
for name,default in readonly.items():
classdict[name] = property(getmethod(name))
return type.__new__(cls,classname,bases,classdict)
class ROClass(object):
__metaclass__ = metaClass
__readonly__ = {'a':1,'b':'text'}
if __name__ == '__main__':
def test1():
t = ROClass()
print t.a
print t.b
def test2():
t = ROClass()
t.a = 2
test1()
注意,如果你尝试去设置一个只读属性(比如说用t.a = 2
),Python会抛出一个叫做AttributeError
的错误。
Pynt提到的这个ActiveState解决方案,让ROClass的实例拥有只读属性。你的问题似乎是在问,类本身是否可以有只读属性。
这里有一种方法,基于Raymond Hettinger的评论:
#!/usr/bin/env python
def readonly(value):
return property(lambda self: value)
class ROType(type):
CLASS_PROPERTY = readonly(1)
class Foo(object):
__metaclass__=ROType
print(Foo.CLASS_PROPERTY)
# 1
Foo.CLASS_PROPERTY=2
# AttributeError: can't set attribute
这个想法是这样的:首先看看Raymond Hettinger的解决方案:
class Bar(object):
CLASS_PROPERTY = property(lambda self: 1)
bar=Bar()
bar.CLASS_PROPERTY=2
它展示了一种相对简单的方法,让bar拥有一个只读属性。
注意,你需要在bar的类定义中添加CLASS_PROPERTY = property(lambda self: 1)
这一行,而不是直接加在bar上。
所以,如果你想让类Foo
有一个只读属性,那么Foo
的父类必须定义CLASS_PROPERTY = property(lambda self: 1)
。
类的父类是元类。因此,我们将ROType定义为元类:
class ROType(type):
CLASS_PROPERTY = readonly(1)
然后我们让Foo的父类是ROType:
class Foo(object):
__metaclass__=ROType
现有的解决方案有点复杂——那我们干脆确保某一组中的每个类都有一个独特的 metaclass(类的类),然后在这个自定义的 metaclass 上设置一个普通的只读属性,这样做怎么样呢?
>>> class Meta(type):
... def __new__(mcl, *a, **k):
... uniquemcl = type('Uniq', (mcl,), {})
... return type.__new__(uniquemcl, *a, **k)
...
>>> class X: __metaclass__ = Meta
...
>>> class Y: __metaclass__ = Meta
...
>>> type(X).foo = property(lambda *_: 23)
>>> type(Y).foo = property(lambda *_: 45)
>>> X.foo
23
>>> Y.foo
45
>>>
这样做其实简单多了,因为这只是基于一个事实:当你获取一个实例的属性时,属性描述符是从类中查找的(所以当然,当你获取一个类的属性时,属性描述符是从 metaclass 中查找的),而让类和 metaclass 唯一并不是特别困难。
哦,当然:
>>> X.foo = 67
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: can't set attribute
只是为了确认它确实是只读的!