如何在Python中创建只读类属性?
基本上,我想做的事情是这样的:
class foo:
x = 4
@property
@classmethod
def number(cls):
return x
然后我希望下面的代码能够正常运行:
>>> foo.number
4
可惜的是,上面的代码并没有成功。它给我的不是 4
,而是 <property object at 0x101786c58>
。有没有办法能实现我想要的效果呢?
5 个回答
17
我同意unubtu的回答;这个方法看起来有效,不过在Python 3(具体来说,我在使用Python 3.4时遇到了问题)上,这种特定的写法是行不通的。下面是要在Python 3.4中正确写出这个模式的方法:
class MetaFoo(type):
@property
def number(cls):
return cls.x
class Foo(metaclass=MetaFoo):
x = 4
print(Foo.number)
# 4
Foo.number = 6
# AttributeError: can't set attribute
76
这段代码会让 Foo.number
成为一个 只读 属性:
class MetaFoo(type):
@property
def number(cls):
return cls.x
class Foo(object, metaclass=MetaFoo):
x = 4
print(Foo.number)
# 4
Foo.number = 6
# AttributeError: can't set attribute
解释:通常使用 @property
的场景是这样的:
class Foo(object):
@property
def number(self):
...
foo = Foo()
在 Foo
中定义的属性对于它的实例来说是只读的。也就是说,如果你尝试执行 foo.number = 6
,就会出现一个 AttributeError
错误。
类似地,如果你想让 Foo.number
也抛出 AttributeError
,你需要在 type(Foo)
中设置一个属性。因此,这就需要用到元类。
需要注意的是,这种只读的属性并不是完全安全的,黑客可以通过修改 Foo
的类来让这个属性变得可写:
class Base(type): pass
Foo.__class__ = Base
# makes Foo.number a normal class attribute
Foo.number = 6
print(Foo.number)
会打印出
6
或者,如果你想让 Foo.number
成为一个可设置的属性,
class WritableMetaFoo(type):
@property
def number(cls):
return cls.x
@number.setter
def number(cls, value):
cls.x = value
Foo.__class__ = WritableMetaFoo
# Now the assignment modifies `Foo.x`
Foo.number = 6
print(Foo.number)
也会打印出
6
54
当你从一个类中访问 property
描述符时,它总是会返回自己(也就是说,当 instance
在它的 __get__
方法中是 None
时)。
如果你想要的不是这样,你可以写一个新的描述符,它总是使用类对象(owner
),而不是实例:
>>> class classproperty(object):
... def __init__(self, getter):
... self.getter= getter
... def __get__(self, instance, owner):
... return self.getter(owner)
...
>>> class Foo(object):
... x= 4
... @classproperty
... def number(cls):
... return cls.x
...
>>> Foo().number
4
>>> Foo.number
4