5. Structures de données
************************

Ce chapitre reprend plus en détail quelques points déjà décrits
précédemment et introduit également de nouvelles notions.


5.1. Compléments sur les listes
===============================

Le type liste dispose de méthodes supplémentaires. Voici toutes les
méthodes des objets liste :

list.append(x)

   Ajoute un élément à la fin de la liste. Équivalent à "a[len(a):] =
   [x]".

list.extend(iterable)

   Étend la liste en y ajoutant tous les éléments de l'itérable.
   Équivalent à "a[len(a):] = iterable".

list.insert(i, x)

   Insère un élément à la position indiquée. Le premier argument est
   la position de l'élément avant lequel l'insertion doit s'effectuer,
   donc "a.insert(0, x)" insère l'élément en tête de la liste et
   "a.insert(len(a), x)" est équivalent à "a.append(x)".

list.remove(x)

   Supprime de la liste le premier élément dont la valeur est égale à
   *x*. Une exception "ValueError" est levée s'il n'existe aucun
   élément avec cette valeur.

list.pop([i])

   Enlève de la liste l'élément situé à la position indiquée et le
   renvoie en valeur de retour. Si aucune position n'est spécifiée,
   "a.pop()" enlève et renvoie le dernier élément de la liste (les
   crochets autour du *i* dans la signature de la méthode indiquent
   que ce paramètre est facultatif et non que vous devez placer des
   crochets dans votre code ! Vous retrouverez cette notation
   fréquemment dans le Guide de Référence de la Bibliothèque Python).

list.clear()

   Supprime tous les éléments de la liste. Équivalent à "del a[:]".

list.index(x[, start[, end]])

   Renvoie la position du premier élément de la liste dont la valeur
   égale *x* (en commençant à compter les positions à partir de zéro).
   Une exception "ValueError" est levée si aucun élément n'est trouvé.

   Les arguments optionnels *start* et *end* sont interprétés de la
   même manière que dans la notation des tranches et sont utilisés
   pour limiter la recherche à une sous-séquence particulière.
   L'indice renvoyé est calculé relativement au début de la séquence
   complète et non relativement à *start*.

list.count(x)

   Renvoie le nombre d'éléments ayant la valeur *x* dans la liste.

list.sort(*, key=None, reverse=False)

   Ordonne les éléments dans la liste (les arguments peuvent
   personnaliser l'ordonnancement, voir "sorted()" pour leur
   explication).

list.reverse()

   Inverse l'ordre des éléments dans la liste.

list.copy()

   Renvoie une copie superficielle de la liste. Équivalent à "a[:]".

L'exemple suivant utilise la plupart des méthodes des listes :

   >>> fruits = ['orange', 'apple', 'pear', 'banana', 'kiwi', 'apple', 'banana']
   >>> fruits.count('apple')
   2
   >>> fruits.count('tangerine')
   0
   >>> fruits.index('banana')
   3
   >>> fruits.index('banana', 4)  # Find next banana starting a position 4
   6
   >>> fruits.reverse()
   >>> fruits
   ['banana', 'apple', 'kiwi', 'banana', 'pear', 'apple', 'orange']
   >>> fruits.append('grape')
   >>> fruits
   ['banana', 'apple', 'kiwi', 'banana', 'pear', 'apple', 'orange', 'grape']
   >>> fruits.sort()
   >>> fruits
   ['apple', 'apple', 'banana', 'banana', 'grape', 'kiwi', 'orange', 'pear']
   >>> fruits.pop()
   'pear'

Vous avez probablement remarqué que les méthodes qui ne font que
modifier la liste, comme "insert", "remove" ou "sort", n'affichent pas
de valeur de retour (elles renvoient "None") [1]. C'est un principe
respecté par toutes les structures de données muables en Python.

Vous avez peut-être remarqué aussi que certaines données ne peuvent
pas être ordonnées ni comparées. Par exemple, la liste "[None,
'hello', 10]" ne peut pas être ordonnée parce que les entiers ne
peuvent pas être comparés aux chaînes de caractères et *None* ne peut
pas être comparé à d'autres types. En outre, il existe certains types
qui n'ont pas de relation d'ordre définie. Par exemple, "3+4j < 5+7j"
n'est pas une comparaison valide.


