Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

python

I have many different small classes which have a few fields each, e.g. this:

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

What's the easiest and/or most idiomatic way to make the name field read only, so that

a = Article("Pineapple", True) a.name = "Banana"  # <-- should not be possible 

is not possible anymore?

Here's what I considered so far:

  1. Use a getter (ugh!).

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

    Ugly, non-pythonic - and a lot of boilerplate code to write (especially if I have multiple fields to make read-only). However, it does the job and it's easy to see why that is.

  2. Use __setattr__:

    class Article:     def __init__(self, name, available):         self.name = name         self.available = available      def __setattr__(self, name, value):         if name == "name":             raise Exception("%s property is read-only" % name)         self.__dict__[name] = value 

    Looks pretty on the caller side, seems to be the idiomatic way to do the job - but unfortunately I have many classes with only a few fields to make read only each. So I'd need to add a __setattr__ implementation to all of them. Or use some sort of mixin maybe? In any case, I'd need to make up my mind how to behave in case a client attempts to assign a value to a read-only field. Yield some exception, I guess - but which?

  3. Use a utility function to define properties (and optionally getters) automatically. This is basically the same idea as (1) except that I don't write the getters explicitely but rather do something like

    class Article:     def __init__(self, name, available):         # This function would somehow give a '_name' field to self         # and a 'name()' getter to the 'Article' class object (if         # necessary); the getter simply returns self._name         defineField(self, "name")         self.available = available 

    The downside of this is that I don't even know if this is possible (or how to implement it) since I'm not familiar with runtime code generation in Python. :-)

So far, (2) appears to be most promising to me except for the fact that I'll need __setattr__ definitions to all my classes. I wish there was a way to 'annotate' fields so that this happens automatically. Does anybody have a better idea?

For what it's worth, I'mu sing Python 2.6.

UPDATE: Thanks for all the interesting responses! By now, I have this:

def ro_property(o, name, value):     setattr(o.__class__, name, property(lambda o: o.__dict__["_" + name]))     setattr(o, "_" + name, value)  class Article(object):     def __init__(self, name, available):         ro_property(self, "name", name)         self.available = available 

This seems to work quite nicely. The only changes needed to the original class are

  • I need to inherit object (which is not such a stupid thing anyway, I guess)
  • I need to change self._name = name to ro_property(self, "name", name).

This looks quite neat to me - can anybody see a downside with it?

like image 356
Frerich Raabe Avatar asked Mar 29 '12 07:03

Frerich Raabe


People also ask

How do you make a class read-only in Python?

If you need to make a read-only attribute in Python, you can turn your attribute into a property that delegates to an attribute with almost the same name, but with an underscore prefixed before the its name to note that it's private convention.

What is a read-only property in Python?

To define a readonly property, you need to create a property with only the getter. However, it is not truly read-only because you can always access the underlying attribute and change it. The read-only properties are useful in some cases such as for computed properties.

What is __ slots __ in Python?

slots provide a special mechanism to reduce the size of objects.It is a concept of memory optimisation on objects. As every object in Python contains a dynamic dictionary that allows adding attributes. For every instance object, we will have an instance of a dictionary that consumes more space and wastes a lot of RAM.

How do you make class attributes private in Python?

But there is a method in Python to define Private: Add “__” (double underscore ) in front of the variable and function name can hide them when accessing them from out of class. Python doesn't have real private methods, so one underline in the beginning of a method or attribute means you shouldn't access this method.


2 Answers

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.

like image 78
Chris Avatar answered Oct 05 '22 18:10

Chris


As pointed out in other answers, using a property is the way to go for read-only attributes. The solution in Chris' answer is the cleanest one: It uses the property() built-in in a straight-forward, simple way. Everyone familiar with Python will recognize this pattern, and there's no domain-specific voodoo happening.

If you don't like that every property needs three lines to define, here's another straight-forward way:

from operator import attrgetter  class Article(object):     def __init__(self, name, available):         self._name = name         self.available = available     name = property(attrgetter("_name")) 

Generally, I don't like defining domain-specific functions to do something that can be done easily enough with standard tools. Reading code is so much easier if you don't have to get used to all the project-specific stuff first.

like image 39
Sven Marnach Avatar answered Oct 05 '22 18:10

Sven Marnach