Pensez en Python

Comment maîtriser la science de l'informatique


précédentsommairesuivant

12. Tuples

Ce chapitre présente un autre type interne, le tuple, et ensuite montre comment les listes, les dictionnaires et les tuples fonctionnent ensemble. Je présente également une fonctionnalité utile pour les listes d'arguments de longueur variable, les opérateurs de regroupement et de dispersion.

12-1. Les tuples sont immuables

Un tuple est une séquence de valeurs. Les valeurs peuvent être de tout type, et elles sont indexées par des entiers ; à cet égard, les tuples ressemblent donc beaucoup aux listes. La différence importante est que les tuples sont immuables.

Syntaxiquement, un tuple est une liste de valeurs séparées par des virgules :

 
Sélectionnez
>>> t = 'a', 'b', 'c', 'd', 'e'

Bien que ce ne soit pas nécessaire, il est courant de mettre les tuples entre des parenthèses :

 
Sélectionnez
>>> t = ('a', 'b', 'c', 'd', 'e')

Pour créer un tuple avec un seul élément, vous devez inclure une virgule finale :

 
Sélectionnez
>>> t1 = 'a',
>>> type(t1)
<class 'tuple'>

Une valeur entre parenthèses n'est pas un tuple :

 
Sélectionnez
>>> t2 = ('a')
>>> type(t2)
<class 'str'>

Une autre façon de créer un tuple est d'utiliser la fonction interne tuple. Sans aucun argument, elle crée un tuple vide :

 
Sélectionnez
>>> t = tuple()
>>> t
()

Si l'argument est une séquence (chaîne de caractères, liste ou tuple), le résultat est un tuple contenant les éléments de la séquence :

 
Sélectionnez
>>> t = tuple('lupins')
>>> t
('l', 'u', 'p', 'i', 'n', 's')

Le mot tuple étant le nom d'une fonction interne, vous devez éviter de l'utiliser comme nom de variable.

La majorité des opérateurs de liste fonctionnent également sur des tuples. L'opérateur [] d'indexation indexe un élément :

 
Sélectionnez
>>> t = ('a', 'b', 'c', 'd', 'e')
>>> t[0]
'a'

Et l'opérateur de tranche sélectionne un ensemble d'éléments.

 
Sélectionnez
>>> t[1:3]
('b', 'c')

Mais si vous essayez de modifier un des éléments du tuple, vous obtenez une erreur :

 
Sélectionnez
>>> t[0] = 'A'
TypeError: object doesn't support item assignment

Comme les tuples sont immuables, vous ne pouvez pas modifier les éléments. Mais vous pouvez remplacer un tuple par un autre :

 
Sélectionnez
>>> t = ('A',) + t[1:]
>>> t
('A', 'b', 'c', 'd', 'e')

Cette instruction crée un nouveau tuple, puis fait de t sa référence.

Les opérateurs relationnels fonctionnent sur les tuples et d'autres séquences ; Python commence par comparer le premier élément de chaque séquence. S'ils sont égaux, il passe à l'élément suivant, et ainsi de suite, jusqu'à ce qu'il trouve des éléments qui diffèrent. Les éléments ultérieurs ne sont pas considérés (même s'ils sont très grands).

 
Sélectionnez
>>> (0, 1, 2) < (0, 3, 4)
True
>>> (0, 1, 2000000) < (0, 3, 4)
True

12-2. Affectation de tuple

Il est souvent utile d'échanger les valeurs de deux variables. Avec des affectations classiques, vous devez utiliser une variable temporaire. Par exemple, pour intervertir a et b :

 
Sélectionnez
>>> temp = a
>>> a = b
>>> b = temp

Cette solution est lourde ; l'affectation de tuple est plus élégante :

 
Sélectionnez
>>> a, b = b, a

Le côté gauche de l'affectation est un tuple de variables ; le côté droit est un tuple d'expressions. Chaque valeur est affectée à sa variable respective. Toutes les expressions du côté droit sont évaluées avant toute affectation.

