Декоратор 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

РЕКЛАМА хостинга Beget, которым я пользуюсь более десяти лет

Изображение баннера

Конец рекламы хостинга Beget, который я всем рекомендую.

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

РЕКЛАМА хостинга Beget, которым я пользуюсь более десяти лет

Изображение баннера

Конец рекламы хостинга Beget, который я всем рекомендую.

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
Полиморфизм

Поиск по сайту

Подпишитесь на Telegram канал @aofeed чтобы следить за выходом новых статей и обновлением старых

Перейти на канал

@aofeed

Задать вопрос в Телеграм-группе

@aofeedchat

Контакты и сотрудничество:
Рекомендую наш хостинг beget.ru
Пишите на info@urn.su если Вы:
1. Хотите написать статью для нашего сайта или перевести статью на свой родной язык.
2. Хотите разместить на сайте рекламу, подходящую по тематике.
3. Реклама на моём сайте имеет максимальный уровень цензуры. Если Вы увидели рекламный блок недопустимый для просмотра детьми школьного возраста, вызывающий шок или вводящий в заблуждение - пожалуйста свяжитесь с нами по электронной почте
4. Нашли на сайте ошибку, неточности, баг и т.д. ... .......
5. Статьи можно расшарить в соцсетях, нажав на иконку сети: