← Назад к вопросам
Зачем нужна функция property() в Python?
2.0 Middle🔥 301 комментариев
#Python Core#Асинхронность и многопоточность#Тестирование
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Функция property() в Python
property() — это встроенная функция, которая позволяет создавать управляемые атрибуты класса. Она превращает методы в свойства, позволяя вам вызывать методы как обычные атрибуты, но с дополнительной логикой валидации, преобразования или ограничения доступа.
Проблема без property
# ❌ Плохо: прямой доступ к атрибутам
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
person = Person('Alice', 30)
person.age = -5 # Ничто не помешает!
print(person.age) # -5 — невалидное значение
# Проблемы:
# - Нет валидации
# - Нет логики при установке значения
# - Если позже добавим логику, сломаем существующий код
Решение 1: Методы (старый способ)
class Person:
def __init__(self, name, age):
self.name = name
self._age = age # Приватный атрибут
def get_age(self):
return self._age
def set_age(self, value):
if value < 0 or value > 150:
raise ValueError('Age must be 0-150')
self._age = value
person = Person('Alice', 30)
person.set_age(35) # Валидируется
print(person.get_age()) # 35
# Проблемы:
# - Неудобный API (get_/set_)
# - Много кода
Решение 2: property() — правильный способ
class Person:
def __init__(self, name, age):
self.name = name
self._age = age # Приватный атрибут с _
@property
def age(self):
"""Getter: вызывается при person.age"""
return self._age
@age.setter
def age(self, value):
"""Setter: вызывается при person.age = value"""
if value < 0 or value > 150:
raise ValueError('Age must be 0-150')
self._age = value
@age.deleter
def age(self):
"""Deleter: вызывается при del person.age"""
print('Age deleted')
del self._age
# Использование как обычный атрибут
person = Person('Alice', 30)
print(person.age) # 30 — вызывает getter
person.age = 35 # Вызывает setter с валидацией
print(person.age) # 35
person.age = -5 # ValueError: Age must be 0-150
del person.age # Вызывает deleter
Декоратор @property vs property() функция
# Способ 1: Декоратор @property (современный)
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
return self._radius
@radius.setter
def radius(self, value):
if value <= 0:
raise ValueError('Radius must be positive')
self._radius = value
# Способ 2: Функция property() (старый стиль)
class Circle:
def __init__(self, radius):
self._radius = radius
def get_radius(self):
return self._radius
def set_radius(self, value):
if value <= 0:
raise ValueError('Radius must be positive')
self._radius = value
radius = property(get_radius, set_radius)
# Оба способа работают одинаково
Практические примеры
1. Валидация при установке
class BankAccount:
def __init__(self, balance=0):
self._balance = balance
@property
def balance(self):
return self._balance
@balance.setter
def balance(self, value):
if value < 0:
raise ValueError('Balance cannot be negative')
self._balance = value
def withdraw(self, amount):
self.balance -= amount # Использует setter
account = BankAccount(1000)
print(account.balance) # 1000
account.balance = 500 # OK
account.balance = -100 # ValueError!
2. Вычисляемые свойства (readonly)
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
@property
def area(self):
"""Площадь вычисляется, не хранится"""
return self.width * self.height
@property
def perimeter(self):
"""Периметр вычисляется на лету"""
return 2 * (self.width + self.height)
rect = Rectangle(4, 5)
print(rect.area) # 20
print(rect.perimeter) # 18
rect.area = 30 # AttributeError: can't set attribute
3. Ленивая загрузка (lazy loading)
class User:
def __init__(self, user_id):
self.user_id = user_id
self._profile = None
@property
def profile(self):
"""Загружаем профиль только когда нужен"""
if self._profile is None:
print(f'Loading profile for user {self.user_id}')
# Загружаем из БД или API
self._profile = fetch_profile(self.user_id)
return self._profile
user = User(123)
print(user.user_id) # 123
print(user.profile) # Loading profile for user 123
print(user.profile) # Уже загружено, не грузит снова
4. Кэширование значений
class DataProcessor:
def __init__(self, data):
self._data = data
self._cached_result = None
@property
def result(self):
"""Результат вычисляется один раз"""
if self._cached_result is None:
print('Computing result...')
self._cached_result = sum(self._data) / len(self._data)
return self._cached_result
processor = DataProcessor([1, 2, 3, 4, 5])
print(processor.result) # Computing result... 3.0
print(processor.result) # 3.0 (из кэша)
5. Защита от изменений
class ImmutablePoint:
def __init__(self, x, y):
self._x = x
self._y = y
@property
def x(self):
return self._x
@property
def y(self):
return self._y
# Нет setter — координаты неизменяемы
point = ImmutablePoint(3, 4)
print(point.x) # 3
point.x = 5 # AttributeError: can't set attribute
6. Трансформация данных
from datetime import datetime
class Post:
def __init__(self, title, created_at):
self.title = title
self._created_at = created_at # datetime объект
@property
def created_at_iso(self):
"""Форматируем дату при получении"""
return self._created_at.isoformat()
@property
def created_at_friendly(self):
"""Человеко-читаемый формат"""
return self._created_at.strftime('%d.%m.%Y %H:%M')
post = Post('Hello', datetime(2025, 1, 15, 10, 30))
print(post.created_at_iso) # 2025-01-15T10:30:00
print(post.created_at_friendly) # 15.01.2025 10:30
Сравнение: с и без property
# ❌ Без property — неудобно и небезопасно
class Temperature:
def __init__(self, celsius):
self.celsius = celsius
temp = Temperature(25)
temp.celsius = -300 # Невозможная температура!
# ✅ С property — безопасно и удобно
class TemperatureSafe:
def __init__(self, celsius):
self._celsius = celsius
@property
def celsius(self):
return self._celsius
@celsius.setter
def celsius(self, value):
if value < -273.15: # Абсолютный ноль
raise ValueError('Temperature below absolute zero')
self._celsius = value
temp = TemperatureSafe(25)
temp.celsius = -300 # ValueError!
Как property работает под капотом
class Descriptor:
def __init__(self, fget=None, fset=None, fdel=None):
self.fget = fget # Getter функция
self.fset = fset # Setter функция
self.fdel = fdel # Deleter функция
def __get__(self, obj, objtype=None):
if obj is None:
return self
return self.fget(obj)
def __set__(self, obj, value):
if self.fset is None:
raise AttributeError("can't set attribute")
self.fset(obj, value)
def __delete__(self, obj):
if self.fdel is None:
raise AttributeError("can't delete attribute")
self.fdel(obj)
# property() — это встроенный Descriptor
class Person:
def __init__(self, name):
self._name = name
def get_name(self):
return self._name
def set_name(self, value):
if not value:
raise ValueError('Name cannot be empty')
self._name = value
name = property(get_name, set_name)
# Эквивалентно:
# name = Descriptor(get_name, set_name)
Best Practices
1. Используй _ для приватных атрибутов
class BankAccount:
def __init__(self, balance):
self._balance = balance # Приватный
@property
def balance(self):
return self._balance
2. Документируй property
class Temperature:
@property
def celsius(self):
"""Температура в градусах Цельсия (readonly)."""
return self._celsius
3. Дорогие операции кэшируй
class DataAnalysis:
def __init__(self, data):
self._data = data
self._stats = None
@property
def statistics(self):
"""Статистика кэшируется (дорогая операция)."""
if self._stats is None:
self._stats = compute_statistics(self._data)
return self._stats
4. Не делай side effects в getters
# ❌ Плохо: getter вызывает побочные эффекты
class Cache:
@property
def value(self):
logger.info('Getting value') # Side effect
return self._value
# ✅ Хорошо: getter только возвращает значение
class Cache:
@property
def value(self):
return self._value
Итоговое правило
property() используй когда:
- Нужна валидация при установке значения
- Нужно вычислить значение на лету
- Нужна ленивая загрузка
- Нужно изменить реализацию без изменения API
- Нужно заблокировать запись (readonly свойства)
property() не используй когда:
- Это простой атрибут без логики
- property делает дорогую операцию (используй метод вместо этого)
- Нужны side effects в getter