Le nombre de variables du côté gauche doit être identique au nombre de valeurs du côté droit :

 
Sélectionnez
>>> a, b = 1, 2, 3
ValueError: too many values to unpack

De façon plus générale, le côté droit peut être tout type de séquence (chaîne de caractères, liste ou tuple). Par exemple, pour diviser une adresse de courriel dans un nom d'utilisateur et un domaine, vous pourriez écrire :

 
Sélectionnez
>>> adresse = 'monty@python.org'
>>> nom_utilisateur, domaine = adresse.split('@')

La valeur de retour de split est une liste de deux éléments ; le premier élément est assigné à nom_utilisateur et le second à domaine.

 
Sélectionnez
>>> nom_utilisateur
'monty'
>>> domaine
'python.org'

12-3. Tuples comme valeurs de retour

Au sens strict, une fonction ne peut renvoyer qu'une seule valeur, mais si la valeur est un tuple, l'effet est le même que de renvoyer des valeurs multiples. Par exemple, si vous voulez effectuer une division entière entre deux nombres entiers et calculer le quotient et le reste, il est inefficace de calculer x / y puis x % y. Il est préférable de calculer les deux en même temps.

La fonction interne divmod prend deux arguments et retourne un tuple de deux valeurs, le quotient et le reste. Vous pouvez stocker le résultat comme un tuple :

 
Sélectionnez
>>> t = divmod(7, 3)
>>> t
(2, 1)

Ou utiliser l'affectation de tuple pour stocker les éléments séparément :

 
Sélectionnez
>>> quot, res = divmod(7, 3)
>>> quot
2
>>> res
1

Voici un exemple de fonction qui retourne un tuple :

 
Sélectionnez
def min_max(t):
    return min(t), max(t)

max et min sont des fonctions internes qui trouvent le plus grand et le plus petit éléments d'une séquence. min_max calcule les deux et retourne un tuple de deux valeurs.

12-4. Arguments tuples à longueur variable

Les fonctions peuvent prendre un nombre variable d'arguments. Un nom de paramètre qui commence par un symbole * assemble les arguments dans un tuple. Par exemple, printall prend un nombre quelconque d'arguments et les affiche :

 
Sélectionnez
def printall(*args):
    print(args)

Le paramètre d'assemblage peut avoir un nom quelconque, mais par convention on l'appelle souvent args. Voici comment cela fonctionne :

 
Sélectionnez
>>> printall(1, 2.0, '3')
(1, 2.0, '3')

Le complément de l'assemblage est la dispersion. Si vous avez une séquence de valeurs et que vous souhaitez la passer à une fonction sous forme de plusieurs arguments, vous pouvez utiliser l'opérateur *. Par exemple, divmod prend exactement deux arguments ; elle ne fonctionne pas sur un tuple :

 
Sélectionnez
>>> t = (7, 3)
>>> divmod(t)
TypeError: divmod expected 2 arguments, got 1

Mais si vous dispersez le tuple, cela fonctionne :

 
Sélectionnez
>>> divmod(*t)
(2, 1)

Beaucoup de fonctions internes utilisent des arguments tuples à longueur variable. Par exemple, max et min peuvent prendre un nombre quelconque d'arguments :

 
Sélectionnez
>>> max(1, 2, 3)
3

Mais sum ne le fait pas.

 
Sélectionnez
>>> sum(1, 2, 3)
TypeError: sum expected at most 2 arguments, got 3

À titre d'exercice, écrivez une fonction appelée sumall qui prend un nombre quelconque d'arguments et renvoie leur somme.

12-5. Listes et tuples

zip est une fonction interne qui prend deux ou plusieurs séquences et retourne une liste de tuples où chaque tuple contient un élément de chaque séquence. Le nom de la fonction fait allusion à une fermeture-éclair (zip ou zipper en anglais), qui joint et entrelace deux rangées de dents.

Cet exemple zippe une chaîne et une liste :

 
Sélectionnez
>>> s = 'abc'
>>> t = [0, 1, 2]
>>> zip(s, t)
<zip object at 0x7f7d0a9e7c48>

