English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Python Basic Tutorial

Python Flow Control

Python Functions

Python Data Types

Python File Operations

Python Objects and Classes

Python Date and Time

Advanced Knowledge of Python

Python Reference Manual

Python @property

Python has a great concept called attribute, which makes the life of object-oriented programmers easier.

Before defining and understanding what @property is, let's understand why it is needed first.

an instance starts

Suppose you decideCreate aA class that stores temperature in Celsius. It will also implement a method to convert temperature to Fahrenheit. One method is as follows.

class Celsius:
    def __init__(self, temperature = 0):
        self.temperature = temperature
    def to_fahrenheit(self):
        return (self.temperature * 1.8) + 32

We can create objects from this class and manipulate the attribute temperature as needed. Try these in the Python shell.

>>> # Create a new object
>>> man = Celsius()
>>> # Set temperature
>>> man.temperature = 37
>>> # Get temperature
>>> man.temperature
37
>>> # Get Fahrenheit
>>> man.to_fahrenheit()
98.60000000000001

When converting to Fahrenheit, the extra decimal places are due to floating-point operation errors (when trying to1.1 + 2.2).

As shown above, every time we allocate or retrieve any object attribute (such astemperature) Python will search the __dict__ dictionary of the object when it searches for (self.get_temperature()).

>>> man.__dict__
{'temperature': 37}

Therefore, man.temperature internally becomes man.__dict__['temperature'].

Now, let's further assume that our course is very popular among customers, and they are starting to use it in their programs. They have made various allocations to the objects.

One day, a trusted customer came to us and suggested that the temperature should not be below-273Celsius (students in the field of thermodynamics might say it is actually-273.15Celsius, also known as absolute zero. He further requires us to implement this value constraint. As a company that pursues customer satisfaction, we are delighted to hear this suggestion and have released1.01Version (an upgrade to the existing class).

Using getter and setter

An obvious method to address the above constraints is to hide the attribute temperature (set it as private) and define new getter and setter interfaces to operate on it. This can be done as follows.

class Celsius:
    def __init__(self, temperature = 0):
        self.set_temperature(temperature)
    def to_fahrenheit(self):
        return (self.get_temperature()) * 1.8) + 32
    # new update
    def get_temperature(self):
        return self._temperature
    def set_temperature(self, value):
        if value < -273:
            raise ValueError("-273degrees is impossible")
        self._temperature = value

We can see above that get_temperature() and set_temperature() have defined new methods, and that _temperature has replaced temperature. The underscore (_) at the beginning indicates a private variable in Python.

>>> c = Celsius(-277)
Traceback (most recent call last):
...
ValueError: Temperature below -273 is not possible
>>> c = Celsius(37)
>>> c.get_temperature()
37
>>> c.set_temperature(10)
>>> c.set_temperature(-300)
Traceback (most recent call last):
...
ValueError: Temperature below -273 is not possible

This update has successfully implemented the new restrictions. We are no longer allowed to set the temperature below-273.

Please note that there are no private variables in Python. Just follow some conventions, and the language itself has no restrictions.

>>> c._temperature = -300
>>> c.get_temperature()
-300

But this is not a big problem. The biggest problem with the above update is that all clients who have implemented the previous class in the program must change their code from obj.temperature to obj.get_temperature(), and change all assignments (such as obj.temperature = val modified to obj.set_temperature(val)).

This refactoring will bring trouble to customers with hundreds of thousands of lines of code.

In summary, our new update is not backward compatible. This is where @property comes into play.

The power of @property

Python handles the above issue using the property. We can implement it in this way.

class Celsius:
    def __init__(self, temperature = 0):
        self.temperature = temperature
    def to_fahrenheit(self):
        return (self.temperature * 1.8) + 32
    def get_temperature(self):
        print("The obtained value")
        return self._temperature
    def set_temperature(self, value):
        if value < -273:
            raise ValueError("Below zero"273degrees is impossible")
        print("Set value")
        self._temperature = value
    temperature = property(get_temperature, set_temperature)

And once run, the following code is issued in the shell.

>>> c = Celsius()

We have added the print() function in get temperature() and set temperature() to clearly observe their execution.

The last line of the code creates a property object called temperature. In short, the property attaches some code (get_temperature and set_temperature) to member attribute access (temperature).

Any code that retrieves the temperature value will automatically call get_temperature() instead of dictionary (__dict__) lookup. Similarly, any code that assigns a value to the temperature will automatically call set_temperature(). This is a very cool feature in Python.

We can see from above that set_temperature() is called even when the object is created.

Can you guess why?

The reason is that when the object is created, the __init__() method will be called. The line of this method is self.temperature = temperature. This assignment is automatically called set_temperature().

>>> c.temperature
Getting value
0

Similarly, any access like c.temperature will automatically call get_temperature(). This is the role of properties. Here are some examples.

>>> c.temperature = 37
Setting value
>>> c.to_fahrenheit()
Getting value
98.60000000000001

By using properties, we can see that we have modified the class and implemented value constraints without changing the client code. Therefore, our implementation is backward compatible.

Finally, please note that the actual temperature value is stored in the private variable _temperature. The temperature attribute is a property object that provides an interface to this private variable.

Deepen your understanding of property

In Python, property() is a built-in function used to create and return a property object. The signature of this function is

property(fget=None, fset=None, fdel=None, doc=None)

Among them, fget is the function to get the attribute value, fset is the function to set the attribute value, fdel is the function to delete the attribute, and doc is a string (such as comments). From the implementation, it can be seen that these function parameters are optional. Therefore, it can be simply created in the following way.

>>> property()
<property object at 0x0000000003239B38>

The property object has three methods, getter(), setter(), and deleter(), which are used to specify fget, fset, and fdel later. This means that

temperature = property(get_temperature, set_temperature)

can also be decomposed into

# Create an empty property
temperature = property()
# Set fget
temperature = temperature.getter(get_temperature)
# Set fset
temperature = temperature.setter(set_temperature)

These two pieces of code are equivalent.

Familiar withDecorators in PythonProgrammers can recognize that the above construction can be implemented as a decorator.

We can go a step further and not define names get_temperature, set_temperature, as they are unnecessary and may affect the class namespace. To do this, we reuse the name temperature when defining getter and setter functions. This is possible.

class Celsius:
    def __init__(self, temperature = 0):
        self._temperature = temperature
    def to_fahrenheit(self):
        return (self.temperature * 1.8) + 32
    @property
    def temperature(self):
        print("Get value")
        return self._temperature
    @temperature.setter
    def temperature(self, value):
        if value < -273:
            raise ValueError("Below zero"273degrees is impossible")
        print("Set value")
        self._temperature = value

The above implementation is a simple and recommended method for creating properties. When looking for properties in Python, you are likely to encounter these types of constructions.

Alright, that's it for today.