Python @Property


In this article, you will learn about Python @property. You will also learn about getters and setters and also learn about the Pythonic way of using them.

What is a Python attribute?
Why do we need Python property?
Using getters and setters
Python property() function
Python property decorator

python @property

Okay, before learning about Python @property, you must be familiar of some basic yet must know concepts of attributes in Python.

What is a Python attribute?


Python attributes are simply instance variables.

If we dig deeper into it, then you will know that you can actually control attributes. Attributes are simply the names used after objects as a reference to a function or a variable.

For example, object.foo. Here foo is the attribute.

Let’s take an example to understand.

>>> class Area:
      def __init__(self, length):
        self.length = length
  
>>> obj = Area(5)
>>> obj.length
5

Here length is the attribute.

So what’s actually happening here is that when we access an object like obj.length, we are getting back the value stored in a dict on the object.

Whenever we access an attribute Python first search that in objects __dict__() dictionary. When we say obj.length, it’s default behavior is effectively obj.__dict__('length').

>>> obj.__dict__
{'length':5}

Now that we know about attributes, let’s jump on to the main topic: Python properties.

Let’s start with the need of Python property in real time problems.

Why do we need Python property?


Let’s explain the exact need of Python property with an example to convert currency (currency_y = currency_x  * 28).

class CurrencyConverter:
  
  def __init__(self, currency_x = 1):
    self.currency_x = currency_x

  def currency_converter(self):
    currency_y = currency_x * 28
    return (currency_y)

Now we can make objects of this class to manipulate the attribute currency_x.

>>> #Create an object
>>> c = CurrencyConverter()

>>> #set the value of attribute currency_x
>>> c.currency_x = 2

>>> #convert currency_x to currency_y
>>> c.currency_converter()
56

So far everything works great.

Now let’s say people like this converter and start using our currency converter with their programs and software by inheriting this class or using it however to develop further modules.

At some point in time, let’s say one of the users comes and suggests that this program should not allow converting the negative value of the currency.

Sounds pretty much fair as the value of the currency cannot be less than 0 and why should we even allow this conversion?

One might argue about encapsulation of data as we are giving direct access to the class variables. We should have used private variables but note that there are no private variables in Python technically.

Yes, we can make them private explicitly by using a leading underscore(_) with variables like self._currency_x but that doesn’t even prevent a programmer from accessing it beyond the class and manipulating it.

The bigger concern to address at this point of time is to restrict users from converting negative values of currencies. This problem can be addressed by using getters and setters interface in our program.

Using getters and setters in Python


Here is the way to implement getters and setters in Python.

class CurrencyConverter:
 
 def __init__(self, currency_x = 1):
   self.set_currency_x(currency_x)

 def currency_converter(self):
   currency_y = self.get_currency_x() * 28
   return (currency_y)

 #implementing new getter method
 def get_currency_x(self):
   return (self.currency_x)

 #implementing new setter method
 def set_currency_x(self, currency_value):
   if currency_value < 0:
     raise ValueError("Currency can't be negative")
   self.currency_x = currency_value

Now we can test our program using the interpreter.

>>> #test the negative value
>>> CurrencyConverter(-2)
Traceback (most recent call last):
...........
raise ValueError("Currency can't be negative")
ValueError: Currency can't be negative

>>> #Creating new object
>>> c = CurrencyConverter(2)

>>> #get the value of currency_x using getter method
>>> c.get_currency_x()
2

>>> #set the new value of currency_x using setter method
>>> c.set_currency_x(3)

>>> #convert currency_x to currency_y
>>> c.currency_converter()
56
>>> #try setting negative value
>>> c.set_currency_x(-4)
Traceback (most recent call last):
...........
raise ValueError("Currency can't be negative")
ValueError: Currency can't be negative

That worked perfectly fine and with that update, we successfully imposed the restriction on converting the negative value of the currency.

Now comes the bigger problem.

This update may is easy to fix but we will have to replace c.currency_x = 2 with c.set_currency_x (2). This seems so easy for a small program but when we have hundreds of lines of code using this class, changing code becomes so hectic.

This isn’t the optimal solution for such problems as the program by no means supports the backward compatibility. This is where Python property works like charm.

Now let’s implement Pythonic way to address such problems using Python property.

 

Python Property() Function


In earlier versions of Python (<2.6), property decorators (which we will learn in a bit) were not introduced and property() function was used.

The property() function is used to provide methods to control the access of attributes. Using property() function we can bind the getter, setter and deleter function altogether or individually with an attribute name.

To create a property, we define the instance variable and one or more method functions.

Syntax

property(fget, fset, fdel, doc)

Where,

  • fget is a getter function and must be defined if we need to read the attribute else AttributeError is raised.
  • fset is a setter function and must be defined if we want to set or write that attribute else AttributeError is raised.
  • fdel is a deleter method to delete the attribute.
  • doc is the documentation string that describes the attribute.

Here is the simple implementation of property function to address the problem we faced earlier.

class CurrencyConverter:
 
 def __init__(self, currency_x = 1):
   self.currency_x = currency_x

 def currency_converter(self):
   currency_y = self.get_currency_x() * 28
   return (currency_y)

 #implementing new getter method
 def get_currency_x(self):
   print ('inside getter method')
   return (self._currency_x)

 #implementing new setter method
 def set_currency_x(self, currency_value):
   if currency_value < 0:
     raise ValueError("Currency can't be negative")
   print ('inside setter method')
   self._currency_x = currency_value

 currency_x = property(get_currency_x, set_currency_x)

That’s the implementation of property function in the problem we faced previously. Here the property attaches the defined getter and setter methods to the variable currency_x. Now, whenever a value is assigned to currency_x, set_currency_x() method will be automatically invoked and we won’t need to change any remaining code.

We have attached print statements inside getter and setter methods to make sure that the program flow is reaching to those functions. Here is the demonstration in Python shell.

>>> #Creating new object
>>> c = CurrencyConverter(2)
inside setter method

>>> #get the value of currency_x using getter method
>>> c.get_currency_x()
inside getter method
2

Note: The value of the currency is stored in private variable _currrency_x and attribute currency_x is the property object providing the interface to the private variable.

Python @property decorator


Starting with the Python version later than 2.6, this property function can be implemented as Python decorator. The decorator method is used as getter method.

Here is how above program can be implemented using Python @property decorator.

class CurrencyConverter:
 
 def __init__(self, currency_x = 1):
   self.currency_x = currency_x

 def currency_converter(self):
   currency_y = self.get_currency_x() * 28
   return (currency_y)

 @property
 def get_currency_x(self):
   print ('inside getter method')
   return (self._currency_x)

 @currency_x.setter
 def set_currency_x(self, currency_value):
   if currency_value < 0:
     raise ValueError("Currency can't be negative")
   print ('inside setter method')
   self._currency_x = currency_value