Le résultat est un objet zip qui sait comment parcourir les paires. L'utilisation la plus courante de zip est dans une boucle for :

 
Sélectionnez
>>> for pair in zip(s, t):
...     print(pair)
...
('a', 0)
('b', 1)
('c', 2)

Un objet zip est une sorte d'itérateur, c'est-à-dire un objet qui parcourt une séquence. Les itérateurs ressemblent aux listes à certains égards, mais à la différence des listes, vous ne pouvez pas utiliser un index pour sélectionner un élément d'un itérateur.

Si vous souhaitez utiliser des opérateurs et méthodes de liste, vous pouvez utiliser un objet zip pour faire une liste :

 
Sélectionnez
>>> list(zip(s, t))
[('a', 0), ('b', 1), ('c', 2)]

Le résultat est une liste de tuples ; dans cet exemple, chaque tuple contient un caractère de la chaîne de caractères s et l'élément correspondant de la liste t.

Si les séquences n'ont pas la même longueur, le résultat a la longueur de la séquence la plus courte.

 
Sélectionnez
>>> list(zip('Anne', 'Elk'))
[('A', 'E'), ('n', 'l'), ('n', 'k')]

Vous pouvez utiliser l'affectation de tuple dans une boucle for pour parcourir une liste de tuples :

 
Sélectionnez
t = [('a', 0), ('b', 1), ('c', 2)]
for lettre, nombre in t:
    print(nombre, lettre)

À chaque passage dans la boucle, Python sélectionne le prochain tuple dans la liste et affecte les éléments à lettre et nombre. Cette boucle affiche :

 
Sélectionnez
0 a
1 b
2 c

Si vous combinez zip, for et l'affectation de tuple, vous obtenez une syntaxe utile pour parcourir deux (ou plusieurs) séquences en même temps. Par exemple, a_correspondant prend deux séquences, t1 et t2, et renvoie True s'il existe un indice i tel que t1[i] == t2[i] :

 
Sélectionnez
def a_correspondant(t1, t2):
    for x, y in zip(t1, t2):
        if x == y:
            return True
    return False

Si vous devez parcourir les éléments d'une séquence et de leurs indices, vous pouvez utiliser la fonction interne enumerate :

 
Sélectionnez
for index, element in enumerate('abc'):
    print(index, element)

Le résultat de enumerate est un objet énumération, qui parcourt une séquence de paires ; chaque paire contient un indice (numéroté à partir de 0) et un élément de la séquence donnée. Cet exemple affiche à nouveau :

 
Sélectionnez
0 a
1 b
2 c

12-6. Dictionnaires et tuples

Les dictionnaires ont une méthode appelée items qui renvoie une séquence de tuples, où chaque tuple est une paire clé-valeur.

 
Sélectionnez
>>> d = {'a':0, 'b':1, 'c':2}
>>> t = d.items()
>>> t
dict_items([('c', 2), ('a', 0), ('b', 1)])

Le résultat est un objet dict_items, un itérateur qui parcourt les paires clé-valeur. Vous pouvez l'utiliser dans une boucle for comme ceci :

 
Sélectionnez
>>> for key, value in d.items():
...     print(key, value)
...
c 2
a 0
b 1

Comme vous devriez vous attendre de la part d'un dictionnaire, les éléments ne sont dans aucun ordre particulier.

Dans l'autre sens, vous pouvez utiliser une liste de tuples pour initialiser un nouveau dictionnaire :

 
Sélectionnez
>>> t = [('a', 0), ('c', 2), ('b', 1)]
>>> d = dict(t)
>>> d
{'a': 0, 'c': 2, 'b': 1}

La combinaison entre dict et zip donne une manière concise de créer un dictionnaire :

 
Sélectionnez
>>> d = dict(zip('abc', range(3)))
>>> d
{'a': 0, 'c': 2, 'b': 1}

La méthode de dictionnaire update prend également une liste de tuples et les ajoute, en tant que paires clé-valeur, à un dictionnaire existant.

