How to create a read-only class property in Python? [duplicate]

Essentially I want to do something like this:

class foo:
    x = 4
    @property
    @classmethod
    def number(cls):
        return x

Then I would like the following to work:

>>> foo.number
4

Unfortunately, the above doesn't work. Instead of given me 4 it gives me <property object at 0x101786c58>. Is there any way to achieve the above?


This will make Foo.number a read-only property:

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

Explanation: The usual scenario when using @property looks like this:

class Foo(object):
    @property
    def number(self):
        ...
foo = Foo()

A property defined in Foo is read-only with respect to its instances. That is, foo.number = 6 would raise an AttributeError.

Analogously, if you want Foo.number to raise an AttributeError you would need to setup a property defined in type(Foo). Hence the need for a metaclass.


Note that this read-onlyness is not immune from hackers. The property can be made writable by changing Foo's class:

class Base(type): pass
Foo.__class__ = Base

# makes Foo.number a normal class attribute
Foo.number = 6   
print(Foo.number)

prints

6

or, if you wish to make Foo.number a settable property,

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)

also prints

6

The property descriptor always returns itself when accessed from a class (ie. when instance is None in its __get__ method).

If that's not what you want, you can write a new descriptor that always uses the class object (owner) instead of the instance:

>>> 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