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