Il est courant d'utiliser des tuples comme clés dans les dictionnaires (principalement parce que vous ne pouvez pas utiliser des listes). Par exemple, un annuaire téléphonique pourrait établir une correspondance entre des paires nom - prénom et des numéros de téléphone. En supposant que nous avons défini nom, prenom et numero, nous pourrions écrire :

 
Sélectionnez
annuaire[nom, prenom] = numero

L'expression entre crochets est un tuple. Nous pourrions utiliser l'affectation de tuple pour parcourir ce dictionnaire.

 
Sélectionnez
for nom, prenom in annuaire:
    print(nom, prenom, annuaire[nom,prenom])

Cette boucle parcourt les clés de annuaire, qui sont des tuples. Il attribue les éléments de chaque tuple à nom et prenom, puis affiche le nom et le numéro de téléphone correspondant.

Il existe deux façons de représenter les tuples sur un diagramme d'état. La version plus détaillée montre les indices et les éléments de la même façon qu'ils apparaissent dans une liste. Par exemple, le tuple ('Cleese', 'John') apparaît comme dans la figure 12.1.

Image non disponible
Figure 12.1 : Diagramme d'état.

Mais sur un diagramme plus grand, vous pourriez vouloir laisser de côté les détails. Par exemple, un diagramme d'annuaire téléphonique des membres de la troupe des Monty Python pourrait apparaître comme sur la figure 12.2.

Image non disponible
Figure 12.2 : Diagramme d'état.

Ici, les tuples sont représentés en utilisant la syntaxe Python comme un raccourci graphique. Le numéro de téléphone dans le diagramme est le numéro des réclamations de la BBC, inutile de les appeler.

12-7. Séquences de séquences

Je me suis concentré sur les listes de tuples, mais presque tous les exemples de ce chapitre fonctionnent également avec des listes de listes, des tuples de tuples et des tuples de listes. Pour éviter d'énumérer toutes les combinaisons possibles, il est parfois plus facile de parler des séquences de séquences.

Dans de nombreux contextes, les différents types de séquences (chaînes de caractères, listes et tuples) peuvent être utilisés de manière interchangeable. Alors, comment en choisir une par rapport aux autres ?

Pour commencer par le plus évident, les chaînes de caractères sont plus limitées que les autres séquences parce que leurs éléments doivent être des caractères. Elles sont également immuables. Si vous avez besoin de la capacité de modifier les caractères d'une chaîne (par opposition à la création d'une nouvelle chaîne de caractères), vous pouvez utiliser plutôt une liste de caractères.

Les listes sont plus utilisées que les tuples, principalement parce qu'elles sont modifiables. Mais il existe quelques cas où vous pourriez préférer les tuples :

  1. Dans certains contextes, comme une instruction return, il est syntaxiquement plus simple de créer un tuple qu'une liste ;
  2. Si vous souhaitez utiliser une séquence comme une clé de dictionnaire, vous devez utiliser un type immuable comme un tuple ou une chaîne de caractères ;
  3. Si vous passez une séquence comme argument à une fonction, l'utilisation des tuples réduit le risque de comportement inattendu à cause de l'aliasing.

Comme les tuples sont immuables, ils ne fournissent pas de méthodes telles que sort et reverse, qui modifient les listes existantes. Mais Python fournit la fonction interne sorted, qui prend une séquence quelconque et renvoie une nouvelle liste avec les mêmes éléments triés, et reversed, qui prend une séquence et retourne un itérateur qui parcourt la liste dans l'ordre inverse.

12-8. Débogage

Les listes, les dictionnaires et les tuples sont des exemples de structures de données ; dans ce chapitre, nous commençons à découvrir les structures composées de données, comme les listes de tuples, ou les dictionnaires qui contiennent des tuples comme clés et des listes comme valeurs. Les structures composées de données sont utiles, mais elles sont sujettes à ce que j'appelle des erreurs de forme, c'est-à-dire des erreurs provenant du fait qu'une structure de données n'a pas le bon type, la bonne taille ou la bonne constitution. Par exemple, si vous vous attendez à une liste contenant un nombre entier et je vous donne un simple entier (qui ne se trouve pas dans une liste), cela ne fonctionnera pas.

