Python @property: як ним користуватися і чому? - Програміз

У цьому підручнику ви дізнаєтесь про декоратор 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').

Використання геттерів та сетерів

Припустимо, ми хочемо розширити зручність використання класу Цельсія, визначеного вище. Ми знаємо, що температура будь-якого об’єкта не може сягати нижче -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 = valв 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) до атрибутів членів acces ( 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 неможлива

Як бачимо, будь-який код, який отримує значення, temperatureбуде автоматично викликати get_temperature()замість пошуку словника (__dict__). Подібним чином, будь-який код, якому присвоєно значення, temperatureбуде автоматично викликати set_temperature().

Вище ми можемо бачити, як set_temperature()це називалося, навіть коли ми створювали об’єкт.

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

Чи можете ви здогадатися, чому?

Причина полягає в тому, що при створенні об'єкта __init__()метод викликається. Цей метод має лінію self.temperature = temperature. Цей вираз автоматично викликає set_temperature().

Подібним чином, будь-який доступ, як-от c.temperatureавтоматичні дзвінки get_temperature(). Це те, що робить властивість. Ось ще кілька прикладів.

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

Використовуючи property, ми можемо побачити, що в реалізації обмеження значення не потрібно ніяких змін. Таким чином, наша реалізація є зворотно сумісною.

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 Decorators, можуть усвідомити, що зазначена конструкція може бути реалізована як декоратори.

Ми можемо навіть не визначити імена 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 Встановлення значення… Відстеження (останній дзвінок останній): Файл «», рядок 29, у файлі «», рядок 4, у __init__ Файл «», рядок 18, при температурі ValueError: Температура нижче -273 неможлива

Вищезазначена реалізація проста і ефективна. Це рекомендований спосіб використання property.

Цікаві статті...