Écrire proprement en Python avec les Namedtuples
- 2021-07-13
- Publié par : Christophe DELEUZE
- Catégorie : Python
Python est livré avec un type de conteneur spécialisé nommé namedtuple et qui ne semble pas beaucoup attirer l’attention qu’il mérite.
Namedtuples peut être une excellente alternative à la définition manuelle d’une classe et ils ont d’autres fonctionnalités intéressantes que je souhaite vous présenter dans cet article.
Qu'est-ce qu'un tuple nommé et qu'est-ce qui le rend si spécial ?
Une bonne façon de penser aux tuples nommés est de les voir comme une extension du type de données tuple intégré.
Les tuples de Python sont une structure de données simple pour regrouper des objets arbitraires. Les tuples sont également immuables, ils ne peuvent pas être modifiés une fois qu’ils ont été créés.
>>> tup = ('salut', object(), 42)
>>> tup
('salut', <object object at 0x105e76b70>, 42)
>>> tup[2]
42
>>> tup[2] = 23
TypeError: "'salut' object does not support item assignment"
Un inconvénient des tuples simples est que les données que vous y stockez ne peuvent être extraites qu’en y accédant via des index entiers. Vous ne pouvez pas donner de noms à des propriétés individuelles stockées dans un tuple. Cela peut avoir un impact sur la lisibilité du code.
De plus, un tuple est toujours une structure ad hoc. Il est difficile de garantir que deux tuples ont le même nombre de champs et les mêmes propriétés stockées sur eux. Cela facilite l’introduction de bugs en mélangeant l’ordre des champs par exemple. Les Namedtuples vise à résoudre ces deux problèmes.
Définir des Namedtuples
Tout d’abord, les tuples nommés sont immuables, tout comme les tuples normaux. Une fois que vous y stockez quelque chose, vous ne pouvez pas le modifier.
En plus de cela, les tuples nommés sont, eh bien… des tuples nommés. Chaque objet qui y est stocké est accessible via un identifiant unique (lisible par l’homme). Cela vous évite d’avoir à mémoriser les index entiers ou de recourir à des solutions de contournement telles que la définition de constantes entières comme mnémoniques pour vos index.
Voici à quoi ressemble un tuple nommé :
>>> from collections import namedtuple
>>> Voiture = namedtuple('Voiture' , 'couleur kilometrage')
Pour utiliser des tuples nommés, vous devez importer le module collections. Ils ont été ajoutés à la bibliothèque standard de Python depuis la version 2.6. Dans l’exemple ci-dessus, nous avons défini un type de données Voiture simple avec deux champs : couleur et kilometrage.
Vous noterez que la syntaxe utilisée est peu courante : pourquoi passons-nous les champs sous forme de chaîne “couleur kilometrage” ?
La réponse est que la fonction d’usine des namedtuples appelle split() sur la chaîne de noms de champs, donc ce n’est vraiment qu’un raccourci pour dire ce qui suit :
>>> 'couleur kilometrage'.split()
['couleur', 'kilometrage']
>>> Voiture = namedtuple('Voiture', ['couleur', 'kilometrage'])
Bien sûr, vous pouvez également transmettre directement une liste de chaînes de caractères si vous préférez. L’avantage d’utiliser une liste appropriée est qu’il est plus facile de reformater ce code si vous devez le diviser sur plusieurs lignes :
>>> Voiture = namedtuple('Voiture', [
... 'couleur',
... 'kilometrage',
... ])
Quelle que soit votre décision, vous pouvez désormais créer de nouveaux objets Voiture avec la fonction usine Voiture. Il se comporte comme si vous aviez défini manuellement une classe Voiture et lui aviez donné un constructeur acceptant une valeur de couleur et de kilometrage :
>>> ma_voiture = Voiture('rouge', 3812.4)
>>> ma_voiture.couleur
'rouge'
>>> ma_voiture.kilometrage
3812.4
L'aspect fonctionnel des Namedtuples
L’unpacking (déballage) des tuples et l’opérateur * utilisé pour le déballage des arguments de fonction fonctionnent également comme prévu :
>>> couleur, kilometrage = ma_voiture
>>> print(couleur, kilometrage)
rouge 3812.4
>>> print(*ma_voiture)
rouge 3812.4
En plus d’accéder aux valeurs stockées dans un tuple nommé par leurs identifiants, vous pouvez toujours y accéder par leur index. De cette façon, les tuples nommés peuvent être utilisés en remplacement des tuples normaux :
>>> ma_voiture[0]
'rouge'
>>> tuple(ma_voiture)
('rouge', 3812.4)
Même la représentation de l’objet dans l’interpréteur interactif a été soignée :
>>> ma_voiture
Voiture(couleur='rouge' , kilometrage=3812.4)
Comme les tuples, les tuples nommés sont immuables. Lorsque vous essayez d’écraser l’un de leurs champs, vous obtenez une exception AttributeError :
>>> ma_voiture.couleur = 'bleu'
AttributeError: "can't set attribute"
Les objets Namedtuple sont implémentés en tant que classes Python standard en interne. En ce qui concerne l’utilisation de la mémoire, ils sont également meilleurs que les classes ordinaires et tout aussi efficaces en mémoire que les tuples normaux.
Une bonne façon de les visualiser est de penser que les tuples nommés sont un raccourci efficace en mémoire pour définir manuellement une classe immuable en Python.
Les Namedtuples en tant que substitut aux classes
Parce qu’ils sont construits au-dessus de classes régulières, vous pouvez même ajouter des méthodes à la classe d’un tuple nommé. Par exemple, vous pouvez étendre la classe comme n’importe quelle autre classe et lui ajouter des méthodes et de nouvelles propriétés de cette façon. Voici un exemple :
>>> Voiture = namedtuple('Voiture', 'couleur kilometrage')
>>> class MaVoitureAvecDesMethodes(Voiture):
... def couleur_hexa(self):
... if self.couleur == 'rouge':
... return '#ff0000'
... else:
... return '#000000'
Nous pouvons maintenant créer des objets MaVoitureAvecDesMethodes et appeler leur méthode couleur_hexa(), comme prévu :
>>> c = MaVoitureAvecDesMethodes('rouge', 1234)
>>> c.couleur_hexa()
'#ff0000'
Cela peut valoir la peine si vous voulez une classe avec des propriétés immuables. Mais il est aussi facile de se tirer une balle dans le pied ici. Par exemple, l’ajout d’un nouveau champ immuable est délicat en raison de la structure interne des tuples nommés. Le moyen le plus simple de créer des hiérarchies de tuples nommés est d’utiliser la propriété ._fields du tuple de base :
>>> Voiture = namedtuple('Voiture', 'couleur kilometrage')
>>> VoitureElectrique = namedtuple('VoitureElectrique', Voiture._fields + ('charge',))
Cela donne le résultat souhaité :
>>> VoitureElectrique('rouge', 1234, 45.0)
VoitureElectrique(couleur='rouge', kilometrage=1234, charge=45.0)
Méthodes d'assistance intégrées
Outre la propriété _fields, chaque instance de namedtuple fournit également quelques méthodes d’assistance supplémentaires qui pourraient vous être utiles. Leurs noms commencent tous par un caractère de soulignement (_) qui signale généralement qu’une méthode ou une propriété est privée et ne fait pas partie de l’interface publique stable d’une classe ou d’un module.
Avec les Namedtuples, la convention de nommage du trait de soulignement a une signification différente : ces méthodes et propriétés d’assistance font partie de l’interface publique de namedtuple. Les assistants ont été nommés de cette façon pour éviter les conflits de noms avec les champs de tuple définis par l’utilisateur. Alors, allez-y et utilisez-les si vous en avez besoin !
Je veux vous montrer quelques scénarios dans lesquels ces méthodes pourraient vous être utiles. Commençons par _asdict() qui renvoie le contenu d’un tuple nommé sous forme de dictionnaire :
>>> ma_voiture._asdict()
OrderedDict([('couleur', 'rouge'), ('kilometrage', 3812.4)])
C’est idéal pour éviter les fautes de frappe lors de la génération d’une sortie JSON, par exemple :
>>> json.dumps(ma_voiture._asdict())
'{"couleur": "rouge", "kilometrage": 3812.4}'
Une autre aide utile est la méthode _replace(). Celle-ci crée une copie (superficielle) d’un tuple et vous permet de remplacer certains de ses champs de manière sélective :
>>> ma_voiture._replace(couleur='bleu')
Voiture(couleur='bleu', kilometrage=3812.4)
Enfin, la méthode de classe _make() peut être utilisée pour créer de nouvelles instances d’un tuple nommé à partir d’une séquence ou d’un itérable :
>>> Voiture._make(['rouge', 999])
Voiture(couleur='rouge', kilometrage=999)
Quand utiliser les Namedtuples ?
Namedtuples peut être un moyen facile de nettoyer votre code et de le rendre plus lisible en appliquant une meilleure structure pour vos données.
Par exemple, passer de types de données ad-hoc comme des dictionnaires avec un format fixe à des tuples nommés vous aidera à exprimer plus clairement vos intentions.
L’utilisation de tuples nommés sur des tuples et des dicts non structurés peut également faciliter la vie de vos collègues, car dans une certaine mesure, ils rendent les données transmises auto-documentées.
D’un autre côté, je vous conseille de ne pas utiliser les tuples nommés s’ils ne vous aident pas à écrire un code plus propre, plus lisible et plus maintenable.
Cependant, si vous les utilisez avec précaution, les tuples nommés peuvent sans aucun doute rendre votre code Python meilleur et plus expressif.
Le mot de la fin
En résumé :
collection.namedtupleest un raccourci économe en mémoire pour définir manuellement une classe immuable en Python ;- Namedtuples peut vous aider à nettoyer votre code en appliquant une structure plus facile à comprendre sur vos données ;
- Namedtuples fournit quelques méthodes d’assistance utiles qui commencent toutes par un
_, mais font partie de l’interface publique. C’est bien de les utiliser.
Des questions, des remarques ? Les commentaires sont là pour ça.