DEV Community

Cover image for Constructing In Private @property • Python
Gabriel Ruiz
Gabriel Ruiz

Posted on

Constructing In Private @property • Python

So you’ve mastered the basics of Python classes, and now you are trying to do something more intermediate. Maybe you come from a purely Object-Oriented Programming background like Java and have created some getters and setters on your class only to be told by your coworker that this isn't the "pythonic" way, whatever that means. Or you just realized that the completed project you've been handed needs a modification to a class attribute, but adding getter or setter method would mean changes to hundreds of lines of code and reworking the unit tests.

In this article, I will walk you through the process using the decorator @property to modify the getter, setter, and delete functions of an attribute in a python class. For this example, let’s assume that you have already declared this attribute in the init method of your class:

   class Classy:
       def __init__(self, var):
           self.var = var

Let's use the @property decorator to first create the getter. The getter function serves both as a getter and to define the name of the attribute for the decorator.

class Classy:
    def __init__(self, var):
        self.var = var

    @propierty
    def var(self):
        return self._var

For this function to work, the name of the function must match the name of the attribute on our init method. However, we return self._var. Why get a different attribute? After all, self._var is not the same as self.var. Let's break it down Barney style. If we try to return self.var, first, the program calls the getter of self.var. Then the getter calls the getter of self.var, which in turn calls the getter of self.var. Then the program finds itself in recursion hell.

"But if I use self._var, my IDE yells at me," you may say.

"Where is this self._var you speak of? I've never heard of this self._var chap!" the helpful IDE points out in a posh British accent.

Let's help quiet your IDE by creating a setter for the attribute. Now that we created and named our getter function of the @property, we can use the pattern @attribute_name.function to define the other functions for this attribute.

class Classy:
    def __init__(self, var):
        self.var = var

    @propierty
    def var(self):
        return self._var

    @var.setter
    def var(self, value):
        self._var = value

Once again, we match the name of the function with the name of the attribute in our init method. Then we save the value into a "hidden/private" variable we have named self._var, thus avoiding the recursive armageddon and quieting your py IDE's charming warning of impending doom.

Likewise, we may add a deleter function following the same system.

class Classy:
    def __init__(self, var):
        self.var = var

    @propierty
    def var(self):
        return self._var

    @var.setter
    def var(self, value):
        self._var = value

    @var.deleter
    def var(self):
        self._var = None

Using the @property decorator is a simple way to extend the getter/setter functionality of your python class. It makes your code more resilient and streamlines the process of attribute modification and retrieval. And yes, it makes your code more “pythonic.” So, in conclusion, here are the most important properties to remember when using a @property decorator:

  • The getter will serve both to retrieve and name the attribute in question. There is no such thing as a @var.getter.
  • The name of the function(s) must match the name of the attribute it modifies.
  • To avoid the dreaded "recursion limit error," the attribute's value must be stored in a new "hidden/private" variable.

Latest comments (3)

Collapse
 
waylonwalker profile image
Waylon Walker • Edited

I often find myself wrapping @propterty with try:... except AttributeError: I had to dig up some code to remind myself why I would need to do this. It appears to me that I like to utilize @property methods as a persistent store instead of something like LRU cache.

Here is an example of a method inside of a click application that gets a list of available conda environments.

    @property
    def conda_envs(self):
        try:
            return self._conda_envs
        except AttributeError:
            # this is a really slow function
            # we need the data several times
            # but It never needs updated during the lifecycle of the cli
            self._conda_envs = self.list_conda_environment_names()
Collapse
 
waylonwalker profile image
Waylon Walker

I had no idea there was a deleter function. Thanks for sharing!

Collapse
 
waylonwalker profile image
Waylon Walker

📢 @islandwonderer , Anton found a typo here.

I completely overlooked it myself.