Si vous ne voulez qu'une partie du comportement de la liste, utilisez la composition (c'est-à-dire que vos instances contiennent une référence à une liste réelle) et implémentez uniquement les méthodes nécessaires au comportement que vous souhaitez. Ces méthodes doivent déléguer le travail à la liste réelle à laquelle toute instance de votre classe fait référence, par exemple :
def __getitem__(self, item):
return self.li[item] # delegate to li.__getitem__
Mise en œuvre de __getitem__
seul vous donnera une quantité surprenante de fonctionnalités, par exemple l'itération et le découpage en tranches.
>>> class WrappedList:
... def __init__(self, lst):
... self._lst = lst
... def __getitem__(self, item):
... return self._lst[item]
...
>>> w = WrappedList([1, 2, 3])
>>> for x in w:
... x
...
1
2
3
>>> w[1:]
[2, 3]
Si vous voulez le complet d'une liste, héritent de collections.UserList
. UserList
est une implémentation Python complète du type de données liste.
Alors pourquoi ne pas hériter de list
directement ?
Un problème majeur avec l'héritage direct de list
(ou tout autre builtin écrit en C) est que le code des builtins peut ou non appeler des méthodes spéciales surchargées dans les classes définies par l'utilisateur. Voici un extrait pertinent du document documentation sur pypy :
Officiellement, CPython n'a aucune règle pour savoir quand exactement les méthodes surchargées des sous-classes de types intégrés sont appelées implicitement ou non. De manière approximative, ces méthodes ne sont jamais appelées par d'autres méthodes intégrées du même objet. Par exemple, une méthode surchargée __getitem__
dans une sous-classe de dict ne sera pas appelée, par exemple, par la fonction intégrée get
méthode.
Une autre citation, tirée de l'ouvrage de Luciano Ramalho Python courant page 351 :
Sous-classer directement des types intégrés comme dict, list ou str est source d'erreurs d'erreurs car les méthodes intégrées ignorent la plupart du temps les définies par l'utilisateur. Au lieu de sous-classer les types intégrés, dérivez vos classes de UserDict , UserList et UserString à partir du module collections qui sont conçus pour être facilement étendus.
... et plus encore, page 370+ :
Les modules intégrés qui se comportent mal : bogue ou fonctionnalité ? Les types intégrés dict, list et str sont des éléments essentiels de Python, ils doivent donc être rapides. ils doivent être rapides - tout problème de performance dans ces types aurait un impact sévère sur presque tout le reste. tout le reste. C'est la raison pour laquelle CPython a adopté les raccourcis qui font que leurs méthodes intégrées se comportent mal. se comportent mal en ne coopérant pas avec les méthodes surchargées par les sous-classes.
Après avoir joué un peu, les problèmes liés à l'option list
semble être moins critique (j'ai essayé de le casser dans Python 3.4 pendant un certain temps mais je n'ai pas trouvé de comportement inattendu vraiment évident), mais je voulais quand même poster une démonstration de ce qui peut arriver en principe, donc en voici une avec un dict
et un UserDict
:
>>> class MyDict(dict):
... def __setitem__(self, key, value):
... super().__setitem__(key, [value])
...
>>> d = MyDict(a=1)
>>> d
{'a': 1}
>>> class MyUserDict(UserDict):
... def __setitem__(self, key, value):
... super().__setitem__(key, [value])
...
>>> m = MyUserDict(a=1)
>>> m
{'a': [1]}
Comme vous pouvez le voir, le __init__
méthode de dict
a ignoré la surcharge __setitem__
tandis que la méthode __init__
de notre UserDict
ne l'a pas fait.