The Catch

The property function: why Python does not have Private Methods

by Michael

Every now and then, I see it come up that Python should have private or protected methods on its classes. This typically comes from someone that is used to programming in Java or C++ or the like, who misses them and insists they are important for any programming language. But python does not have them and never will. Private methods are a solution to a problem that works in statically typed languages. It is not the solution for a highly dynamic language like Python.

tl;dr Python has a dynamic way of fixing this problem, just scroll down to the end to see the code.

But let us look at the problem starting with an example class. Lets do something non trivial, that might actually be used in the real world: a class that handles colour manipulation, maybe because I am building a colour picker, or doing some else cool with colours. So my code might start out like this.

class Colour(object): """A colour wrangler class""" def __init__(self, hexcode="ff0000"): self.hexcode = hexcode

I would then go add add a pile of other methods that will be needed, but let us focus on this. The class has this hexcode attribute, that anyone can now access. So I build the rest of my project around this, and access the hexcode in a lot of places in my code, and also others start doing it in theirs as well.

But then I hit a problem, storing the colour as a hex string is both clumsy, since I have to keep getting out the RGB components any time I want to manipulate it, and also when trying to convert to other colour spaces and back I get rounding errors. I now want to store it as three floats. So I update my code.

class Colour(object): """A colour wrangler class""" def __init__(self, hexcode="ff0000"): self.r = float(int(hexcode[0:2], 16)) self.g = float(int(hexcode[2:4], 16)) self.b = float(int(hexcode[4:6], 16))

Now everyone else's code that depended on the hexcode attribute is broken. At this point the the Java/C++ people would say I told you so, if only you had static methods, you could have had getter and setter methods, and kept the attribute private. But that is never going to happen in Python. Python is too dynamic to check this at compile time, and run time checking would be far to expensive. But python does have an elegant solution to this. It is the build in property function. This is how I would now fix my code using it.

class Colour(object): """A colour wrangler class""" def __init__(self, hexcode="ff0000"): self.hexcode = hexcode @property def hexcode(self): """Return the colour as a hex string""" return ("{:02x}" * 3).format(int(self.r), int(self.g), int(self.b)) @hexcode.setter def hexcode(self, hexcode): self.r = float(int(hexcode[0:2], 16)) self.g = float(int(hexcode[2:4], 16)) self.b = float(int(hexcode[4:6], 16))

I can now get and set the hexcode attribute as before, and it will all get transparently handled. I have even used it as a setter in my __init__ method, so I don't have to repeat the conversion.

Note that @property is put first, before the function that acts as the getter, and then @hexcode.setter not @property.setter is used to define the setter. You can't change the order and expect it to still work. Also note that the two functions have the same name. And the doc string from the getter will be given if you do help(Colour.hexcode).

There is also an alternative way of doing the same thing, this time not using property as a decorator.

class Colour(object): """A colour wrangler class""" def __init__(self, hexcode="ff0000"): self.hexcode = hexcode def gethexcode(self): return ("{:02x}" * 3).format(int(self.r), int(self.g), int(self.b)) def sethexcode(self, hexcode): self.r = float(int(hexcode[0:2], 16)) self.g = float(int(hexcode[2:4], 16)) self.b = float(int(hexcode[4:6], 16)) hexcode = property(gethexcode, sethexcode, doc="Return the colour as a hex string")

This gives the same result, except it also exposes the two functions as an alternative way of getting/setting the value. Also for completeness, I guess I should mention there is a deleter as well, which can be passed as the third argument to property, or used as @hexcode.deleter, but that seems less useful to me.

I would say I kind of prefer the python way of doing it. It means I only have to define the getter and setter functions after the fact of an API change, not as some pre-emptive move just in case it changes later. Also it looks cleaner to me to access attributes then call methods.

This can also be used to create read only attributes by leaving out the setter (you will get an AttributeError) or to add some validation in around an attribute to prevent some invalid values be added in. I could in this case make the self.r etc. into attributes as well to make sure they are in the correct range of 0 to 255.

This however is not going stop people messing with variables that you wanted to be protected (indicated by putting an underscore before it). But you told them they are playing with fire, if they get burned, that can't be helped. So this solution of python does not cover all use cases that private and protected methods are supposed to, but it covers enough that I think the need for them is not great in Python. Instead Python has its own and very powerful and flexible solution.

If you found this interesting you might want to