Python中的类级只读属性

35 投票
4 回答
15561 浏览
提问于 2025-04-15 16:01

有没有什么办法可以在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 个回答

0

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的错误。

5

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
10

现有的解决方案有点复杂——那我们干脆确保某一组中的每个类都有一个独特的 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

只是为了确认它确实是只读的!

撰写回答