Где хранятся атрибуты экземпляра класса
Что проверяют
Знаете ли вы, как устроен объект в CPython и как работает поиск атрибута. Следом спрашивают про __slots__, про разницу атрибутов экземпляра и класса и про то, почему изменяемый атрибут класса оказывается «общим на всех».
Как отвечать
Базовый ответ: в словаре экземпляра, доступном как obj.__dict__. Присваивание self.x = 1 записывает ключ "x" в этот словарь, поэтому атрибуты можно добавлять на лету. Дальше стоит показать, что картина шире:
- атрибуты класса: лежат в
type(obj).__dict__, этоmappingproxy, общий для всех экземпляров. Там же хранятся методы; __slots__: если класс объявляет__slots__ = ("x", "y"), словаря у экземпляра нет, значения лежат в фиксированных ячейках внутри объекта, а на классе появляются дескрипторы для доступа к ним. Это экономит память на миллионах объектов и запрещает заводить новые атрибуты;- порядок поиска при чтении: сначала data-дескрипторы в классе и его MRO (например,
property), затемobj.__dict__, затем прочие атрибуты класса и non-data дескрипторы, в том числе функции, которые становятся методами. Если ничего не нашлось, вызывается__getattr__; - оптимизации CPython: экземпляры одного класса делят ключи словаря (PEP 412, с версии 3.3), а начиная с 3.11 словарь экземпляра создаётся лениво, только когда к
__dict__обращаются явно.
Для работы с атрибутами по имени есть getattr, setattr, hasattr, delattr и vars(obj), который возвращает тот же __dict__.
Где спотыкаются
Объявляют в теле класса items = [] и считают его атрибутом экземпляра. Это атрибут класса, и self.items.append(...) меняет один список на все объекты. Отдельный список создаёт присваивание self.items = [] в __init__.
Считают, что obj.__dict__ содержит всё, что доступно через точку. Методов, свойств и атрибутов класса там нет.
Уверены, что __slots__ работает при любом наследовании. Если в иерархии хоть один класс без __slots__, у экземпляра снова появится __dict__, и экономия памяти пропадёт.