Что такое property декоратор и когда его использовать?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Что такое property декоратор и когда его использовать?
Основная идея
@property — это встроенный декоратор Python, который превращает метод в атрибут-свойство. Это позволяет вызывать метод как обычный атрибут, без круглых скобок, но при этом выполняется дополнительная логика.
Это реализация паттерна getter/setter без явного вызова методов:
class Person:
def __init__(self, name, age):
self._name = name
self._age = age
# Без @property
def get_name(self):
return self._name
# С @property — более элегантно
@property
def name(self):
return self._name
person = Person("Alice", 30)
# Без @property нужны скобки
name1 = person.get_name() # Alice
# С @property — как обычный атрибут
name2 = person.name # Alice
Синтаксис и основные декораторы
Комплект из трёх декораторов:
class Rectangle:
def __init__(self, width, height):
self._width = width
self._height = height
# Getter — чтение свойства
@property
def width(self):
print("Получаем ширину...")
return self._width
# Setter — установка значения
@width.setter
def width(self, value):
if value <= 0:
raise ValueError("Ширина должна быть положительной")
print(f"Устанавливаем ширину: {value}")
self._width = value
# Deleter — удаление свойства
@width.deleter
def width(self):
print("Удаляем ширину...")
del self._width
# Использование
rect = Rectangle(10, 20)
print(rect.width) # Получаем ширину... → 10
rect.width = 15 # Устанавливаем ширину: 15
print(rect.width) # Получаем ширину... → 15
rect.width = -5 # ValueError: Ширина должна быть положительной
del rect.width # Удаляем ширину...
Практический пример: Валидация
Основное применение — валидация данных при присваивании:
class User:
def __init__(self, email):
self._email = None
self.email = email # Использует setter с валидацией
@property
def email(self):
return self._email
@email.setter
def email(self, value):
# Валидация перед сохранением
if "@" not in value:
raise ValueError("Некорректный email")
self._email = value
@email.deleter
def email(self):
self._email = None
user = User("test@example.com")
print(user.email) # test@example.com
user.email = "invalid-email" # ValueError: Некорректный email
Вычисляемые свойства
@property часто используется для вычисляемых атрибутов, которые зависят от других данных:
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
# Вычисляемое свойство — нет _area, вычисляется на лету
@property
def area(self):
return self.width * self.height
@property
def perimeter(self):
return 2 * (self.width + self.height)
rect = Rectangle(10, 20)
print(rect.area) # 200
print(rect.perimeter) # 60
# При изменении размеров — свойства автоматически пересчитываются
rect.width = 5
print(rect.area) # 100
Кэширование с @property (lru_cache)
Для дорогостоящих вычислений можно кэшировать результат:
from functools import lru_cache, cached_property
class DataProcessor:
def __init__(self, data):
self.data = data
# Вариант 1: property с lru_cache (старый способ)
@property
@lru_cache(maxsize=1)
def expensive_result(self):
print("Выполняем дорогостоящее вычисление...")
return sum(self.data) * 2
# Вариант 2: cached_property (новый способ, Python 3.8+)
@cached_property
def another_result(self):
print("Вычисляем...")
return len(self.data) ** 2
processor = DataProcessor([1, 2, 3, 4, 5])
print(processor.expensive_result) # Вычисляем... → 30
print(processor.expensive_result) # Возвращаем кэш → 30
Инкапсуляция: Защита данных
@property помогает скрыть внутреннюю реализацию:
class BankAccount:
def __init__(self, balance):
self._balance = balance # Внутренний атрибут (защищён)
@property
def balance(self):
return self._balance
def deposit(self, amount):
if amount > 0:
self._balance += amount
else:
raise ValueError("Сумма должна быть положительной")
def withdraw(self, amount):
if amount > self._balance:
raise ValueError("Недостаточно средств")
self._balance -= amount
account = BankAccount(1000)
print(account.balance) # 1000
# Прямое изменение невозможно (нет setter)
account.balance = 500 # AttributeError
# Правильный способ
account.deposit(100)
print(account.balance) # 1100
Сравнение подходов
# Плохо — открытый атрибут
class PersonBad:
def __init__(self, age):
self.age = age
person = PersonBad(25)
person.age = -5 # Нет валидации!
# Хорошо — с @property
class PersonGood:
def __init__(self, age):
self._age = None
self.age = age # Использует setter
@property
def age(self):
return self._age
@age.setter
def age(self, value):
if not 0 <= value <= 150:
raise ValueError("Некорректный возраст")
self._age = value
person = PersonGood(25)
print(person.age) # 25
person.age = -5 # ValueError
Когда использовать @property
ИСПОЛЬЗУЙ когда:
- Валидация данных при присваивании
- Вычисляемые атрибуты (зависят от других полей)
- Инкапсуляция (скрытие внутренней реализации)
- Логирование доступа к атрибутам
- Кэширование дорогостоящих вычислений
НЕ используй когда:
- Нет валидации или логики — просто переди открытый атрибут
- Операция дорогостоящая, но не кэшируется — методом лучше
- Множественные побочные эффекты — явный метод лучше
Альтернативы: getattr и setattr
class DynamicProperties:
def __init__(self):
self._data = {}
def __getattr__(self, name):
if name in self._data:
return self._data[name]
raise AttributeError(f"Атрибут {name} не найден")
def __setattr__(self, name, value):
if name.startswith("_"):
super().__setattr__(name, value)
else:
self._data[name] = value
obj = DynamicProperties()
obj.foo = 10
print(obj.foo) # 10
Лучшие практики
class Animal:
def __init__(self, name, age):
self._name = name
self._age = age
@property
def name(self):
"""Возвращает имя животного."""
return self._name
@property
def age(self):
"""Возвращает возраст в годах."""
return self._age
@age.setter
def age(self, value):
"""Устанавливает возраст с валидацией."""
if not isinstance(value, int) or value < 0:
raise ValueError("Возраст должен быть неотрицательным целым числом")
self._age = value
@property
def is_adult(self):
"""Проверяет, является ли животное взрослым (возраст >= 2)."""
return self._age >= 2
Заключение
@property — это мощный инструмент для валидации данных, создания вычисляемых атрибутов, инкапсуляции и защиты данных. Он делает код более Pythonic, скрывая сложность за простым интерфейсом атрибутов.