МУНИЦИПАЛЬНОЕ АВТОНОМНОЕ ОБРАЗОВАТЕЛЬНОЕ УЧРЕЖДЕНИЕ ДОПОЛНИТЕЛЬНОГО ОБРАЗОВАНИЯ
«ЦЕНТР ДЕТСКОГО ТЕХНИЧЕСКОГО ТВОРЧЕСТВА»
Методическая разработка
«Декоратор property в Python»
к дополнительной общеобразовательной
общеразвивающей программе
технической направленности
«Программирование на Python»
Возраст детей: 10-17 лет
Автор: Костычев Вадим Александрович
г. Заречный Пензенской области
2025 г.
Данный материал предназначен для преподавателей информатики, программирования и студентов (учащихся), изучающих язык программирования Python. Он может быть использован как на лекциях, так и при проведении практических занятий или самостоятельной работе.
Цель: научить использовать декоратор @property для реализации контролируемого доступа к атрибутам класса, заменяя геттеры и сеттеры на читаемый и безопасный подход, основанный на принципах инкапсуляции и обратной совместимости.
Задачи:
Объяснить философию @property как альтернативы геттерам/сеттерам.
Разобрать синтаксис геттера, сеттера и делитера.
Научить создавать вычисляемые свойства и избегать рекурсии.
Отработать валидацию данных через @setter.
Показать применение @deleter в реальных сценариях.
Научить обеспечивать обратную совместимость при изменении интерфейса.
«В Python не нужны геттеры и сеттеры — пока они не понадобятся. А когда понадобятся — используйте @property» — правило Python-разработчиков
Зачем нужен @property?
В ООП часто возникает потребность:
- читать значение атрибута (геттер),
- устанавливать его с проверкой (сеттер),
- вычислять значение на лету (вычисляемое свойство).
В языках вроде Java или C# для этого используют геттеры (getBalance()) и сеттеры (setBalance()):
// Java-пример
public class BankAccount {
private double balance;
public double getBalance() { return balance; }
public void setBalance(double amount) {
if (amount Баланс не может быть отрицательным!");
this.balance = amount;
}
}
Но в Python это неудобно:
# Анти-паттерн: геттеры/сеттеры в Python
class BankAccount:
def __init__(self, balance):
self._balance = balance
def get_balance(self):
return self._balance
def set_balance(self, amount):
if amount
raise ValueError("Баланс не может быть отрицательным!")
self._balance = amount
acc = BankAccount(1000)
print(acc.get_balance()) # Неуклюже
acc.set_balance(1500) # Неестественно
Что делает @property?
Он превращает метод в атрибут, который можно читать и (опционально) записывать как обычное поле, но при этом с сохранением логики!
class BankAccount:
def __init__(self, balance):
self._balance = balance
@property
def balance(self): # Геттер
return self._balance
@balance.setter # Сеттер
def balance(self, amount):
if amount
raise ValueError("Баланс не может быть отрицательным!")
self._balance = amount
acc = BankAccount(1000)
print(acc.balance) # Как у атрибута!
acc.balance = 1500 # Как у атрибута — но с проверкой!
Ключевая идея: @property позволяет изменить внутреннюю реализацию класса без изменения внешнего интерфейса — даже если пользователь уже использовал .balance как атрибут.
Основные компоненты @property
Компонент | Назначение | Синтаксис |
Геттер | Читает значение | @property |
Сеттер | Записывает значение с проверкой | @имя_свойства.setter |
Делитер | Удаляет значение | @имя_свойства.deleter |
Геттер (@property)
Превращает метод в читаемое свойство.
Пример:
class Circle:
def __init__(self, radius):
self.radius = radius
@property
def area(self): # Геттер
return 3.14159 * self.radius 2
c = Circle(5)
print(c.area) # 78.53975 — как атрибут!
# print(c.area()) Ошибка — это не метод!
area теперь вычисляется динамически при каждом обращении, но используется как поле.
Когда использовать?
- Когда значение можно вычислить из других атрибутов.
- Когда нужно скрыть сложность вычисления.
- Когда вы хотите отказаться от хранения значения и считать его на лету (экономия памяти).
Сеттер (@имя.setter)
Позволяет устанавливать значение с валидацией, преобразованием или побочными эффектами.
Пример:
class Temperature:
def __init__(self, celsius):
self._celsius = celsius
@property
def celsius(self):
return self._celsius
@celsius.setter
def celsius(self, value):
if value
raise ValueError("Температура не может быть ниже абсолютного нуля!")
self._celsius = value
t = Temperature(25)
t.celsius = 30 # OK
t.celsius = -300 # ValueError: Температура не может быть ниже...
Преимущества:
- Обратная совместимость: если раньше было obj.temp = 20, то после добавления @property код продолжает работать.
- Валидация: нельзя установить некорректное значение.
- Автоматическое обновление зависимых данных:
class Temperature:
def __init__(self, celsius):
self._celsius = celsius
self._fahrenheit = None # кэш
@property
def celsius(self):
return self._celsius
@celsius.setter
def celsius(self, value):
if value
raise ValueError("Недопустимая температура!")
self._celsius = value
self._fahrenheit = None # сброс кэша — пересчитаем при следующем запросе
@property
def fahrenheit(self):
if self._fahrenheit is None:
self._fahrenheit = (self._celsius * 9/5) + 32
return self._fahrenheit
Делитер (@имя.deleter)
Позволяет определить поведение при удалении свойства через del.
Пример:
class Person:
def __init__(self, name):
self._name = name
@property
def name(self):
return self._name
@name.setter
def name(self, value):
if not isinstance(value, str) or len(value.strip()) == 0:
raise ValueError("Имя должно быть непустой строкой")
self._name = value.strip()
@name.deleter
def name(self):
print(f" Имя '{self._name}' удалено.")
self._name = "Аноним"
p = Person("Alice")
print(p.name) # Alice
del p.name # Выводит: Имя 'Alice' удалено.
print(p.name) # Аноним
- del obj.prop вызывает @prop.deleter, но не удаляет атрибут из __dict__ — он просто выполняет ваш код.
Полный пример: Класс с @property, @setter, @deleter
python
class Student:
def __init__(self, name, grade):
self.name = name # через setter
self._grade = None
self.grade = grade # через setter
@property
def name(self):
return self._name
@name.setter
def name(self, value):
if not isinstance(value, str) or len(value.strip()) == 0:
raise ValueError("Имя должно быть непустой строкой")
self._name = value.strip().title()
@property
def grade(self):
return self._grade
@grade.setter
def grade(self, value):
if not isinstance(value, (int, float)) or not (0
raise ValueError("Оценка должна быть числом от 0 до 100")
self._grade = value
@grade.deleter
def grade(self):
print(f"🗑️ Оценка студента {self.name} сброшена.")
self._grade = None
@property
def letter_grade(self):
"""Вычисляемое свойство: буквенная оценка"""
if self._grade is None:
return "Не оценено"
elif self._grade = 90:
return "A"
elif self._grade = 80:
return "B"
elif self._grade = 70:
return "C"
elif self._grade = 60:
return "D"
else:
return "F"
# Использование
s = Student("alice smith", 87)
print(s.name) # Alice Smith
print(s.grade) # 87
print(s.letter_grade) # B
s.grade = 95
print(s.letter_grade) # A
del s.grade # Вывод: Оценка студента Alice Smith сброшена.
print(s.grade) # None
print(s.letter_grade) # Не оценено
# s.grade = 150 # ValueError
# s.name = "" # ValueError
Почему @property лучше, чем геттеры/сеттеры?
Критерий | Геттер/сеттер (get_name()) | @property |
Синтаксис | obj.get_name() | obj.name |
Читаемость | Многословно | Естественно |
Инкапсуляция | Есть | Есть, лучше |
Обратная совместимость | Ломается при изменении | Сохраняется |
Стиль Python | Не рекомендуется | Рекомендуется PEP 8 |
Возможность вычисляемых свойств | Нет | Да |
Если вы начинаете с obj.x, и потом решаете добавить валидацию — просто добавьте @property и @x.setter. Все существующие вызовы obj.x продолжат работать — никто ничего не сломает.
Типичные ошибки и как их избежать
Ошибка | Почему не стоит использовать | Как исправить |
@property без _-атрибута | self.value внутри getter - бесконечная рекурсия | Всегда используйте скрытый атрибут: self._value |
@property с return self.property | Рекурсия - RecursionError | return self._property |
Использование @property для простого чтения без причины | Излишняя сложность | Делайте атрибут публичным: self.value |
Отсутствие валидации в сеттере | Можно установить неверные данные | Всегда проверяйте входные данные в @setter |
Использование @property для тяжёлых операций | Замедлит код, если используется часто | Добавьте кэширование (if self._cached is None: ...) |
### Плохой пример:
class BadExample:
@property
def x(self):
return self.x # Бесконечная рекурсия!
→ RecursionError: maximum recursion depth exceeded
### Правильный пример:
class GoodExample:
def __init__(self):
self._x = 0
@property
def x(self):
return self._x
@x.setter
def x(self, value):
self._x = value
Когда НЕ использовать @property?
Ситуация | Рекомендация |
Атрибут — просто данные, без логики | Делайте его публичным: self.color = "red" |
Вы пишете быстрый скрипт | Не усложняйте |
Вам нужна производительность в цикле | @property немного медленнее — но разница ничтожна |
Вы делаете API для внешних систем | Используйте @property — это чистый и стандартный способ |
Начинайте с публичных атрибутов. Добавляйте @property только тогда, когда вам нужно добавить логику.
Python не требует от вас писать геттеры. Но если вы захотите — вы можете добавить их без изменения кода клиентов.
@property — это не просто декоратор. Это культурный символ Python: «Начни просто. Стань мощнее, не меняя интерфейс. Будь ответственным. Не ломай другим».