Python

Read-Only Properties in Python

Python descriptors are a fairly powerful mechanic, especially for creating properties. The built-in property descriptor/decorator works very well for defining properties, but they have a small weakness in my eyes: it doesn’t have defaults for simple usage.

Yes, not supplying the property class with all methods does have the decent default of not allowing that usage, but I mean that it doesn’t automatically implement some form of backing field. Backing fields are done completely explicitly by you.

This is not a problem in some cases. In fact, it makes the property class exceptionally versatile, but at the cost of my explicitness on the part of the user. It has its uses, and it’s great at them. But I have need for something a little less versatile, more specialized, and more implicit.

I strongly try to make as many of my classes immutable as possible, avoiding side effects. Because of this, I want my class attributes to be read-only. There are two existing ways to deal with this. The first is to simply document that I want the attributes to be left alone. This is valid, and has a sort of simplicity to it, but sometimes I like to back up those intents with something a little more restrictive. The second option is to use a property without defining a setter. This is actually a very good option, but as I said while leading up to this, it’s highly explicit. I want a property that is designed for the express purpose of being read-only.

In this article, I present two different ideas that I’ve had for “read-only” properties. I put read-only in quotes because, being properties, you still need to set the initial value. That part was the most difficult part, but I’ve found two different ways to get around it. I call the two methods for doing so “set once” and “secret set”.

Both methods have a simple lookup for the __get__ implementation. You could do this one of two ways. When setting the value, you could store it back on the instance object with a backing field, or within the descriptor using a dict with the instance as the key.

In general, I like the former method more, since it more accurately follows how other property mechanisms in other languages work. It can also speed up lookup if there is a large number of the objects, since there are less likely to be collisions within the small dict of an object than a large dict in a descriptor. It comes at a price, though.

You either have to come up with a random backing field name and hope it doesn’t clash with anything, or you can have the constructor take in an explicit name for the backing field. Lastly, the backing field is more easily discovered, since it’s within the object’s __dict__.

In order to make the code simpler overall, the examples (and probably what I fully end up going with) will use an internal dict for the descriptor.

Set Once Property

The SetOnceProperty has what is probably the most obvious and direct route of implementation and is easier to use. It’s a little less open to being set again if needed, but it fits most purposes. It’s also very slightly less performant when setting the initial value. Not enough to even be noticeable unless you are doing it billions of time.

The way it works is like this: When __set__ is called with an instance, it checks to see if the instance already has a value set. If it does, it raises an AttributeError (with no message in this case, but that’s just for brevity). If it doesn’t have a value, it’s set. That’s all.

If you REALLY need to change the value again, the __get__ method returns the descriptor instance when called from the class instead of the instance. From there, you can access the dict and fiddle there. This will be the same in the SecretSetProperty, too, but it has a slightly cleaner way of changing it again.

Here it is:

class SetOnceProperty():
    def __init__(self):
        self.instances = {}

    def __get__(self, instance, owner):
        if instance is None:
            return self
        else:
            return self.instances[instance]

    def __set__(self, instance, value):
        if instance in self.instances:
            raise AttributeError
        else:
            self.instances[instance] = value

Secret Set Property

I call it “secret set” because the way of setting the value to the property is done is a secret way. Generally, it’s impossible to have direct access to a descriptor, since it’s “magically” accessed normally. You can override __getattribute__ to change descriptor access, which I considered for a while, using a function that would monkeypatch the method, use the new one, then set the method back to how it previously was, but I try to avoid monkeypatching. Instead, like I did in the SetOnceProperty, I simply return the descriptor itself when it’s accessed by class instead of instance.

Why do I want to get the descriptor object? Because SecretSetProperty has a “secret” method, called initialize, for setting the property value. This way, the normal __set__ call can always prevent the change by accident, but the value can still fairly easily be set at any time.

Here’s the code for it:

class SecretSetProperty():
    def __init__(self):
        self.instances = {}

    def __get__(self, instance, owner):
        if instance is None:
            return self
        else:
            return self.instances[instance]

    def __set__(self, instance, value):
        raise AttributeError

    def initialize(self, instance, value):
        self.instances[instance] = value

Using the Properties

Okay, we have the properties, but I should make sure you guys know how to create them and set the values. Following, I have SOPTest and SSPTest. They are classes that each have a property named prop, and each use the SetOnceProperty and SecretSetProperty, respectively.

class SOPTest():
    prop = SetOnceProperty()

    def __init__(self, val):
        self.prop = val
class SSPTest():
prop = SecretSetProperty()
def __init__(self, val):
SSPTest.prop.initialize(self, val)

Outro

That’s my proposal for some “read-only” property classes. What do you think? Are they worthwhile? Has anyone seen some made by someone else?

Reference: Read-Only Properties in Python from our WCG partner Jacob Zimmerman at the Programming Ideas With Jake blog.

Jacob Zimmerman

Jacob is a certified Java programmer (level 1) and Python enthusiast. He loves to solve large problems with programming and considers himself pretty good at design.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button