Pour vous aider à déboguer ces types d'erreurs, j'ai écrit un module appelé structshape qui fournit une fonction, appelée également structshape, qui prend comme argument un type quelconque de structure de données et renvoie une chaîne de caractères qui résume son format. Vous pouvez le télécharger à l'adresse structshape.py.

Voici un exemple de résultat pour une liste simple :

 
Sélectionnez
>>> from structshape import structshape
>>> t = [1, 2, 3]
>>> structshape(t)
'list of 3 int'

Un programme plus élégant pourrait écrire « liste de 3 ints », mais il était plus facile de ne pas traiter les pluriels. Voici une liste de listes :

 
Sélectionnez
>>> t2 = [[1,2], [3,4], [5,6]]
>>> structshape(t2)
'list of 3 list of 2 int'

Si les éléments de la liste ne sont pas du même type, structshape les groupe, dans l'ordre, par type :

 
Sélectionnez
>>> t3 = [1, 2, 3, 4.0, '5', '6', [7], [8], 9]
>>> structshape(t3)
'list of (3 int, float, 2 str, 2 list of int, int)'

Voici une liste de tuples :

 
Sélectionnez
>>> s = 'abc'
>>> lt = list(zip(t, s))
>>> structshape(lt)
'list of 3 tuple of (int, str)'

Et voici un dictionnaire avec trois éléments qui font correspondre des entiers à des chaînes de caractères :

 
Sélectionnez
>>> d = dict(lt) 
>>> structshape(d)
'dict of 3 int->str'

Si vous éprouvez des difficultés à vous y retrouver dans vos structures de données, structshape peut vous aider.

12-9. Glossaire

  • tuple : une séquence immuable d'éléments.
  • affectation de tuple : une affectation ayant une séquence du côté droit et un tuple de variables du côté gauche. Le côté droit est évalué, puis ses éléments sont affectés aux variables du côté gauche.
  • assembler : l'opération d'assemblage d'un tuple de longueur variable passé comme argument.
  • disperser : l'opération de traitement d'une séquence comme une liste d'arguments.
  • objet zip : le résultat de l'appel de la fonction interne zip ; un objet qui parcourt une séquence de tuples.
  • itérateur : un objet qui peut parcourir une séquence, mais qui ne fournit pas des opérateurs et des méthodes de liste.
  • structure de données : une collection de valeurs apparentées, souvent organisées en listes, dictionnaires, tuples, etc.
  • erreur de forme : une erreur provoquée parce qu'une valeur a la mauvaise forme, c'est-à-dire le mauvais type ou la mauvaise taille.

12-10. Exercices

Exercice 1

Écrivez une fonction appelée most_frequent qui prend une chaîne de caractères et affiche les lettres dans l'ordre décroissant de leur fréquence. Trouvez des échantillons de texte dans plusieurs langues différentes et analysez comment varie la fréquence des lettres d'une langue à l'autre. Comparez vos résultats aux tables à l'adresse https://fr.wikipedia.org/wiki/Fr%C3%A9quence_d%27apparition_des_lettres_en_fran%C3%A7ais.

Solution : most_frequent.py.

Exercice 2

Encore des anagrammes !

  1. Écrivez un programme qui lit le fichier mots.txt et imprime tous les groupes de mots qui forment des anagrammes.
    Voici à quoi pourrait ressembler la sortie :

     
    Sélectionnez
    ['ARGENT', 'GERANT', 'GRENAT', 'GARENT', 'RAGENT', 'GANTER']
    ['CRANE', 'ECRAN', 'NACRE', 'CARNE', 'RANCE', 'ANCRE', 'ENCRA', 'CANER', 'CERNA']
    ['PLATINE', 'PATELIN', 'PLAINTE', 'EPILANT', 'PLIANTE']

    Indice : vous pourriez vouloir construire un dictionnaire qui établit des correspondances entre une collection de lettres et une liste de mots contenant ces lettres. La question est : comment pouvez-vous représenter la collection de lettres de façon qu'elle puisse être utilisée comme une clé ? Notre liste mots.txt renferme plus de 2000 groupes de lettres formant des anagrammes (NdT).

  2. Modifiez le programme précédent de sorte qu'il imprime la plus longue liste d'anagrammes en premier suivie par la deuxième la plus longue, et ainsi de suite. Indice : les listes les plus longues sont quatre groupes de lettres formant chacun neuf anagrammes (NdT).

  3. Au jeu de Scrabble, vous réalisez un « scrabble » quand vous jouez les sept jetons de votre chevalet, avec généralement une lettre du plateau, pour former un mot de huit lettres. Quelle collection de huit lettres forme le plus de scrabbles possibles ? Indice : il y a deux collections de huit lettres permettant de former chacune huit scrabbles (NdT).

