How to Create a Read-Only Class Property in Python

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

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

When should an attribute be private and made a read-only property?

Generally, Python programs should be written with the assumption that all users are consenting adults, and thus are responsible for using things correctly themselves. However, in the rare instance where it just does not make sense for an attribute to be settable (such as a derived value, or a value read from some static datasource), the getter-only property is generally the preferred pattern.

Make a class instance attribute read-only in Python

It looks fine to me, except one thing: _path = None belongs to a class.

This is just a small improvment:

class DataFolder(object):
def __init__(self, owner, path):
self._path = None # now it is an instance variable.
self.path = path
self.owner = owner

@property
def owner(self):
return self._owner

def _validate_owner(self, owner_value):
if "me" not in owner_value:
raise ValueError("invalid owner")

@owner.setter
def owner(self, owner_value):
self._validate_owner(owner_value)
self._owner = owner_value

@property
def path(self):
return self._path

def _validate_path(self, path_value):
if self._path is not None:
raise AttributeError("Cannot edit path of existing data folder")
if "dat" not in path_value:
raise ValueError("invalid folder")

@path.setter
def path(self, path_value):
self._validate_path(path_value)
self._path = path_value

Using:

d = DataFolder('me', 'data')
print(d.path, d.owner)
d.path = 'new_data'

Output:

('data', 'me')
new_me
AttributeError: Cannot edit path of existing data folder

How to create object having read-only attributes dynamically

Consider starting with __setattr__:

>>> class ReadOnlyClass:
... def __init__(self, **kwargs):
... self.__dict__.update(kwargs)
...
... def __setattr__(self, key, value):
... raise AttributeError("can't set attribute")
...
>>> readonly_object = ReadOnlyClass(name='Tom', age=24)
>>> readonly_object.name
'Tom'
>>> readonly_object.age
24
>>> readonly_object.age = 10
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 6, in __setattr__
AttributeError: can't set attribute

However, this may not fully meet your expectations. You can still set the attributes through __dict__:

>>> readonly_object.__dict__['age'] = 10
>>> readonly_object.age
10

Shorter way to make properties read-only

At the very least, you could just call property as a function, rather than using it as a decorator. At the same time, you can store the underlying values in a list or dict rather than as separate attributes.

class MyClass(object):
def __init__(self):
self._values = [...]

a = property(lambda self: self._values[0])
b = property(lambda self: self._values[1])
# etc

However, a read-only property doesn't really need to store its value in the instance dict; just hard-code the value directly in the getter:

class MyClass(object):

a = property(lambda self: "foo")
b = property(lambda self: "bar")

And then wrap the call to property in another function :)

def constant(value):
def _(self):
return value
return property(_)

class MyClass(object):
a = constant("foo")
b = constant("bar")

Here's a pure-Python read-only property, modeled after the example shown at https://docs.python.org/3/howto/descriptor.html#properties:

class Constant(object):
def __init__(self, value)
def _(self):
return value
self.fget = _

def __get__(self, obj, objtype=None):
if obj is None:
return self
return self.fget(obj)

This is probably simpler than subclassing property and overriding __set__ and __del__ to "unimplement" them. But, I like my idea of a wrapper around a regular property better.

How to set a read-only @property in __init__

Functions are 1st class citizens in python. You cant have two members in your class that are named exactly the same. Essentially your class already has a self.value of type <class 'property'> that your code tries to set to a integer given as value which is forbidden - so this error pop up.

Circumvent it by:

class A:
def __init__(self, value: int):
self._value = value # private backer

@property
def value(self):
return self._value

my_a = A(22) # works, no error
print(my_a.value) # 22

How should I expose read-only fields from Python classes?

I would use property as a decorator to manage your getter for name (see the example for the class Parrot in the documentation). Use, for example, something like:

class Article(object):
def __init__(self, name, available):
self._name = name
self.available = available

@property
def name(self):
return self._name

If you do not define the setter for the name property (using the decorator x.setter around a function) this throws an AttributeError when you try and reset name.

Note: You have to use Python's new-style classes (i.e. in Python 2.6 you have to inherit from object) for properties to work correctly. This is not the case according to @SvenMarnach.

python read-only class properties

Use a metaclass

class MetaVector3(type):

@property
def zero(cls):
return cls(0,0,0)

class Vector3(object):
__metaclass__ = MetaVector3

def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z

>>> v = Vector3.zero
>>> v.x, v.y, v.z
(0, 0, 0)


Related Topics



Leave a reply



Submit