Коротко — потому что вы создали список как атрибут класса, а не как атрибут экземпляра. Атрибуты класса общие для всех экземпляров; если этот атрибут — изменяемый объект (список, словарь и т. п.), то изменения будут видны у всех объектов.
Почему так происходит (пошагово):
- Когда вы пишете class User: notes = [], вы создаёте один объект списка и привязываете его к имени User.notes.
- При обращении self.notes Python сначала ищет атрибут в словаре экземпляра (self.__dict__), не находит и берёт User.notes.
- При вызове self.notes.append(...) вы не создаёте новое поле в экземпляре, вы берёте ссылку на общий список и изменяете его — поэтому все экземпляры увидят изменение.
- Если вы присвоите self.notes = [...], то это создаст независимый атрибут в экземпляре, который скроет атрибут класса.
Пример, иллюстрирующий проблему и исправление:
Неправильно (делит список между всеми экземплярами):
```python
class User:
notes = [] # общий для всех
def __init__(self, login):
self.login = login
```
Правильно (у каждого экземпляра свой список):
```python
class User:
def __init__(self, login):
self.login = login
self.notes = [] # новый список для каждого экземпляра
```
Альтернативы и полезные приёмы:
- Для dataclass:
```python
from dataclasses import dataclass, field
@dataclass
class User:
login: str
notes: list = field(default_factory=list)
```
- Если по какой-то причине вы хотите начинать с общего списка, но иногда копировать его — используйте copy.copy или list(User.notes).
- Проверка того, что это один и тот же объект: print(id(user1.notes), id(user2.notes)) — если id одинаковые, это один список.
Резюме: создавайте изменяемые коллекции в __init__ (или через default_factory), если хотите, чтобы они были независимыми для каждого экземпляра.