How does the @property decorator work in Python?

后端 未结 13 2130
闹比i
闹比i 2020-11-21 04:49

I would like to understand how the built-in function property works. What confuses me is that property can also be used as a decorator, but it only

13条回答
  •  忘掉有多难
    2020-11-21 05:48

    Let's start with Python decorators.

    A Python decorator is a function that helps to add some additional functionalities to an already defined function.

    In Python, everything is an object. Functions in Python are first-class objects which means that they can be referenced by a variable, added in the lists, passed as arguments to another function etc.

    Consider the following code snippet.

    def decorator_func(fun):
        def wrapper_func():
            print("Wrapper function started")
            fun()
            print("Given function decorated")
            # Wrapper function add something to the passed function and decorator 
            # returns the wrapper function
        return wrapper_func
    
    def say_bye():
        print("bye!!")
    
    say_bye = decorator_func(say_bye)
    say_bye()
    
    # Output:
    #  Wrapper function started
    #  bye
    #  Given function decorated
    

    Here, we can say that decorator function modified our say_hello function and added some extra lines of code in it.

    Python syntax for decorator

    def decorator_func(fun):
        def wrapper_func():
            print("Wrapper function started")
            fun()
            print("Given function decorated")
            # Wrapper function add something to the passed function and decorator 
            # returns the wrapper function
        return wrapper_func
    
    @decorator_func
    def say_bye():
        print("bye!!")
    
    say_bye()
    

    Let's Concluded everything than with a case scenario, but before that let's talk about some oops priniciples.

    Getters and setters are used in many object oriented programming languages to ensure the principle of data encapsulation(is seen as the bundling of data with the methods that operate on these data.)

    These methods are of course the getter for retrieving the data and the setter for changing the data.

    According to this principle, the attributes of a class are made private to hide and protect them from other code.

    Yup, @property is basically a pythonic way to use getters and setters.

    Python has a great concept called property which makes the life of an object-oriented programmer much simpler.

    Let us assume that you decide to make a class that could store the temperature in degree Celsius.

    class Celsius:
    def __init__(self, temperature = 0):
        self.set_temperature(temperature)
    
    def to_fahrenheit(self):
        return (self.get_temperature() * 1.8) + 32
    
    def get_temperature(self):
        return self._temperature
    
    def set_temperature(self, value):
        if value < -273:
            raise ValueError("Temperature below -273 is not possible")
        self._temperature = value
    

    Refactored Code, Here is how we could have achieved it with property.

    In Python, property() is a built-in function that creates and returns a property object.

    A property object has three methods, getter(), setter(), and delete().

    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("Getting value")
        return self.temperature
    
    def set_temperature(self, value):
        if value < -273:
            raise ValueError("Temperature below -273 is not possible")
        print("Setting value")
        self.temperature = value
    
    temperature = property(get_temperature,set_temperature)
    

    Here,

    temperature = property(get_temperature,set_temperature)
    

    could have been broken down as,

    # make empty property
    temperature = property()
    # assign fget
    temperature = temperature.getter(get_temperature)
    # assign fset
    temperature = temperature.setter(set_temperature)
    

    Point To Note:

    • get_temperature remains a property instead of a method.

    Now you can access the value of temperature by writing.

    C = Celsius()
    C.temperature
    # instead of writing C.get_temperature()
    

    We can further go on and not define names get_temperature and set_temperature as they are unnecessary and pollute the class namespace.

    The pythonic way to deal with the above problem is to use @property.

    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("Getting value")
            return self.temperature
    
        @temperature.setter
        def temperature(self, value):
            if value < -273:
                raise ValueError("Temperature below -273 is not possible")
            print("Setting value")
            self.temperature = value
    

    Points to Note -

    1. A method which is used for getting a value is decorated with "@property".
    2. The method which has to function as the setter is decorated with "@temperature.setter", If the function had been called "x", we would have to decorate it with "@x.setter".
    3. We wrote "two" methods with the same name and a different number of parameters "def temperature(self)" and "def temperature(self,x)".

    As you can see, the code is definitely less elegant.

    Now,let's talk about one real-life practical scenerio.

    Let's say you have designed a class as follows:

    class OurClass:
    
        def __init__(self, a):
            self.x = a
    
    
    y = OurClass(10)
    print(y.x)
    

    Now, let's further assume that our class got popular among clients and they started using it in their programs, They did all kinds of assignments to the object.

    And One fateful day, a trusted client came to us and suggested that "x" has to be a value between 0 and 1000, this is really a horrible scenario!

    Due to properties it's easy: We create a property version of "x".

    class OurClass:
    
        def __init__(self,x):
            self.x = x
    
        @property
        def x(self):
            return self.__x
    
        @x.setter
        def x(self, x):
            if x < 0:
                self.__x = 0
            elif x > 1000:
                self.__x = 1000
            else:
                self.__x = x
    

    This is great, isn't it: You can start with the simplest implementation imaginable, and you are free to later migrate to a property version without having to change the interface! So properties are not just a replacement for getters and setter!

    You can check this Implementation here

提交回复
热议问题