Декоратор @property в Python

 6064

Python надає вбудований декоратор @property, який значно спрощує використання геттерів та сеттерів у об’єктно-орієнтованому програмуванні. Перш ніж ми розглянемо, що є декоратором @property, давайте спочатку розберемося, навіщо він взагалі потрібен.

Клас без геттерів та сеттерів

Припустимо, що ми вирішили створити клас, який зберігає температуру в градусах за Цельсієм, та метод для конвертації температури із градусів за Цельсієм у градуси за Фаренгейтом. Це можна зробити наступним чином:

Ми можемо створювати об’єкти на основі цього класу та маніпулювати атрибутом temperature на свій розсуд:

Результат:

37
98.60000000000001

Примітка: Тут зайві десяткові знаки при перетворенні в градуси за Фаренгейтом викликані помилкою арифметики з числами типу з плаваючою крапкою.

Таким чином, кожного разу, коли ми присвоюємо або отримуємо будь-який атрибут об’єкта, наприклад, temperature, як показано вище, Python шукає його у вбудованому атрибуті словника __dict__ як:

Тому human.temperature внутрішньо стає human.__dict__['temperature'].

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

Припустимо, що ми хочемо розширити можливості використання класу Celsius, визначеного вище. Ми знаємо, що температура будь-якого об’єкта не може бути нижчою від -273.15 градусів за Цельсієм. Оновимо наш код, щоб реалізувати це обмеження.

Одним із рішень є приховування атрибута temperature (зробимо його приватним) та визначення нових методів (геттера та сеттера) для роботи з ним.

Примітка: Геттер — це функція, яка повертає значення змінних членів (атрибутів) класу. Сеттер — це функція, яка дозволяє надавати значення змінним-членам (атрибутам) класу.

Це можна зробити наступним чином:

З’явилися два нові методи: get_temperature() та set_temperature().

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

Тепер додамо цю реалізацію до нашої програми:

Результат:

37
98.60000000000001
Traceback (most recent call last):
File "<string>", line 30, in <module>
File "<string>", line 16, in set_temperature
ValueError: Temperature below -273.15 is not possible.

У цьому оновленні успішно реалізовано нове обмеження. Тепер ми не зможемо встановлювати температуру нижче -273.15 градусів за Цельсієм.

Примітка: Насправді приватних змінних в Python немає. Просто існують норми, яких необхідно дотримуватися. Сам Python не накладає жодних обмежень.

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

Такий рефакторинг може викликати проблеми під час роботи з сотнями тисяч рядків коду. Загалом виходить, що наше оновлення не має зворотної сумісності. Саме тут на допомогу приходить декоратор @property.

Клас property

Пітонічний спосіб вирішення цієї проблеми полягає у використанні класу property. Наприклад:

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

Останній рядок коду створює об’єкт temperature класу property. Простіше кажучи, property прикріплює певний код (get_temperature та set_temperature) до атрибуту-члена, до якого здійснюється доступ (temperature).

Повна програма:

Результат:

Setting value...
Getting value...
37
Getting value...
98.60000000000001
Setting value...
Traceback (most recent call last):
File "<string>", line 31, in <module>
File "<string>", line 18, in set_temperature
ValueError: Temperature below -273 is not possible

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

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

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

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

Аналогічно, будь-який виклик типу c.temperature автоматично викликає get_temperature(). Ось що робить клас property. Використовуючи property, жодних змін у реалізації обмеження не потрібно. Таким чином, наша реалізація є зворотно сумісною.

Примітка: Фактичне значення температури зберігається у приватній змінній _temperature. Атрибут temperature — це об’єкт класу property, який забезпечує інтерфейс цієї приватної змінної.

Декоратор @property

В Python функція property() є вбудованою функцією, яка створює і повертає об’єкт property. Синтаксис цієї функції наступний:

Тут:

   fget — це функція для отримання значення атрибута;

   fset — це функція для встановлення значення атрибута;

   fdel — це функція для видалення атрибута;

   doc — це рядок (як коментар).

Як видно з реалізації, ці аргументи функції не є обов’язковими.

Об’єкт property має три методи: getter(), setter() і deleter(), що дозволяють вказати fget, fset та fdel. Це означає, що наступний рядок:

Може бути розбитий на:

Ці два фрагменти коду рівнозначні.

Вищенаведена конструкція може бути реалізована у вигляді декораторів. Ми можемо навіть не визначати ідентифікатори get_temperature та set_temperature, оскільки вони не потрібні.

Для цього ми повторно використовуємо ідентифікатор temperature при визначенні наших функцій геттера та сеттера. Наприклад, реалізація за допомогою декоратора @property:

Результат:

Setting value...
Getting value...
37
Getting value...
98.60000000000001
Setting value...
Traceback (most recent call last):
File "", line 29, in
File "", line 4, in __init__
File "", line 18, in temperature
ValueError: Temperature below -273 is not possible

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

Оцінити статтю:

1 Зірка2 Зірки3 Зірки4 Зірки5 Зірок (13 оцінок, середня: 5,00 з 5)
Завантаження...

Залишити відповідь

Ваш E-mail не буде опублікований. Обов'язкові поля відмічені *