Une liste définie dans le corps d’une classe est un attribut de classe : elle est créée une seule fois lors de la définition de la classe et stockée sur celle-ci. Tant qu’une instance ne possède pas d’attribut du même nom, l’accès via self.articles retrouve cette même liste.

class Panier:
articles = []

def ajouter(self, article):
self.articles.append(article)

a = Panier()
b = Panier()

a.ajouter("pomme")

print(a.articles) ['pomme']
print(b.articles) ['pomme']
print(a.articles is b.articles) True

append() modifie la liste en place : il ne crée donc pas d’attribut propre à a.

La correction consiste à créer une nouvelle liste pour chaque instance :

class Panier:
def __init__(self):
self.articles = []

def ajouter(self, article):
self.articles.append(article)

Avec un paramètre optionnel, il faut éviter un argument par défaut mutable :

class Panier:
def __init__(self, articles=None):
self.articles = [] if articles is None else list(articles)

Avec une dataclass, utiliser default_factory :

from dataclasses import dataclass, field

@dataclass
class Panier:
articles: list = field(default_factory=list)

Un attribut de classe mutable reste approprié s’il doit réellement être partagé. On peut alors expliciter cette intention avec ClassVar :

from typing import ClassVar

class Panier:
registre: ClassVar[list] = []