Quels sont les tuples nommés?
Un tuple nommé est un tuple.
Il fait tout ce qu'un tuple peut.
Mais c'est plus qu'un simple tuple.
Il s'agit d'une sous-classe spécifique d'un tuple créé par programme selon vos spécifications, avec des champs nommés et une longueur fixe.
Cela, par exemple, crée une sous-classe de tuple, et en plus d'être de longueur fixe (dans ce cas, trois), il peut être utilisé partout où un tuple est utilisé sans rupture. Ceci est connu sous le nom de substituabilité Liskov.
Nouveau dans Python 3.6 , nous pouvons utiliser une définition de classe avectyping.NamedTuple
pour créer un tuple nommé:
from typing import NamedTuple
class ANamedTuple(NamedTuple):
"""a docstring"""
foo: int
bar: str
baz: list
Ce qui précède est le même que ci-dessous, sauf que ce qui précède a en outre des annotations de type et une docstring. Ce qui suit est disponible en Python 2+:
>>> from collections import namedtuple
>>> class_name = 'ANamedTuple'
>>> fields = 'foo bar baz'
>>> ANamedTuple = namedtuple(class_name, fields)
Cela l'instancie:
>>> ant = ANamedTuple(1, 'bar', [])
Nous pouvons l'inspecter et utiliser ses attributs:
>>> ant
ANamedTuple(foo=1, bar='bar', baz=[])
>>> ant.foo
1
>>> ant.bar
'bar'
>>> ant.baz.append('anything')
>>> ant.baz
['anything']
Explication plus approfondie
Pour comprendre les tuples nommés, vous devez d'abord savoir ce qu'est un tuple. Un tuple est essentiellement une liste immuable (ne peut pas être modifiée sur place en mémoire).
Voici comment utiliser un tuple standard:
>>> student_tuple = 'Lisa', 'Simpson', 'A'
>>> student_tuple
('Lisa', 'Simpson', 'A')
>>> student_tuple[0]
'Lisa'
>>> student_tuple[1]
'Simpson'
>>> student_tuple[2]
'A'
Vous pouvez développer un tuple avec un déballage itérable:
>>> first, last, grade = student_tuple
>>> first
'Lisa'
>>> last
'Simpson'
>>> grade
'A'
Les tuples nommés sont des tuples qui permettent d'accéder à leurs éléments par leur nom au lieu d'un simple index!
Vous créez un tuple nommé comme ceci:
>>> from collections import namedtuple
>>> Student = namedtuple('Student', ['first', 'last', 'grade'])
Vous pouvez également utiliser une seule chaîne avec les noms séparés par des espaces, une utilisation légèrement plus lisible de l'API:
>>> Student = namedtuple('Student', 'first last grade')
Comment les utiliser?
Vous pouvez faire tout ce que les tuples peuvent faire (voir ci-dessus) ainsi que les opérations suivantes:
>>> named_student_tuple = Student('Lisa', 'Simpson', 'A')
>>> named_student_tuple.first
'Lisa'
>>> named_student_tuple.last
'Simpson'
>>> named_student_tuple.grade
'A'
>>> named_student_tuple._asdict()
OrderedDict([('first', 'Lisa'), ('last', 'Simpson'), ('grade', 'A')])
>>> vars(named_student_tuple)
OrderedDict([('first', 'Lisa'), ('last', 'Simpson'), ('grade', 'A')])
>>> new_named_student_tuple = named_student_tuple._replace(first='Bart', grade='C')
>>> new_named_student_tuple
Student(first='Bart', last='Simpson', grade='C')
Un intervenant a demandé:
Dans un grand script ou programme, où définit-on habituellement un tuple nommé?
Les types que vous créez avec namedtuple
sont essentiellement des classes que vous pouvez créer avec un raccourci facile. Traitez-les comme des cours. Définissez-les au niveau du module, afin que les cornichons et les autres utilisateurs puissent les trouver.
L'exemple de travail, au niveau du module global:
>>> from collections import namedtuple
>>> NT = namedtuple('NT', 'foo bar')
>>> nt = NT('foo', 'bar')
>>> import pickle
>>> pickle.loads(pickle.dumps(nt))
NT(foo='foo', bar='bar')
Et cela montre l'échec de la recherche de la définition:
>>> def foo():
... LocalNT = namedtuple('LocalNT', 'foo bar')
... return LocalNT('foo', 'bar')
...
>>> pickle.loads(pickle.dumps(foo()))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
_pickle.PicklingError: Can't pickle <class '__main__.LocalNT'>: attribute lookup LocalNT on __main__ failed
Pourquoi / quand dois-je utiliser des tuples nommés au lieu de tuples normaux?
Utilisez-les pour améliorer votre code afin d'exprimer la sémantique des éléments de tuple dans votre code.
Vous pouvez les utiliser à la place d'un objet si vous utilisez autrement un objet avec des attributs de données immuables et aucune fonctionnalité.
Vous pouvez également les sous-classer pour ajouter des fonctionnalités, par exemple :
class Point(namedtuple('Point', 'x y')):
"""adding functionality to a named tuple"""
__slots__ = ()
@property
def hypot(self):
return (self.x ** 2 + self.y ** 2) ** 0.5
def __str__(self):
return 'Point: x=%6.3f y=%6.3f hypot=%6.3f' % (self.x, self.y, self.hypot)
Pourquoi / quand dois-je utiliser des tuples normaux au lieu de tuples nommés?
Ce serait probablement une régression pour passer de l'utilisation de tuples nommés à des tuples. La décision de conception initiale se concentre sur la question de savoir si le coût du code supplémentaire impliqué vaut la lisibilité améliorée lorsque le tuple est utilisé.
Il n'y a pas de mémoire supplémentaire utilisée par les tuples nommés par rapport aux tuples.
Existe-t-il une sorte de "liste nommée" (une version modifiable du tuple nommé)?
Vous recherchez un objet à fente qui implémente toutes les fonctionnalités d'une liste de taille statique ou une liste de sous-classes qui fonctionne comme un tuple nommé (et qui empêche en quelque sorte la liste de changer de taille.)
Un exemple maintenant étendu, et peut-être même substituable par Liskov, du premier:
from collections import Sequence
class MutableTuple(Sequence):
"""Abstract Base Class for objects that work like mutable
namedtuples. Subclass and define your named fields with
__slots__ and away you go.
"""
__slots__ = ()
def __init__(self, *args):
for slot, arg in zip(self.__slots__, args):
setattr(self, slot, arg)
def __repr__(self):
return type(self).__name__ + repr(tuple(self))
# more direct __iter__ than Sequence's
def __iter__(self):
for name in self.__slots__:
yield getattr(self, name)
# Sequence requires __getitem__ & __len__:
def __getitem__(self, index):
return getattr(self, self.__slots__[index])
def __len__(self):
return len(self.__slots__)
Et pour l'utiliser, il suffit de sous-classer et de définir __slots__
:
class Student(MutableTuple):
__slots__ = 'first', 'last', 'grade' # customize
>>> student = Student('Lisa', 'Simpson', 'A')
>>> student
Student('Lisa', 'Simpson', 'A')
>>> first, last, grade = student
>>> first
'Lisa'
>>> last
'Simpson'
>>> grade
'A'
>>> student[0]
'Lisa'
>>> student[2]
'A'
>>> len(student)
3
>>> 'Lisa' in student
True
>>> 'Bart' in student
False
>>> student.first = 'Bart'
>>> for i in student: print(i)
...
Bart
Simpson
A