В подклассе одноимённым обычным атрибутом заменили унаследованное свойство: почему чтение экземпляра перестало вызывать getter?
Потому что поиск атрибута сначала просматривает классы по MRO и останавливается на первом классе, где найдено имя. Обычный атрибут подкласса скрывает унаследованное свойство целиком, поэтому дескриптор property базового класса до обработки не доходит.
Дескрипторы позволяют классу управлять доступом к атрибутам через специальные методы. На этом механизме построены property, связанные методы, classmethod и многие инструменты ORM.
Такой подход отделяет интерфейс атрибута от способа хранения значения: клиент обращается к obj.value, а класс может вычислять значение, проверять его или выполнять побочные действия. Для наследования это означает, что дочерний класс может заменить поведение атрибута обычным значением или новым дескриптором.
Наличие свойства в базовом классе не гарантирует, что оно будет участвовать в доступе к атрибуту экземпляра. Одноимённое объявление в подклассе может полностью изменить поведение: getter, setter и проверка значения базового свойства перестанут вызываться.
Это особенно опасно при расширении моделей, настроек или API: разработчик может считать, что переопределяет только значение по умолчанию, но фактически отключить валидацию или вычисление, реализованные свойством.
При чтении атрибута Python ищет имя в MRO класса экземпляра — от подкласса к базовым классам. Если имя найдено в словаре подкласса, поиск не продолжается в базовом классе.
Далее Python проверяет найденный объект. Если это дескриптор данных, например property, вызывается его __get__. Если найден обычный атрибут, у экземпляра дополнительно проверяется __dict__; при наличии там такого имени возвращается значение экземпляра, иначе — найденное значение класса.
Поэтому унаследованное свойство не конкурирует с атрибутом подкласса по приоритету дескрипторов: оно исключается уже на этапе поиска первого определения имени. При этом если подкласс объявит вместо обычного атрибута новый дескриптор, будет вызван именно новый дескриптор.
В первом обращении getter Parent.value не вызывается: в Child уже найден обычный атрибут value. Во втором обращении значение экземпляра перекрывает обычный атрибут класса.
Если требуется сохранить поведение свойства, его нужно явно переопределить новым property, вызвать реализацию базового класса из нового getter или выбрать другое имя. Простое объявление одноимённого значения не является расширением свойства.
В базовой модели свойство status нормализует строку и запрещает недопустимые значения. В наследнике разработчик объявил status = "new", рассчитывая задать значение по умолчанию. В результате проверки базового свойства исчезли, а экземпляры стали принимать произвольные значения через __dict__.
Возможны три решения:
property в подклассе — сохраняет контроль, но требует явно поддерживать логику getter и setter;Практично оставить status свойством, а начальное значение задавать через внутреннее поле, например _status. Так сохраняются единые правила валидации для базового класса и всех наследников.
Да, если атрибут подкласса найден первым по MRO. Неважно, является ли унаследованное свойство дескриптором данных: до него поиск не дойдёт. Исключение по сути только одно — найденный атрибут подкласса сам может делегировать доступ базовому свойству или быть новым дескриптором с нужной логикой.
Будет использован дескриптор подкласса, потому что именно он является первым найденным определением имени. Его __get__, __set__ или __delete__ могут полностью заменить поведение базового свойства. Наследуемый дескриптор автоматически не объединяется с новым.
super()?Да, но только явно внутри переопределённого свойства или метода. Обращение к super().value ищет value после текущего класса в MRO и поэтому может получить дескриптор базового класса. Простое наличие атрибута в подклассе не создаёт такого делегирования автоматически.