Короткий ответ
Атрибут, объявленный прямо в теле класса (а не внутри __init__ через self.attr), принадлежит самому классу и является общим для всех экземпляров; если это изменяемый объект (список, словарь) и к нему обращаются через self.attr.append(...), все экземпляры мутируют один и тот же общий объект — исправление: инициализировать список индивидуально для каждого экземпляра внутри __init__.
Как это работает подробнее
Атрибут класса (class Order: items = []) создаётся один раз при определении класса и хранится в namespace самого класса, а не в __dict__ каждого экземпляра. Чтение self.items находит этот общий атрибут (Python сначала ищет в __dict__ экземпляра, не находит и поднимается к классу), но self.items.append(x) не создаёт новый атрибут экземпляра — он мутирует тот самый общий объект класса, поэтому изменение видно через любой экземпляр.
Пример кода
class OrderBroken:
items = [] # общий для ВСЕХ экземпляров атрибут класса
def add_item(self, item):
self.items.append(item)
o1 = OrderBroken()
o2 = OrderBroken()
o1.add_item("book")
print(o2.items) # ['book'] — утечка через общий атрибут класса!
print(o1.items is o2.items) # True — это один и тот же объект
class OrderFixed:
def __init__(self):
self.items = [] # отдельный список для каждого экземпляра
def add_item(self, item):
self.items.append(item)
f1, f2 = OrderFixed(), OrderFixed()
f1.add_item("book")
print(f2.items) # [] — изолировано корректноЧто использовать на практике
- Практика. Диагностируют такую проблему, сравнивая
id(order1.items)иid(order2.items)— совпадающий id подтверждает, что это один и тот же объект. Исправление — перенести инициализацию в__init__:self.items = [], что создаёт новый список для каждого экземпляра при его создании, присваивая атрибут экземпляра, который перекрывает (shadow) атрибут класса при последующих обращениях черезself.
Подводные камни
- Риски. Используют изменяемые атрибуты класса для "значений по умолчанию", полагая, что это аналог параметра по умолчанию в функции — поведение здесь похоже на классическую ошибку с mutable default arguments, но проявляется на уровне всего класса, а не отдельного вызова функции. Эта ошибка особенно опасна в коде, где такие объекты используются как кэш или аккумулятор состояния на уровне запроса/сессии.