5.1.1. Utilisation des listes comme des piles
---------------------------------------------

Les méthodes des listes rendent très facile leur utilisation comme des
piles, où le dernier élément ajouté est le premier récupéré (« dernier
entré, premier sorti » ou LIFO pour *last-in, first-out* en anglais).
Pour ajouter un élément sur la pile, utilisez la méthode "append()".
Pour récupérer l'objet au sommet de la pile, utilisez la méthode
"pop()" sans indicateur de position. Par exemple :

   >>> stack = [3, 4, 5]
   >>> stack.append(6)
   >>> stack.append(7)
   >>> stack
   [3, 4, 5, 6, 7]
   >>> stack.pop()
   7
   >>> stack
   [3, 4, 5, 6]
   >>> stack.pop()
   6
   >>> stack.pop()
   5
   >>> stack
   [3, 4]


5.1.2. Utilisation des listes comme des files
---------------------------------------------

Il est également possible d'utiliser une liste comme une file, où le
premier élément ajouté est le premier récupéré (« premier entré,
premier sorti » ou FIFO pour *first-in, first-out*) ; toutefois, les
listes ne sont pas très efficaces pour réaliser ce type de traitement.
Alors que les ajouts et suppressions en fin de liste sont rapides, les
insertions ou les retraits en début de liste sont lents (car tous les
autres éléments doivent être décalés d'une position).

Pour implémenter une file, utilisez plutôt la classe
"collections.deque" qui a été conçue spécialement pour réaliser
rapidement les opérations d'ajout et de retrait aux deux extrémités.
Par exemple :

   >>> from collections import deque
   >>> queue = deque(["Eric", "John", "Michael"])
   >>> queue.append("Terry")           # Terry arrives
   >>> queue.append("Graham")          # Graham arrives
   >>> queue.popleft()                 # The first to arrive now leaves
   'Eric'
   >>> queue.popleft()                 # The second to arrive now leaves
   'John'
   >>> queue                           # Remaining queue in order of arrival
   deque(['Michael', 'Terry', 'Graham'])


5.1.3. Listes en compréhension
------------------------------

Les listes en compréhension fournissent un moyen de construire des
listes de manière très concise. Une application classique consiste à
construire une liste dont les éléments sont les résultats d'une
opération appliquée à chaque élément d'une autre séquence ; une autre
consiste à créer une sous-séquence des éléments respectant une
condition donnée.

Par exemple, supposons que l'on veuille créer une liste de carrés,
comme :

   >>> squares = []
   >>> for x in range(10):
   ...     squares.append(x**2)
   ...
   >>> squares
   [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

Notez que cela crée (ou remplace) une variable nommée "x" qui existe
toujours après l'exécution de la boucle. On peut calculer une liste de
carrés sans effet de bord avec :

   squares = list(map(lambda x: x**2, range(10)))

ou, de manière équivalente :

   squares = [x**2 for x in range(10)]

ce qui est plus court et lisible.

Une liste en compréhension consiste à placer entre crochets une
expression suivie par une clause "for" puis par zéro ou plus clauses
"for" ou "if". Le résultat est une nouvelle liste résultat de
l'évaluation de l'expression dans le contexte des clauses "for" et
"if" qui la suivent. Par exemple, cette compréhension de liste combine
les éléments de deux listes s'ils ne sont pas égaux :

   >>> [(x, y) for x in [1,2,3] for y in [3,1,4] if x != y]
   [(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]

et c'est équivalent à :

   >>> combs = []
   >>> for x in [1,2,3]:
   ...     for y in [3,1,4]:
   ...         if x != y:
   ...             combs.append((x, y))
   ...
   >>> combs
   [(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]

Notez que l'ordre des instructions "for" et "if" est le même dans ces
différents extraits de code.

Si l'expression est un *n*-uplet (c'est-à-dire "(x, y)" dans cet
exemple), elle doit être mise entre parenthèses :

   >>> vec = [-4, -2, 0, 2, 4]
   >>> # create a new list with the values doubled
   >>> [x*2 for x in vec]
   [-8, -4, 0, 4, 8]
   >>> # filter the list to exclude negative numbers
   >>> [x for x in vec if x >= 0]
   [0, 2, 4]
   >>> # apply a function to all the elements
   >>> [abs(x) for x in vec]
   [4, 2, 0, 2, 4]
   >>> # call a method on each element
   >>> freshfruit = ['  banana', '  loganberry ', 'passion fruit  ']
   >>> [weapon.strip() for weapon in freshfruit]
   ['banana', 'loganberry', 'passion fruit']
   >>> # create a list of 2-tuples like (number, square)
   >>> [(x, x**2) for x in range(6)]
   [(0, 0), (1, 1), (2, 4), (3, 9), (4, 16), (5, 25)]
   >>> # the tuple must be parenthesized, otherwise an error is raised
   >>> [x, x**2 for x in range(6)]
     File "<stdin>", line 1
       [x, x**2 for x in range(6)]
        ^^^^^^^
   SyntaxError: did you forget parentheses around the comprehension target?
   >>> # flatten a list using a listcomp with two 'for'
   >>> vec = [[1,2,3], [4,5,6], [7,8,9]]
   >>> [num for elem in vec for num in elem]
   [1, 2, 3, 4, 5, 6, 7, 8, 9]

Les listes en compréhension peuvent contenir des expressions complexes
et des fonctions imbriquées :

   >>> from math import pi
   >>> [str(round(pi, i)) for i in range(1, 6)]
   ['3.1', '3.14', '3.142', '3.1416', '3.14159']


5.1.4. Listes en compréhensions imbriquées
------------------------------------------

La première expression dans une liste en compréhension peut être
n'importe quelle expression, y compris une autre liste en
compréhension.

Voyez l'exemple suivant d'une matrice de 3 par 4, implémentée sous la
forme de 3 listes de 4 éléments :

   >>> matrix = [
   ...     [1, 2, 3, 4],
   ...     [5, 6, 7, 8],
   ...     [9, 10, 11, 12],
   ... ]

Cette compréhension de liste transpose les lignes et les colonnes :

   >>> [[row[i] for row in matrix] for i in range(4)]
   [[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

Comme nous l'avons vu dans la section précédente, la liste en
compréhension imbriquée est évaluée dans le contexte de l'instruction
"for" qui la suit, donc cet exemple est équivalent à :

   >>> transposed = []
   >>> for i in range(4):
   ...     transposed.append([row[i] for row in matrix])
   ...
   >>> transposed
   [[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

lequel est lui-même équivalent à :

   >>> transposed = []
   >>> for i in range(4):
   ...     # the following 3 lines implement the nested listcomp
   ...     transposed_row = []
   ...     for row in matrix:
   ...         transposed_row.append(row[i])
   ...     transposed.append(transposed_row)
   ...
   >>> transposed
   [[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

Dans des cas concrets, il est toujours préférable d'utiliser des
fonctions natives plutôt que des instructions de contrôle de flux
complexes. La fonction "zip()" fait dans ce cas un excellent travail :

   >>> list(zip(*matrix))
   [(1, 5, 9), (2, 6, 10), (3, 7, 11), (4, 8, 12)]

Voir Séparation des listes d'arguments pour plus de détails sur
l'astérisque de cette ligne.


5.2. L'instruction "del"
========================

Il existe un moyen de retirer un élément d'une liste à partir de sa
position au lieu de sa valeur : l'instruction "del". Elle diffère de
la méthode "pop()" qui, elle, renvoie une valeur. L'instruction "del"
peut également être utilisée pour supprimer des tranches d'une liste
ou la vider complètement (ce que nous avions fait auparavant en
affectant une liste vide à la tranche). Par exemple :

   >>> a = [-1, 1, 66.25, 333, 333, 1234.5]
   >>> del a[0]
   >>> a
   [1, 66.25, 333, 333, 1234.5]
   >>> del a[2:4]
   >>> a
   [1, 66.25, 1234.5]
   >>> del a[:]
   >>> a
   []

"del" peut aussi être utilisée pour supprimer des variables :

   >>> del a

À partir de là, référencer le nom "a" est une erreur (au moins jusqu'à
ce qu'une autre valeur lui soit affectée). Vous trouverez d'autres
utilisations de la fonction "del" dans la suite de ce tutoriel.


5.3. *n*-uplets et séquences
============================

Nous avons vu que les listes et les chaînes de caractères ont beaucoup
de propriétés en commun, comme l'indiçage et les opérations sur des
tranches. Ce sont deux exemples de *séquences* (voir Types séquentiels
— list, tuple, range). Comme Python est un langage en constante
évolution, d'autres types de séquences y seront peut-être ajoutés. Il
existe également un autre type standard de séquence : le *n*-uplet
(*tuple* en anglais).

Un *n*-uplet consiste en différentes valeurs séparées par des
virgules, par exemple :

   >>> t = 12345, 54321, 'hello!'
   >>> t[0]
   12345
   >>> t
   (12345, 54321, 'hello!')
   >>> # Tuples may be nested:
   ... u = t, (1, 2, 3, 4, 5)
   >>> u
   ((12345, 54321, 'hello!'), (1, 2, 3, 4, 5))
   >>> # Tuples are immutable:
   ... t[0] = 88888
   Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
   TypeError: 'tuple' object does not support item assignment
   >>> # but they can contain mutable objects:
   ... v = ([1, 2, 3], [3, 2, 1])
   >>> v
   ([1, 2, 3], [3, 2, 1])

Comme vous pouvez le voir, les *n*-uplets sont toujours affichés entre
parenthèses, de façon à ce que des *n*-uplets imbriqués soient
interprétés correctement ; ils peuvent être saisis avec ou sans
parenthèses, même si celles-ci sont souvent nécessaires (notamment
lorsqu'un *n*-uplet fait partie d'une expression plus longue). Il
n'est pas possible d'affecter de valeur à un élément d'un *n*-uplet ;
par contre, il est en revanche possible de créer des *n*-uplets
contenant des objets muables, comme des listes.

Si les *n*-uplets peuvent sembler similaires aux listes, ils sont
souvent utilisés dans des cas différents et pour des raisons
différentes. Les *n*-uplets sont *immuables* et contiennent souvent
des séquences hétérogènes d'éléments auxquelles on accède par «
dissociation » (*unpacking* en anglais, voir plus loin) ou par indice
(ou même par attribut dans le cas des "namedtuples"). Les listes sont
souvent *muables* et contiennent des éléments généralement homogènes
auxquels on accède en itérant sur la liste.

La construction de *n*-uplets ne contenant aucun ou un seul élément
est un cas particulier : la syntaxe a quelques tournures spécifiques
pour le gérer. Un *n*-uplet vide se construit avec une paire de
parenthèses vides ; un *n*-uplet avec un seul élément se construit en
faisant suivre la valeur par une virgule (placer cette valeur entre
parenthèses ne suffit pas). Pas très joli, mais efficace. Par exemple
:

   >>> empty = ()
   >>> singleton = 'hello',    # <-- note trailing comma
   >>> len(empty)
   0
   >>> len(singleton)
   1
   >>> singleton
   ('hello',)

L'instruction "t = 12345, 54321, 'hello!'" est un exemple
*d'agrégation de *n*-uplet* (*tuple packing* en anglais) : les valeurs
"12345", "54321" et "hello!" sont agrégées ensemble dans un *n*-uplet.
L'opération inverse est aussi possible :

   >>> x, y, z = t

Ceci est appelé, de façon plus ou moins appropriée, une *dissociation
de séquence* (*sequence unpacking* en anglais) et fonctionne pour
toute séquence placée à droite de l'expression. Cette dissociation
requiert autant de variables dans la partie gauche qu'il y a
d'éléments dans la séquence. Notez également que cette affectation
multiple est juste une combinaison entre une agrégation de *n*-uplet
et une dissociation de séquence.


5.4. Ensembles
==============

Python fournit également un type de donnée pour les *ensembles*. Un
ensemble est une collection non ordonnée sans éléments en double. Un
ensemble permet de réaliser des tests d'appartenance ou des
suppressions de doublons de manière simple. Les ensembles savent
également effectuer les opérations mathématiques telles que les
unions, intersections, différences et différences symétriques.

On crée des ensembles en appelant avec des accolades ou avec la
fonction "set()". Notez que "{}" ne crée pas un ensemble vide, mais un
dictionnaire (une structure de données dont nous allons parler dans la
séquence suivante) vide ; utilisez "set()" pour ce cas.

Voici une brève démonstration :

   >>> basket = {'apple', 'orange', 'apple', 'pear', 'orange', 'banana'}
   >>> print(basket)                      # show that duplicates have been removed
   {'orange', 'banana', 'pear', 'apple'}
   >>> 'orange' in basket                 # fast membership testing
   True
   >>> 'crabgrass' in basket
   False

   >>> # Demonstrate set operations on unique letters from two words
   ...
   >>> a = set('abracadabra')
   >>> b = set('alacazam')
   >>> a                                  # unique letters in a
   {'a', 'r', 'b', 'c', 'd'}
   >>> a - b                              # letters in a but not in b
   {'r', 'd', 'b'}
   >>> a | b                              # letters in a or b or both
   {'a', 'c', 'r', 'd', 'b', 'm', 'z', 'l'}
   >>> a & b                              # letters in both a and b
   {'a', 'c'}
   >>> a ^ b                              # letters in a or b but not both
   {'r', 'd', 'b', 'm', 'z', 'l'}

Tout comme pour les listes en compréhension, il est possible d'écrire
des ensembles en compréhension :

   >>> a = {x for x in 'abracadabra' if x not in 'abc'}
   >>> a
   {'r', 'd'}


5.5. Dictionnaires
==================

Un autre type de donnée très utile, natif dans Python, est le
*dictionnaire* (voir Les types de correspondances — dict). Ces
dictionnaires sont parfois présents dans d'autres langages sous le nom
de « mémoires associatives » ou de « tableaux associatifs ». À la
différence des séquences, qui sont indexées par des nombres, les
dictionnaires sont indexés par des *clés*, qui peuvent être de
n'importe quel type immuable ; les chaînes de caractères et les
nombres peuvent toujours être des clés. Des *n*-uplets peuvent être
utilisés comme clés s'ils ne contiennent que des chaînes, des nombres
ou des *n*-uplets ; si un *n*-uplet contient un objet muable, de façon
directe ou indirecte, il ne peut pas être utilisé comme une clé. Vous
ne pouvez pas utiliser des listes comme clés, car les listes peuvent
être modifiées en place en utilisant des affectations par position,
par tranches ou via des méthodes comme "append()" ou "extend()".

Le plus simple est de voir un dictionnaire comme un ensemble de paires
*clé-valeur* au sein duquel les clés doivent être uniques. Une paire
d'accolades crée un dictionnaire vide : "{}". Placer une liste de
paires clé-valeur séparées par des virgules à l'intérieur des
accolades ajoute les valeurs correspondantes au dictionnaire ; c'est
également de cette façon que les dictionnaires sont affichés.

Les opérations classiques sur un dictionnaire consistent à stocker une
valeur pour une clé et à extraire la valeur correspondant à une clé.
Il est également possible de supprimer une paire clé-valeur avec
"del". Si vous stockez une valeur pour une clé qui est déjà utilisée,
l'ancienne valeur associée à cette clé est perdue. Si vous tentez
d'extraire une valeur associée à une clé qui n'existe pas, une
exception est levée.

Exécuter "list(d)" sur un dictionnaire "d" renvoie une liste de toutes
les clés utilisées dans le dictionnaire, dans l'ordre d'insertion (si
vous voulez qu'elles soient ordonnées, utilisez "sorted(d)"). Pour
tester si une clé est dans le dictionnaire, utilisez le mot-clé "in".

Voici un petit exemple utilisant un dictionnaire :

   >>> tel = {'jack': 4098, 'sape': 4139}
   >>> tel['guido'] = 4127
   >>> tel
   {'jack': 4098, 'sape': 4139, 'guido': 4127}
   >>> tel['jack']
   4098
   >>> del tel['sape']
   >>> tel['irv'] = 4127
   >>> tel
   {'jack': 4098, 'guido': 4127, 'irv': 4127}
   >>> list(tel)
   ['jack', 'guido', 'irv']
   >>> sorted(tel)
   ['guido', 'irv', 'jack']
   >>> 'guido' in tel
   True
   >>> 'jack' not in tel
   False

Le constructeur "dict()" fabrique un dictionnaire directement à partir
d'une liste de paires clé-valeur stockées sous la forme de *n*-uplets
:

   >>> dict([('sape', 4139), ('guido', 4127), ('jack', 4098)])
   {'sape': 4139, 'guido': 4127, 'jack': 4098}

De plus, il est possible de créer des dictionnaires en compréhension
depuis un jeu de clés et valeurs :

   >>> {x: x**2 for x in (2, 4, 6)}
   {2: 4, 4: 16, 6: 36}

Lorsque les clés sont de simples chaînes de caractères, il est parfois
plus facile de définir les paires en utilisant des paramètres nommés :

   >>> dict(sape=4139, guido=4127, jack=4098)
   {'sape': 4139, 'guido': 4127, 'jack': 4098}


5.6. Techniques de boucles
==========================

Lorsque vous faites une boucle sur un dictionnaire, la clé et la
valeur associée peuvent être récupérées en même temps en utilisant la
méthode "items()" :

   >>> knights = {'gallahad': 'the pure', 'robin': 'the brave'}
   >>> for k, v in knights.items():
   ...     print(k, v)
   ...
   gallahad the pure
   robin the brave

Lorsque vous faites une boucle sur une séquence, la position et la
valeur associée peuvent être récupérées en même temps en utilisant la
fonction "enumerate()".

   >>> for i, v in enumerate(['tic', 'tac', 'toe']):
   ...     print(i, v)
   ...
   0 tic
   1 tac
   2 toe

Pour faire une boucle sur deux séquences ou plus en même temps, les
éléments peuvent être associés en utilisant la fonction "zip()" :

   >>> questions = ['name', 'quest', 'favorite color']
   >>> answers = ['lancelot', 'the holy grail', 'blue']
   >>> for q, a in zip(questions, answers):
   ...     print('What is your {0}?  It is {1}.'.format(q, a))
   ...
   What is your name?  It is lancelot.
   What is your quest?  It is the holy grail.
   What is your favorite color?  It is blue.

Pour faire une boucle en sens inverse sur une séquence, commencez par
spécifier la séquence dans son ordre normal, puis appliquez la
fonction "reversed()" :

   >>> for i in reversed(range(1, 10, 2)):
   ...     print(i)
   ...
   9
   7
   5
   3
   1

Pour faire une boucle sur une séquence de manière ordonnée, utilisez
la fonction "sorted()" qui renvoie une nouvelle liste ordonnée sans
altérer la source :

   >>> basket = ['apple', 'orange', 'apple', 'pear', 'orange', 'banana']
   >>> for i in sorted(basket):
   ...     print(i)
   ...
   apple
   apple
   banana
   orange
   orange
   pear

L'utilisation de la fonction "set()" sur une séquence élimine les
doublons. Combiner les fonctions "sorted()" et "set()" sur une
séquence est la façon « canonique » d'itérer sur les éléments uniques
d'une séquence dans l'ordre. :

   >>> basket = ['apple', 'orange', 'apple', 'pear', 'orange', 'banana']
   >>> for f in sorted(set(basket)):
   ...     print(f)
   ...
   apple
   banana
   orange
   pear

Il est parfois tentant de modifier une liste pendant que l'on itère
dessus. Il est souvent plus simple et plus sûr de créer une nouvelle
liste à la place. :

   >>> import math
   >>> raw_data = [56.2, float('NaN'), 51.7, 55.3, 52.5, float('NaN'), 47.8]
   >>> filtered_data = []
   >>> for value in raw_data:
   ...     if not math.isnan(value):
   ...         filtered_data.append(value)
   ...
   >>> filtered_data
   [56.2, 51.7, 55.3, 52.5, 47.8]


5.7. Plus d'informations sur les conditions
===========================================

Les conditions utilisées dans une instruction "while" ou "if" peuvent
contenir n'importe quel opérateur, pas seulement des comparaisons.

Les opérateurs de comparaison "in" et "not in" testent si une valeur
appartient (ou pas) à un conteneur. Les opérateurs "is" et "is not"
testent si deux objets sont vraiment le même objet. Tous les
opérateurs de comparaison ont la même priorité, qui est plus faible
que celle des opérateurs numériques.

Les comparaisons peuvent être chaînées. Par exemple, "a < b == c"
teste si "a" est inférieur à "b" et si, de plus, "b" égale "c".

Les comparaisons peuvent être combinées en utilisant les opérateurs
booléens "and" et "or", le résultat d'une comparaison (ou de toute
expression booléenne) pouvant être inversé avec "not". Ces opérateurs
ont une priorité inférieure à celle des opérateurs de comparaison ;
entre eux, "not" a la priorité la plus élevée et "or" la plus faible,
de telle sorte que "A and not B or C" est équivalent à "(A and (not
B)) or C". Comme d'habitude, les parenthèses permettent d'exprimer
l'instruction désirée.

Les opérateurs booléens "and" et "or" sont appelés opérateurs *en
circuit court* : leurs arguments sont évalués de la gauche vers la
droite et l'évaluation s'arrête dès que le résultat est déterminé. Par
exemple, si "A" et "C" sont vrais et "B" est faux, "A and B and C"
n'évalue pas l'expression "C". Lorsqu'elle est utilisée en tant que
valeur et non en tant que booléen, la valeur de retour d'un opérateur
en circuit court est celle du dernier argument évalué.

Il est possible d'affecter le résultat d'une comparaison ou d'une
autre expression booléenne à une variable. Par exemple :

   >>> string1, string2, string3 = '', 'Trondheim', 'Hammer Dance'
   >>> non_null = string1 or string2 or string3
   >>> non_null
   'Trondheim'

Notez qu'en Python, à la différence du C, une affectation à
l'intérieur d'une expression doit être faite explicitement avec
l'opérateur morse ":=". Cela évite des erreurs fréquentes que l'on
rencontre en C, lorsque l'on tape "=" alors que l'on voulait faire un
test avec "==".


5.8. Comparer des séquences avec d'autres types
===============================================

Des séquences peuvent être comparées avec d'autres séquences du même
type. La comparaison utilise un ordre *lexicographique* : les deux
premiers éléments de chaque séquence sont comparés et, s'ils
diffèrent, cela détermine le résultat de la comparaison ; s'ils sont
égaux, les deux éléments suivants sont comparés à leur tour et ainsi
de suite jusqu'à ce que l'une des séquences soit épuisée. Si deux
éléments à comparer sont eux-mêmes des séquences du même type, alors
la comparaison lexicographique est effectuée récursivement. Si tous
les éléments des deux séquences sont égaux, les deux séquences sont
alors considérées comme égales. Si une séquence est une sous-séquence
de l'autre, la séquence la plus courte est celle dont la valeur est
inférieure. La comparaison lexicographique des chaînes de caractères
utilise le code Unicode des caractères. Voici quelques exemples de
comparaisons entre séquences de même type :

   (1, 2, 3)              < (1, 2, 4)
   [1, 2, 3]              < [1, 2, 4]
   'ABC' < 'C' < 'Pascal' < 'Python'
   (1, 2, 3, 4)           < (1, 2, 4)
   (1, 2)                 < (1, 2, -1)
   (1, 2, 3)             == (1.0, 2.0, 3.0)
   (1, 2, ('aa', 'ab'))   < (1, 2, ('abc', 'a'), 4)

Comparer des objets de type différents avec "<" ou ">" est autorisé si
les objets ont des méthodes de comparaison appropriées. Par exemple,
les types numériques sont comparés via leur valeur numérique, donc "0"
égale "0,0", etc. Dans les autres cas, au lieu de donner un ordre
imprévisible, l'interpréteur lève une exception "TypeError".

-[ Notes ]-

[1] D'autres langages renvoient l'objet modifié, ce qui permet de
    chaîner les méthodes comme ceci :
    "d->insert("a")->remove("b")->sort();".