Solution : anagram_sets.py.

Exercice 3

Deux mots forment une « paire par métathèse » si vous pouvez transformer l'un dans l'autre en inversant la position d'un groupe de deux lettres ; par exemple, « CONVERSER » et « CONSERVER ». Écrivez un programme qui trouve toutes les paires par métathèse du document mots.txt. Indice : ne testez pas toutes les paires de mots, et ne testez pas toutes les inversions possibles. Indice 2 : ces mots sont aussi des anagrammes, vous pourriez partir des solutions de l'exercice 1. Solution : metathesis.py. Référence : Cet exercice est inspiré par un exemple sur http://puzzlers.org.

Exercice 4

Voici un autre casse-tête provenant de Car Talk (http://www.cartalk.com/content/puzzlers) :

  • Quel est le mot français le plus long, qui reste toujours un mot français valide au fur et à mesure que vous retirez ses lettres une par une ?
    Les lettres peuvent être retirées de chaque extrémité, ou du milieu, mais vous ne pouvez pas réarranger l'ordre des lettres. Chaque fois que vous supprimez une lettre, vous obtenez un autre mot français. Si vous faites cela, finalement il vous reste une seule lettre qui sera également un mot français que l'on retrouve dans le dictionnaire (même si notre liste de mots pour le Scrabble ne contient pas de mots d'une seule lettre). Je veux savoir : quel est le mot le plus long et combien de lettres a-t-il ?
    Prenons un petit exemple modeste : « VIEILLE ». D'accord ? Vous commencez avec « VIEILLE », vous supprimez une lettre de l'intérieur du mot, le premier I, et nous nous retrouvons avec le mot « VEILLE », puis nous enlevons le premier E, nous nous retrouvons avec « VILLE », nous supprimons un L, il nous reste « VILE », puis « ILE » (ou « VIL »), « IL » (ou « LE » si on a pris « ILE »)) et « I » ou « L ».

Écrivez un programme pour trouver tous les mots qui peuvent être raccourcis de cette manière, et ensuite trouvez-en le plus long.

Cet exercice est un peu plus difficile que les précédents, alors voici quelques suggestions :

  1. Vous pourriez écrire une fonction qui prend un mot et calcule une liste de tous les mots qui peuvent être formés en supprimant une lettre. Ce sont les « enfants » du mot.
  2. De façon récursive, un mot est réductible si l'un de ses enfants est réductible. Comme un cas de base, vous pouvez considérer la chaîne vide comme étant réductible.
  3. La liste de mots fournis, mots.txt , ne contient aucun mot d'une lettre. Donc, vous voudrez peut-être y ajouter toutes les lettres de l'alphabet (en capitales) et la chaîne vide, que l'on considérera pour les besoins de la recherche comme autant de mots valides.
  4. Pour améliorer les performances de votre programme, vous voudrez peut-être « mémoïser » les mots qui sont déjà connus comme réductibles.

Indice : dans notre liste de mots (de 15 lettres au maximum), trois mots de 13 lettres peuvent être réduits ainsi à une seule lettre (NdT).

Solution : reducible.py.


précédentsommairesuivant

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Licence Creative Commons
Le contenu de cet article est rédigé par Allen B. Downey et est mis à disposition selon les termes de la Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 3.0 non transposé.
Les logos Developpez.com, en-tête, pied de page, css, et look & feel de l'article sont Copyright © 2016 Developpez.com.