Представьте, что атрибут класса должен вычисляться при каждом чтении и проверять значение при записи: какой механизм Python обеспечивает такое поведение?
Такое поведение обеспечивает дескриптор, обычно использованный через встроенную функцию property. Он сохраняет синтаксис обращения к атрибуту, но перенаправляет чтение и запись в методы класса.
В объектной модели нужен был способ добавить проверку, вычисление или побочные действия при доступе к атрибуту, не меняя публичный интерфейс с obj.value на явные вызовы вроде obj.get_value() и obj.set_value(...). Дескрипторы решают эту задачу на уровне протокола доступа к атрибутам, а property предоставляет удобную стандартную оболочку для наиболее распространённого случая.
Прямое хранение значения в obj.__dict__ не позволяет автоматически проверять новые присваивания или вычислять значение динамически. Если заменить атрибут на методы, можно нарушить существующий API и усложнить использование класса.
Неверная реализация также может привести к рекурсивному обращению к тому же свойству, невозможности присваивания или обходу требуемой валидации через альтернативные пути хранения данных.
property хранит функции доступа: обычно функцию чтения fget, записи fset и удаления fdel. При обращении к атрибуту экземпляра Python обнаруживает объект property в классе и вызывает соответствующую функцию, передавая ей экземпляр.
В примере user.age вызывает геттер, а user.age = 31 — сеттер. Значение хранится в отдельном атрибуте _age, чтобы сеттер не вызвал сам себя рекурсивно.
property является дескриптором данных: у него есть методы __get__ и __set__. Поэтому при обычном доступе атрибут, определённый таким дескриптором в классе, имеет приоритет над одноимённой записью в словаре экземпляра.
Если определить свойство только с геттером, присваивание ему обычно завершится AttributeError. Если геттер отсутствует, чтение также недоступно. Свойство, обращённое через класс, обычно возвращает сам объект property, что позволяет использовать его, например, для переопределения в подклассе.
Преимущество property — совместимость с естественным синтаксисом атрибутов и централизованная логика доступа. Компромисс состоит в том, что скрытые вычисления и побочные эффекты делают поведение менее очевидным; поэтому свойства обычно должны быть быстрыми, предсказуемыми и не выполнять неожиданную тяжёлую работу.
В модели заказа поле total зависит от строк заказа и должно пересчитываться при каждом чтении. Возможны три варианта: хранить сумму вручную, что быстро, но создаёт риск рассинхронизации; использовать метод calculate_total(), что явно, но меняет интерфейс; или определить свойство, что сохраняет обращение как к атрибуту и исключает устаревшее кэшированное значение.
Если пересчёт дешёвый, выбирают property: вызывающий код получает актуальный результат, а формула сосредоточена в одном месте. Если вычисление дорогое, лучше явно применить кэширование или отдельный метод с понятной стоимостью операции, поскольку свойство маскирует вычисление за обычным чтением.
Нет. property хранит функции доступа, а само значение обычно помещают в отдельный атрибут экземпляра, например _age. Свойство может вообще не иметь хранилища и вычислять результат на основе других полей или внешнего состояния.
__dict__ экземпляра?Для обычного property — не через стандартное присваивание одноимённому имени: это дескриптор данных, и его __set__ имеет приоритет. Однако можно напрямую изменить внутреннее хранилище, например _age, если оно доступно, поэтому property не является полноценной защитой от намеренного обхода инвариантов.
property отличается от обычного дескриптора, написанного вручную?property — готовая реализация распространённого сценария: связать чтение, запись и удаление атрибута с функциями. Пользовательский дескриптор может реализовать более сложное поведение: общее хранилище, автоматическое имя поля, кэширование, регистрацию доступа или разные правила для класса и экземпляров, но требует ручной реализации протокола __get__, __set__ и, при необходимости, __delete__.