Python @property: как его использовать и зачем? - Programiz

В этом руководстве вы узнаете о декораторе Python @property; питонический способ использования геттеров и сеттеров в объектно-ориентированном программировании.

Программирование на Python предоставляет нам встроенный @propertyдекоратор, который значительно упрощает использование геттеров и сеттеров в объектно-ориентированном программировании.

Прежде чем вдаваться в подробности, что такое @propertyдекоратор, давайте сначала разберемся, зачем он вообще может понадобиться.

Класс без геттеров и сеттеров

Допустим, мы решили создать класс, который хранит температуру в градусах Цельсия. Также будет реализован метод преобразования температуры в градусы Фаренгейта. Один из способов сделать это:

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

Мы можем создавать объекты из этого класса и манипулировать temperatureатрибутом по своему желанию:

 # Basic method of setting and getting attributes in Python class Celsius: def __init__(self, temperature=0): self.temperature = temperature def to_fahrenheit(self): return (self.temperature * 1.8) + 32 # Create a new object human = Celsius() # Set the temperature human.temperature = 37 # Get the temperature attribute print(human.temperature) # Get the to_fahrenheit method print(human.to_fahrenheit())

Вывод

 37 98.60000000000001

Дополнительные десятичные разряды при преобразовании в градусы Фаренгейта возникают из-за арифметической ошибки с плавающей запятой. Чтобы узнать больше, посетите Python Ошибка арифметики с плавающей запятой.

Всякий раз, когда мы назначаем или извлекаем какой-либо атрибут объекта, temperatureкак показано выше, Python ищет его во встроенном __dict__атрибуте словаря объекта .

 >>> human.__dict__ ('temperature': 37)

Следовательно, man.temperatureвнутренне становится man.__dict__('temperature').

Использование геттеров и сеттеров

Предположим, мы хотим расширить возможности использования класса Celsius, определенного выше. Мы знаем, что температура любого объекта не может опускаться ниже -273,15 градусов Цельсия (Абсолютный ноль в термодинамике).

Давайте обновим наш код, чтобы реализовать это ограничение значения.

Очевидным решением вышеуказанного ограничения будет скрытие атрибута temperature(сделать его частным) и определение новых методов получения и установки для управления им. Это можно сделать следующим образом:

 # Making Getters and Setter methods class Celsius: def __init__(self, temperature=0): self.set_temperature(temperature) def to_fahrenheit(self): return (self.get_temperature() * 1.8) + 32 # getter method def get_temperature(self): return self._temperature # setter method def set_temperature(self, value): if value < -273.15: raise ValueError("Temperature below -273.15 is not possible.") self._temperature = value

Как мы можем видеть, вышеуказанный способ вводит два новых get_temperature()и set_temperature()методов.

Кроме того, temperatureбыл заменен на _temperature. Нижнее подчеркивание _в начале используется для обозначения частных переменных в Python.

Теперь воспользуемся этой реализацией:

 # Making Getters and Setter methods class Celsius: def __init__(self, temperature=0): self.set_temperature(temperature) def to_fahrenheit(self): return (self.get_temperature() * 1.8) + 32 # getter method def get_temperature(self): return self._temperature # setter method def set_temperature(self, value): if value < -273.15: raise ValueError("Temperature below -273.15 is not possible.") self._temperature = value # Create a new object, set_temperature() internally called by __init__ human = Celsius(37) # Get the temperature attribute via a getter print(human.get_temperature()) # Get the to_fahrenheit method, get_temperature() called by the method itself print(human.to_fahrenheit()) # new constraint implementation human.set_temperature(-300) # Get the to_fahreheit method print(human.to_fahrenheit())

Вывод

 37 98.60000000000001 Отслеживание (последний вызов последним): файл "", строка 30, в файле "", строка 16, в set_temperature ValueError: Температура ниже -273,15 невозможна.

Это обновление успешно реализовало новое ограничение. Нам больше не разрешается устанавливать температуру ниже -273,15 градусов по Цельсию.

Примечание : частных переменных в Python фактически не существует. Есть просто нормы, которым нужно следовать. Сам язык не накладывает никаких ограничений.

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

Однако более серьезная проблема с вышеупомянутым обновлением заключается в том, что все программы, реализовавшие наш предыдущий класс, должны изменить свой код с obj.temperatureна obj.get_temperature()и все выражения вроде obj.temperature = valto obj.set_temperature(val).

