The property function: why Python does not have Private Methods

Home » Programming » Python » The property function: why Python does not have Private Methods

Now and then, I see it said that Python should have private or protected methods on its classes. This typically comes from someone used to programming in Java or C++ or the like, who misses them and insists they are essential for any programming language. But python does not have them and never will. Private methods are a solution 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. Let us do something non-trivial that might be used in the real world: a class that handles colour manipulation, maybe because I am building a colour picker or doing something cool with colours. So my code might start like this.

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

I would then 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 many places in my code, and others start doing it in theirs as well.

But then I hit a problem, storing the colour as a hex string is 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 again, I get rounding errors. So 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 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 too expensive. But python does have an elegant solution to this. It is the built-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 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 prefer the python way of doing it. First, 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 than call methods.

You can also use this to create read-only attributes by leaving out the setter (you will get an AttributeError) or adding some validation 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 ensure they are in the correct range of 0 to 255.

This, however, will not stop people from messing with variables 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 python solution 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 significant in Python. Instead, Python has its own very powerful and flexible solution.

Python, ,

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>