Декоратор property в Python
| Введение | |
| Предпосылки | |
| Пример | |
| setter | |
| deleter | |
| Полный код примера | |
| Валидация аттрибутов | |
| Похожие статьи |
Введение
Это продолжение статьи
«Классы»
из раздела
«ООП в Python»
.
Для лучшего понимания этого материала пригодятся знания из следующих статей:
Методы
,
Класс методы
,
Статические методы
,
super()
,
Декораторы
,
isinstance()
Партнёром этой статьи будем считать моего хостинг-провайдера beget.com
Предпосылки
Рассмотрим простой класс
class Employee: def __init__(self, first, last): self.first = first self.last = last self.email = first + '.' + last + '@beget.com' def fullname(self): return f'{self.first} {self.last}' emp_1 = Employee("Ivan", "Petrov") print(emp_1.first) print(emp_1.email) print(emp_1.fullname())
python property_deco.py
Ivan Ivan.Petrov@beget.com Ivan Petrov
Изменим только атрибут first у emp_1 и посмотрим как преобразится вывод
class Employee: def __init__(self, first, last): self.first = first self.last = last self.email = first + '.' + last + '@beget.com' def fullname(self): return f'{self.first} {self.last}' emp_1 = Employee("Ivan", "Petrov") # Change the attribute emp_1.first = 'Andrei' print(emp_1.first) print(emp_1.email) print(emp_1.fullname())
python property_deco.py
Andrei Ivan.Petrov@beget.com Andrei Petrov
Как видите, email остался прежним, как-будто работника по-прежнему зовут Иван. Это атрибут был создан вместе с объектом и
больше его никто не трогал.
fullname обновился, так как этот метод каждый раз переиспользует текущие атрибуты.
Что делать если нужно автоматически обновлять email?
Если убрать атрибут email и создать вместо него метод email по аналогии с fullname это заработает, но все, кто пользовался
этим классом должны будут изменить свой код. Это нарушает обратную совместимость.
Обратите внимание на вызов метода - нужно везде добавить ()
class Employee: def __init__(self, first, last): self.first = first self.last = last # self.email = first + "." + last + "@beget.com" def fullname(self): return f"{self.first} {self.last}" def email(self): return f"{self.first}.{self.last}@beget.com" emp_1 = Employee("Ivan", "Petrov") # Change the attribute emp_1.first = "Andrei" print(emp_1.first) print(emp_1.email()) print(emp_1.fullname())
python property_deco.py
Andrei Andrei.Petrov@beget.com Andrei Petrov
Оставить и метод и атрибут с тем же название нельзя - получим ошибку TypeError: 'str' object is not callable
Andrei Traceback (most recent call last): File "/home/andrei/python/property_deco.py", line 21, in <module> print(emp_1.email()) TypeError: 'str' object is not callable
Пример
Если написать метод email() и добавить к нему декоратор @property обратная совместимость не пострадает.
class Employee: def __init__(self, first, last): self.first = first self.last = last def fullname(self): return f"{self.first} {self.last}" @property def email(self): return f"{self.first}.{self.last}@beget.com" emp_1 = Employee("Ivan", "Petrov") # Change the attribute emp_1.first = "Andrei" print(emp_1.first) print(emp_1.email) print(emp_1.fullname())
python property_deco.py
Andrei Andrei.Petrov@beget.com Andrei Petrov
setter
Добавим декоратор @property к обоим методам и попробуем задать fullname
class Employee: def __init__(self, first, last): self.first = first self.last = last @property def fullname(self): return f"{self.first} {self.last}" @property def email(self): return f"{self.first}.{self.last}@beget.com" emp_1 = Employee("Ivan", "Petrov") # Change the attribute emp_1.fullname = "Andrei Olegovich" print(emp_1.first) print(emp_1.email) print(emp_1.fullname)
python property_deco.py
Traceback (most recent call last): File "/home/andrei/python/property_deco.py", line 21, in <module> emp_1.fullname = "Andrei Olegovich" AttributeError: can't set attribute
Избавиться от ошибки поможет декоратор setter
@fullname.setter def fullname(self, name): first, last = name.split(' ') self.first = first self.last = last emp_1 = Employee("Ivan", "Petrov") # Change the attribute emp_1.fullname = "Andrei Olegovich" print(emp_1.first) print(emp_1.email) print(emp_1.fullname)
python property_deco.py
Andrei Andrei.Petrov@beget.com Andrei Petrov
deleter
Удалить атрибут лучше всего с помощью декоратора deleter
@fullname.deleter def fullname(self): print("Delete Name!") self.first = None self.last = None emp_1.fullname = "Andrei Olegovich" print(emp_1.first) print(emp_1.email) print(emp_1.fullname) del emp_1.fullname print(emp_1.fullname)
python property_deco.py
Andrei Andrei.Olegovich@beget.com Andrei Olegovich Delete Name! None None
Полный код примера
class Employee: def __init__(self, first, last): self.first = first self.last = last @property def email(self): return f"{self.first}.{self.last}@beget.com" @property def fullname(self): return f"{self.first} {self.last}" @fullname.setter def fullname(self, name): first, last = name.split(' ') self.first = first self.last = last @fullname.deleter def fullname(self): print("Delete Name!") self.first = None self.last = None emp_1 = Employee("Ivan", "Petrov") # Change the attribute emp_1.fullname = "Andrei Olegovich" print(emp_1.first) print(emp_1.email) print(emp_1.fullname) del emp_1.fullname print(emp_1.fullname)
Валидация аттрибутов с помощью property
Рассмотрим пример, в котором объект создаётся из полученного json. Нужно обработать ситуации, когда какого-то поля нет а также
в некоторых атрибутах нужно пустые строки заменять на None. Если какой-то атрибут задуман как целое число - возвращать надо int,
при это нужно обработать случай когда в int полученное значение не конвертируется.
Первый вариант будет без сеттеров:
from __future__ import annotations class Data: def __init__(self, data: dict) -> None: self.data = data def _existence_validator(self, value: str) -> str | None: # print(f"_existence_validator: {value}") try: result = self.data[value] except KeyError as e: print(f"ERROR: {value} not found in data: {e}") return None else: # print(f"data['{value}'] = {result}") return result def _empty_string_validator(self, value): # print(f"_empty_string_validator: {value}") if value == "": # print(f"value {value} is empty string converting to None") return None else: # print(f"value {value} is not empty string") return value def _int_value_validator(self, value: str, empty_str_to_none=True) -> int | None: # print(f"_int_value_validator: {value}") _existing = self._existence_validator(value) if not _existing: return None if empty_str_to_none: _attr = self._empty_string_validator(_existing) else: _attr = _existing try: # print(f"_attr: {_attr} converting to int") result = int(_attr) return result except ValueError as e: print(f"ERROR: {_attr} is not convertable to int: {e}") return None class DynData(Data): def __init__(self, data: dict) -> None: super().__init__(data) @property def mmsi(self): return self._int_value_validator("mmsi") @property def course(self): return self._int_value_validator("course") @property def name(self) -> str: return self._existence_validator("name") if __name__ == "__main__": d = DynData({"mmsi": "123456789", "course": "23Ab"}) print(f"mmsi: {d.mmsi}") print(f"course: {d.course}") print(f"name: {d.name}")
mmsi: 123456789 ERROR: 23Ab is not convertable to int: invalid literal for int() with base 10: '23Ab' course: None ERROR: name not found in data: 'name' name: None
Родительские производные атрибуты
Рассмотрим
пример
из статьи про метод
super()
Если нужно чтобы производный родительский атрибут обновлялся если его составляющие изменяются в дочернем классе можно сделать это с помощью
@property
Рассмотрим следущий пример
class A: def __init__(self): self.x = 1 self.y = 2 @property def a(self): return self.x + self.y class B(A): def __init__(self): super().__init__() self.x = 4 self.y = 5 var_1 = A() print(var_1.a) var_2 = B() print(var_2.a)
3 9
Если бы a был бы простым атрибутом, он бы не обновил значение и остался бы равным 3.
С помощью @property мы заставили пересчитать a при создании var_2
Автор статьи: Андрей Олегович
| ООП в Python | |
| Классы | |
| Методы | |
| class variables | |
| class methods | |
| Статические методы | |
| Наследование | |
| super() | |
| Специальные методы | |
| dataclass | |
| __slots__ | |
| Декоратор property | |
| Полиморфизм |