Этот рефакторинг может вызвать проблемы при работе с сотнями тысяч строк кода.

В общем, наше новое обновление не имело обратной совместимости. Вот где @propertyприходит на помощь.

Класс собственности

Питонический способ справиться с вышеуказанной проблемой - использовать propertyкласс. Вот как мы можем обновить наш код:

 # using property class class Celsius: def __init__(self, temperature=0): self.temperature = temperature def to_fahrenheit(self): return (self.temperature * 1.8) + 32 # getter def get_temperature(self): print("Getting value… ") return self._temperature # setter def set_temperature(self, value): print("Setting value… ") if value < -273.15: raise ValueError("Temperature below -273.15 is not possible") self._temperature = value # creating a property object temperature = property(get_temperature, set_temperature)

Мы добавили print()функцию внутри get_temperature()и set_temperature()четко видели, что они выполняются.

Последняя строка кода создает объект свойства temperature. Проще говоря, свойство присоединяет некоторый код ( get_temperatureи set_temperature) к атрибуту члена accesses ( temperature).

Воспользуемся этим кодом обновления:

 # using property class class Celsius: def __init__(self, temperature=0): self.temperature = temperature def to_fahrenheit(self): return (self.temperature * 1.8) + 32 # getter def get_temperature(self): print("Getting value… ") return self._temperature # setter def set_temperature(self, value): print("Setting value… ") if value < -273.15: raise ValueError("Temperature below -273.15 is not possible") self._temperature = value # creating a property object temperature = property(get_temperature, set_temperature) human = Celsius(37) print(human.temperature) print(human.to_fahrenheit()) human.temperature = -300

Вывод

 Значение настройки… Получение значения… 37 Получение значения… 98.60000000000001 Значение настройки… Отслеживание (последний вызов последним): файл "", строка 31, в файле "", строка 18, в set_temperature ValueError: Температура ниже -273 невозможна

As we can see, any code that retrieves the value of temperature will automatically call get_temperature() instead of a dictionary (__dict__) look-up. Similarly, any code that assigns a value to temperature will automatically call set_temperature().

We can even see above that set_temperature() was called even when we created an object.

 >>> human = Celsius(37) Setting value… 

Can you guess why?

The reason is that when an object is created, the __init__() method gets called. This method has the line self.temperature = temperature. This expression automatically calls set_temperature().

Similarly, any access like c.temperature automatically calls get_temperature(). This is what property does. Here are a few more examples.

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

By using property, we can see that no modification is required in the implementation of the value constraint. Thus, our implementation is backward compatible.

Note: The actual temperature value is stored in the private _temperature variable. The temperature attribute is a property object which provides an interface to this private variable.

The @property Decorator

In Python, property() is a built-in function that creates and returns a property object. The syntax of this function is:

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

where,

  • fget is function to get value of the attribute
  • fset is function to set value of the attribute
  • fdel is function to delete the attribute
  • doc is a string (like a comment)

As seen from the implementation, these function arguments are optional. So, a property object can simply be created as follows.

 >>> property() 

A property object has three methods, getter(), setter(), and deleter() to specify fget, fset and fdel at a later point. This means, the line:

 temperature = property(get_temperature,set_temperature)

can be broken down as:

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

Эти две части кода эквивалентны.

Программисты, знакомые с декораторами Python, могут понять, что указанная выше конструкция может быть реализована как декораторы.

Мы можем даже не определить имена get_temperatureи set_temperatureкак они не нужны и загрязняют пространство имен класса.

Для этого мы повторно используем temperatureимя при определении наших функций получения и установки. Давайте посмотрим, как это реализовать в качестве декоратора:

 # Using @property decorator 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): print("Setting value… ") if value < -273.15: raise ValueError("Temperature below -273 is not possible") self._temperature = value # create an object human = Celsius(37) print(human.temperature) print(human.to_fahrenheit()) coldest_thing = Celsius(-300)

Вывод

 Значение настройки… Получение значения… 37 Получение значения… 98.60000000000001 Значение настройки… Traceback (последний вызов последним): файл "", строка 29, в файле "", строка 4, в __init__ File "", строка 18, в температуре ValueError: Температура ниже -273 невозможна

Вышеупомянутая реализация проста и эффективна. Это рекомендуемый способ использования property.

Интересные статьи...