0% ont trouvé ce document utile (0 vote)
2 vues11 pages

Différences clés entre Python 2 et 3

Ce chapitre aborde divers points avancés sur Python, notamment les différences entre Python 2 et Python 3, les méthodes de formatage des chaînes, et les fonctions lambda. Il souligne l'importance d'utiliser Python 3, en expliquant des éléments clés comme la fonction print(), la division d'entiers, et les fonctions range() et zip(). Enfin, il présente les anciennes méthodes de formatage des chaînes et recommande l'utilisation des f-strings pour une syntaxe plus claire.

Transféré par

a.fabrici
Copyright
© All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd
0% ont trouvé ce document utile (0 vote)
2 vues11 pages

Différences clés entre Python 2 et 3

Ce chapitre aborde divers points avancés sur Python, notamment les différences entre Python 2 et Python 3, les méthodes de formatage des chaînes, et les fonctions lambda. Il souligne l'importance d'utiliser Python 3, en expliquant des éléments clés comme la fonction print(), la division d'entiers, et les fonctions range() et zip(). Enfin, il présente les anciennes méthodes de formatage des chaînes et recommande l'utilisation des f-strings pour une syntaxe plus claire.

Transféré par

a.fabrici
Copyright
© All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd

CHAPITRE 26

Remarques complémentaires

Dans ce chapitre, nous présentons un certain nombre de points en vrac qui ne rentraient pas forcément dans les
autres chapitres ou qui étaient trop avancés au moment où les chapitres étaient abordés. Outre quelques points mineurs,
nous abordons les grandes différences entre Python 2 et Python 3, les anciennes méthodes de formatage des chaînes
de caractères, les fonctions lambda, les itérateurs, la gestion des exceptions, les passage d’arguments avancés dans les
fonctions et les décorateurs. Certains de ces points sont réellement avancés et nécessiteront d’avoir assimilé d’autres
notions avant de les aborder.

26.1 Différences Python 2 et Python 3


Python 3 est la version de Python qu’il faut utiliser.
Néanmoins, Python 2 a été employé pendant de nombreuses années par la communauté scientifique et vous serez
certainement confrontés à un programme écrit en Python 2. Voici quelques éléments pour vous en sortir :

26.1.1 Le mot-clé print / la fonction print()


En Python 2 print est un mot-clé du langage (en anglais statement) au même titre que for, if, def, etc. Il s’utilise
ainsi sans parenthèse. Par exemple :
1 >>> print 12
2 12
3 >>> print "girafe"
4 girafe

Par contre, en Python 3, print() est une fonction. Ainsi, si vous n’utilisez pas de parenthèse, Python vous renverra
une erreur :
1 >>> print 12
2 File "<stdin>", line 1
3 print 12
4 ^
5 SyntaxError: Missing parentheses in call to 'print'

26.1.2 Division d’entiers


En Python 3, la division de deux entiers, se fait naturellement, c’est-à-dire que l’opérateur / renvoie systématiquement
un float. Par exemple :

320
26.1. Différences Python 2 et Python 3 Chapitre 26. Remarques complémentaires

1 >>> 3 / 4
2 0.75

Il est également possible de réaliser une division entière avec l’opérateur // :


>>> 3 // 4
0

La division entière renvoie finalement la partie entière du nombre 0.75, c’est-à-dire 0.


Attention ! En Python 2, la division de deux entiers avec l’opérateur / correspond à la division entière, c’est-à-dire le
résultat arrondi à l’entier inférieur. Par exemple :
1 >>> 3 / 5
2 0
3 >>> 4 / 3
4 1

Faites très attention à cet aspect si vous programmez encore en Python 2, c’est une source d’erreur récurrente.

26.1.3 La fonction range()


En Python 3, la fonction range() renvoie un objet de type range (voir les chapitres 5 Boucles et comparaisons et
14 Conteneurs) :
1 >>> range(3)
2 range(0, 3)

Comme on a vu au chapitre 5 Boucles et comparaisons, ces objets sont itérables produisant successivement les nombres
0, puis 1 puis 2 sur notre exemple :
1 >>> for i in range(3):
2 ... print(i)
3 ...
4 0
5 1
6 2

En Python 2, la fonction range() renvoie une liste. Par exemple :


1 >>> range(3)
2 [0, 1, 2]
3 >>> range(2, 6)
4 [2, 3, 4, 5]

La création de liste avec range() était pratique, mais très peu efficace en mémoire lorsque l’argument donné à
range() était un grand nombre.
D’ailleurs la fonction xrange() est disponible en Python 2 pour faire la même chose que la fonction range() en
Python 3. Attention, ne vous mélangez pas les pinceaux !
1 >>> range(3)
2 [0, 1, 2]
3 >>> xrange(3)
4 xrange(3)

Remarque
Pour générer une liste d’entiers avec la fonction range() en Python 3, vous avez vu dans le chapitre 4 Listes qu’il
suffisait de l’associer avec la fonction list(). Par exemple :
1 >>> list(range(10))
2 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

Cours de Python / Université Paris Cité / UFR Sciences du Vivant 321


Chapitre 26. Remarques complémentaires 26.2. Anciennes méthodes de formatage des chaînes de caractères

Conseil
Pour une comparaison exhaustive entre xrange() en Python 2 et range() en Python 3, vous pouvez lire ce très
bon article 1 tiré du blog de Trey Hunner.

26.1.4 Fonction zip()


En Python 2, la fonction zip() renvoie une liste de tuples, alors qu’en Python 3 elle renvoie un itérateur :
1 >>> # Python2.
2 >>> zip(range(4), range(10, 14))
3 [(0, 10), (1, 11), (2, 12), (3, 13)]

1 >>> # Python3.
2 >>> zip(range(4), range(10, 14))
3 <zip object at 0x7f11423ffd80>

Vous pouvez lire la rubrique Itérables, itérateurs, générateurs et module itertools un peu plus bas dans ce chapitre
pour en savoir plus sur les itérateurs.

26.1.5 Encodage et utf-8


En Python 3, vous pouvez utiliser des caractères accentués dans les commentaires ou dans les chaînes de caractères.
Ce n’est pas le cas en Python 2. Si un caractère accentué est présent dans votre code, cela occasionnera une erreur
de ce type lors de l’exécution de votre script :
SyntaxError: Non-ASCII character '\xc2' in file xxx on line yyy, but no encoding
declared; see [Link] for details

Pour éviter ce genre de désagrément, ajoutez la ligne suivante en tout début de votre script :
1 # coding: utf-8

Si vous utilisez un shebang (voir rubrique précédente), il faudra mettre la ligne # coding: utf-8 sur la deuxième
ligne (la position est importante 2 ) de votre script :
1 #! /usr/bin/env python
2 # coding: utf-8

Remarque
L’encodage utf-8 peut aussi être déclaré de cette manière :
1 # -*- coding: utf-8 -*-

mais c’est un peu plus long à écrire.

26.2 Anciennes méthodes de formatage des chaînes de caractères


Dans les premières versions de Python jusqu’à la 2.6, il fallait utiliser l’opérateur %, puis de la version 2.7 jusqu’à la 3.5
il était plutôt conseillé d’utiliser la méthode .format(). Même si les f-strings sont devenues la manière conseillée pour
mettre en place l’écriture formatée, ces deux anciennes manières, sont encore pleinement compatibles avec les versions
modernes de Python.
Même si elle fonctionne encore, la première manière avec l’opérateur % est maintenant clairement déconseillée pour un
certain nombre de raisons 3 . Néanmoins, nous rappelons ci-dessous son fonctionnement, car il se peut que vous tombiez
dessus dans d’anciens livres ou si vous lisez de vieux programmes Python.
1. [Link]
2. [Link]
3. [Link]

322 Cours de Python / Université Paris Cité / UFR Sciences du Vivant


26.2. Anciennes méthodes de formatage des chaînes de caractères Chapitre 26. Remarques complémentaires

La deuxième manière avec la méthode .format() est encore utilisée et reste tout à fait valide. Elle est clairement
plus puissante et évite un certain nombre de désagréments par rapport à l’opérateur %. Vous la croiserez sans doute
de temps en temps dans des programmes et ouvrages plus ou moins récents. Heureusement elle a un fonctionnement
relativement proche des f-strings, donc vous ne serez pas totalement perdus !
Enfin, nous indiquons à la fin de cette rubrique nos conseils sur quelle méthode utiliser.

26.2.1 L’opérateur %
On a vu avec les entiers que l’opérateur % ou modulo renvoyait le reste d’une division entière. Cet opérateur existe
aussi pour les chaînes de caractères mais il met en place l’écriture formatée. En voici un exemple :
1 >>> x = 32
2 >>> nom = "John"
3 >>> print("%s a %d ans" % (nom, x))
4 John a 32 ans
5 >>> nb_G = 4500
6 >>> nb_C = 2575
7 >>> prop_GC = (nb_G + nb_C)/14800
8 >>> print("On a %d G et %d C -> prop GC = %.2f" % (nb_G, nb_C, prop_GC))
9 On a 4500 G et 2575 C -> prop GC = 0.48

La syntaxe est légèrement différente. Le symbole % est d’abord appelé dans la chaîne de caractères (dans l’exemple
ci-dessus %d, %d et %.2f) pour :
• Désigner l’endroit où sera placée la variable dans la chaîne de caractères.
• Préciser le type de variable à formater, d pour un entier (i fonctionne également) ou f pour un float.
• Éventuellement pour indiquer le format voulu. Ici .2 signifie une précision de deux décimales.
Le signe % est rappelé une seconde fois (% (nb_G, nb_C, prop_GC)) pour indiquer les variables à formater.

26.2.2 La méthode .format()


Depuis la version 2.7 de Python, la méthode .format() a apporté une nette amélioration pour mettre en place
l’écriture formatée. Celle-ci fonctionne de la manière suivante :
1 >>> x = 32
2 >>> nom = "John"
3 >>> print("{} a {} ans".format(nom, x))
4 John a 32 ans
5 >>> nb_G = 4500
6 >>> nb_C = 2575
7 >>> prop_GC = (nb_G + nb_C)/14800
8 >>> print("On a {} G et {} C -> prop GC = {:.2f}".format(nb_G, nb_C, prop_GC))
9 On a 4500 G et 2575 C -> prop GC = 0.48

• Dans la chaîne de caractères, les accolades vides {} précisent l’endroit où le contenu de la variable doit être inséré.
• Juste après la chaîne de caractères, l’instruction .format(nom, x) fournit la liste des variables à insérer, d’abord
la variable nom puis la variable x.
• On peut éventuellement préciser le formatage en mettant un caractère deux-points : puis par exemple ici .2f qui
signifie deux chiffres après la virgule.
• La méthode .format() agit sur la chaîne de caractères à laquelle elle est attachée par le point.
Tout ce que nous avons vu avec les f-strings sur la manière de formater l’affichage d’une variable (après les :
au sein des accolades) est identique avec la méthode .format(). Par exemple {:.2f}, {:0>6d}, {:.6e}, etc.,
fonctionneront de la même manière. La différence notable est qu’on ne met pas directement le nom de la variable au
sein des accolades. Comme pour l’opérateur %, c’est l’emplacement dans les arguments passés à la méthode .format()
qui dicte quelle variable doit être remplacée. Par exemple, dans "{} {} {}".format(bidule, machin, truc), les
premières accolades remplaceront la variable bidule, les deuxièmes la variable machin, les troisièmes la variable truc.
Le formatage avec la méthode .format() se rapproche de la syntaxe des f-strings (accolades, deux-points), mais
présente l’inconvénient – comme avec l’opérateur % – de devoir mettre la liste des variables tout à la fin, alourdissant
ainsi la syntaxe. En effet, dans l’exemple avec la proportion de GC, la ligne équivalente avec une f-string apparait tout
de même plus simple à lire :
1 >>> print(f"On a {nb_G} G et {nb_C} C -> prop GC = {prop_GC:.2f}")
2 On a 4500 G et 2575 C -> prop GC = 0.48

Cours de Python / Université Paris Cité / UFR Sciences du Vivant 323


Chapitre 26. Remarques complémentaires 26.3. Fonctions lambda

Conseil
Pour conclure, ces deux anciennes façons de formater une chaîne de caractères avec l’opérateur % ou la méthode
.format() vous sont présentées à titre d’information. La première avec l’opérateur % est clairement déconseillée. La
deuxième avec la méthode .format() est encore tout à fait valable. Si vous débutez Python, nous vous conseillons
fortement d’apprendre et d’utiliser les f-strings. C’est ce que vous rencontrerez dans la suite de ce cours. Si vous connaissez
déjà Python et que vous utilisez la méthode .format(), nous vous conseillons de passer aux f-strings. Depuis que nous
les avons découvertes, aucun retour n’est envisageable pour nous tant elles sont puissantes et plus claires à utiliser !

Pour aller plus loin


Enfin, si vous souhaitez aller plus loin, voici deux articles (en anglais) très bien faits sur le site RealPython : sur
l’écriture formatée 4 et sur les f-strings 5

26.3 Fonctions lambda


26.3.1 Définition

Définition
Une fonction lambda est une fonction qui s’écrit sur une ligne. En Python, il s’agit du moyen d’implémenter une
fonction anonyme 6 (en anglais anonymous function), c’est-à-dire, une fonction qui est la plupart du temps non reliée
à un nom (d’où le terme anonyme). Une fonction lambda s’utilise en général à la volée. On parle aussi d’expressions
lambda utilisées pour fabriquer des fonctions lambda.

Voici un premier exemple :


1 >>> lambda x: x**2
2 <function <lambda> at 0x7fcbd9c58b80>
3 >>> (lambda x: x**2)(4)
4 16
5 >>> (lambda x: x**2)(10)
6 100

• Ligne 1. On a ici une expression lambda typique définissant une fonction lambda. La syntaxe est (dans l’ordre) :
le mot-clé (statement) lambda, zero ou un ou plusieurs argument(s), deux-points, une expression utilisant ou pas
les arguments.
• Ligne 2. Python confirme qu’il s’agit d’une fonction.
• Lignes 3 à 6. Pour utiliser la fonction lambda, pour l’instant, on la met entre parenthèses et on utilise un autre
jeu de parenthèses pour l’appeler et éventuellement passer des arguments.

Attention
Une fonction lambda ne s’écrit que sur une ligne. Si vous essayez de l’écrire sur plusieurs lignes, Python lèvera une
exception SyntaxError: invalid syntax.

Comme pour les fonctions classiques, le nombre d’arguments est variable et doit être cohérent avec l’appel :

4. [Link]
5. [Link]
6. [Link]

324 Cours de Python / Université Paris Cité / UFR Sciences du Vivant


26.3. Fonctions lambda Chapitre 26. Remarques complémentaires

1 >>> (lambda: 1/2)()


2 0.5
3 >>> (lambda x, y: x + y)(1, 2)
4 3
5 >>> (lambda x, y: x + y)(4, 5)
6 9
7 >>> (lambda: 1/2)(5)
8 Traceback (most recent call last):
9 File "<stdin>", line 1, in <module>
10 TypeError: <lambda>() takes 0 positional arguments but 1 was given
11 >>> (lambda x, y: x + y)(4)
12 Traceback (most recent call last):
13 File "<stdin>", line 1, in <module>
14 TypeError: <lambda>() missing 1 required positional argument: 'y'

• Ligne 1. Fonction lambda à zéro argument.


• Lignes 3 et 5. Fonction lambda à deux arguments.
• Lignes 7 à 10. Le nombre d’argument(s) est incorrect et génère une erreur. Dans cet exemple, on passe un
argument alors que la fonction lambda créée ici n’en prend pas.
• Lignes 11 à 14. Le nombre d’argument(s) est incorrect et génère une erreur. Dans cet exemple, on passe un
argument alors que la fonction lambda créée ici en prend deux.

26.3.2 Assignation d’une fonction lambda à un nom ?


Bien que cela soit déconseillé, il est possible d’assigner une fonction lambda à un nom de variable :
1 >>> carre = lambda x: x**2
2 >>> carre(3)
3 9

L’équivalent avec une fonction classique serait :


1 >>> def carre(x):
2 ... return x**2
3 ...
4 >>> carre(9)
5 81

Dans les deux cas l’appel est identique, mais la fonction lambda requière une syntaxe à une ligne lors de sa définition.
Même si on peut le faire, les dévelopeurs déconseillent toutefois d’assigner une fonction lambda à un nom dans
la PEP8 7 . Une des raisons est que si une erreur est générée, l’interpréteur ne renvoie pas le numéro de ligne dans la
Traceback :
1 >>> inverse = lambda: 1/0
2 >>> inverse()
3 Traceback (most recent call last):
4 File "<stdin>", line 1, in <module>
5 File "<stdin>", line 1, in <lambda>
6 ZeroDivisionError: division by zero

Ligne 5. L’indication de la ligne pour l’erreur dans la fonction lambda (line 1) correspond à celle de l’appel et non
pas de la définition.
Alors qu’avec une fonction classique :
>>> def inverse():
... return 1/0
...
>>> inverse()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in inverse
ZeroDivisionError: division by zero

Ligne 5. Cette fois-ci, la Traceback indique bien la bonne ligne (line 2) dans la fonction.
7. [Link]

Cours de Python / Université Paris Cité / UFR Sciences du Vivant 325


Chapitre 26. Remarques complémentaires 26.3. Fonctions lambda

Conseil
Pour cette raison, n’assignez pas une fonction lambda à un nom, mais utilisez la seulement à la volée (voir ci-dessous).
Une autre raison est que cela peut nuire à la lisibilité. Si une fonction lambda s’écrit en une ligne, c’est bien pour qu’on
puisse la lire quand elle est utilisée.

26.3.3 Utilité des fonctions lambda


Jusqu’à maintenant nous avons défini les fonctions lambda et montré ce qu’il ne fallait pas faire. Vous vous posez
sans doute la question, mais à quoi servent-elles vraiment ? Nous vous montrons ici deux utilisations principales.
La première est qu’elles sont utiles pour implémenter des concepts de programmation fonctionnelle 8 . Dans ce para-
digme de programmation, on cherchera à « emboiter » les fonctions les unes dans les autres. Nous avions déjà croisé
cette idée avec la fonction map() dans le chapitre 11 Plus sur les chaînes de caractères. Celle-ci permet d’appliquer une
fonction à tous les éléments d’un objet itérable. Par exemple, convertir en entier les différents éléments d’une chaîne de
caractères :
1 >>> ligne = "3 5 -10"
2 >>> list(map(int, [Link]()))
3 [3, 5, -10]

Ligne 2. On a converti l’objet map en liste pour voir ce qu’il contenait.


L’utilisation impliquant une fonction lambda permet par exemple d’appliquer une opération à tous les éléments d’une
liste :
1 >>> liste1 = [3, 5, -10]
2 >>> list(map(lambda x: x**2, liste1))
3 [9, 25, 100]
4 >>> list(map(lambda x: 1/x, liste1))
5 [0.3333333333333333, 0.2, -0.1]

Lignes 2 et 4. La fonction lambda permet de lire clairement quelle opération on réalise plutôt que de se référer à
une fonction classique se trouvant à un autre endroit. Ainsi, cela améliore la lisibilité.
Cela vous rappelle peut-être ce qu’on a rencontré avec les objets NumPy et les opérations vectorielles :
1 >>> import numpy as np
2 >>> array1 = [Link](10)
3 >>> array1
4 array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
5 >>> array1 * 2
6 array([ 0, 2, 4, 6, 8, 10, 12, 14, 16, 18])
7 >>>
8 >>> liste1 = list(range(10))
9 >>> liste1
10 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
11 >>> list(map(lambda x: x*2, liste1))
12 [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

Ligne 5. Nativement, l’opération array1 * 2 se fait vectoriellement (élément par élément) avec un array NumPy.
Ligne 11. La fonction map() applique l’opération * 2 de la lambda sur tous les éléments de la liste. Ainsi, on obtient
le même effet que sur l’array NumPy.
Bien que cela s’avère pratique, nous verrons dans la rubrique suivante sur les itérateurs qu’il existe une syntaxe plus
Pythonique avec les listes de compréhensions et les expressions génératrices.
La deuxième grande utilité des fonctions lambda concerne leur utilisation pour faire des tris puissants. Dans le chapitre
14 Conteneurs, nous avions vu les tris de dictionnaires par valeurs :
1 >>> dico = {"a": 15, "b": 5, "c":20}
2 >>> sorted(dico, key=[Link])
3 ['b', 'a', 'c']

8. [Link]

326 Cours de Python / Université Paris Cité / UFR Sciences du Vivant


26.3. Fonctions lambda Chapitre 26. Remarques complémentaires

Ligne 2. On passe à l’argument par mot-clé key la callback [Link] (cette méthode renvoie initialement les valeurs
d’un dictionnaire). Cela permet finalement de trier par ce que renvoie cette méthode, à savoir les valeurs.
Cet argument par mot-clé peut prendre d’autres callback, par exemple len. Dans l’exemple suivant, on prend 10 mots
au hasard dans le dictionnaire et on les trie par leur longueur :
1 >>> mots = ['étudier', 'pie-grièche', 'figurerait', 'retraitait', 'allégerais',
2 'distribuent', 'affilierait', 'ramassa', 'galettes', 'connu']
3 >>> sorted(mots, key=len)
4 ['connu', 'étudier', 'ramassa', 'galettes', 'figurerait', 'retraitait',
5 'allégerais', 'pie-grièche', 'distribuent', 'affilierait']

Bien sûr, on peut utiliser aussi une fonction lambda. Celle-ci va nous permettre de passer une fonction de tri à la
volée au moment de l’appel de la fonction sorted(). Par exemple, si on reprend le même exemple que le dictionnaire
mais sous forme d’une liste de tuples :
1 liste1 = [('a', 15), ('b', 5), ('c', 20)]

Comment trier en fonction du deuxième élément de chaque tuple ? Réponse, avec une fonction lambda bien sûr !
Regardez :
1 >>> sorted(liste1, key=lambda x: x[1])
2 [('b', 5), ('a', 15), ('c', 20)]

Autre exemple, on souhaite trier une liste d’entiers aléatoires non pas par leur valeur, mais par le résultat de la fonction
x**2 :
1 >>> liste1 = [-5, 2, 5, 8, 6, 3, -9, 4, -10, 2]
2 >>> sorted(liste1, key=lambda x: x**2)
3 [2, 2, 3, 4, -5, 5, 6, 8, -9, -10]

Pour comprendre comment le tri est opéré en ligne 3, voici la liste initiale et une autre liste avec les carrés :
1 >>> liste1
2 [-5, 2, 5, 8, 6, 3, -9, 4, -10, 2]
3 >>> [x**2 for x in liste1]
4 [25, 4, 25, 64, 36, 9, 81, 16, 100, 4]

Le tri de liste1 ci-dessus est bien effectué en fonction des valeurs montrées en ligne 4.
L’agument par mot-clé key existe dans d’autres fonctions ou méthodes. Bien sûr il existe dans la méthode .sort()
qui trie les listes sur place. Mais aussi, dans les fonctions natives min() et max(). Enfin, on le croise dans la fonction
groupby() du module itertools (voir rubrique suivante). Dans tous ces cas, on peut utiliser une fonction lambda pour
l’argument key.
Par exemple, dans le code suivant :
1 >>> liste = ['baccalauréat', 'abaissera', 'barricadé', 'zouave', 'tabac',
2 'typographie', 'dactylographes', 'éclipse']
3 >>> min(liste)
4 'abaissera'
5 >>> max(liste)
6 'éclipse'
7 >>> min(liste, key=lambda x: [Link]("a"))
8 'éclipse'
9 >>> max(liste, key=lambda x: [Link]("a"))
10 'baccalauréat'

• Ligne 1. On prend une liste de mots du dictionnaire.


• Lignes 2 et 4. Les fonctions min() et max () considèrent l’ordre ASCII par défaut. Elles renvoient le premier et
dernier élément de la liste après un tel tri.
• Lignes 6 et 8. Comprenez-vous la règle que nous avons utilisée avec la lambda ?
Regardons comment se passe le tri :
1 >>> [Link](key=lambda x: [Link]("a"))
2 >>> liste
3 ['éclipse', 'zouave', 'typographie', 'barricadé', 'tabac', 'dactylographes',
4 'abaissera', 'baccalauréat']

Cours de Python / Université Paris Cité / UFR Sciences du Vivant 327


Chapitre 26. Remarques complémentaires 26.4. Itérables, itérateurs, générateurs et module itertools

Vous l’aurez sans doute compris, avec notre fonction lambda, nous avons trié en fonction du nombre de lettres a dans
chaque mot !

26.3.4 Conclusion
Nous avons vu que les fonctions lambda permettaient des définitions de fonction rapidement sur une ligne. Il faut
absolument éviter de les assigner à un nom. Elles ont toute leur utilité lorsqu’on les utilise avec map() pour appliquer
une opération à tous les éléments d’un conteneur, ou pour des tris puissants avec sorted().

Pour aller plus loin


Pour aller plus loin, vous pouvez consulter ces quelques articles : Dataquest 9 , Trey Hunner 10 , RealPython 11 et Dan
Bader 12 .

26.4 Itérables, itérateurs, générateurs et module itertools


26.4.1 Itérables et itérateurs
Dans le chapitre 14 Conteneurs, nous avons défini le mot itérable lorsque nous avions un objet de type conteneur sur
lequel on pouvait itérer (comme les listes, tuples, dictionnaires, etc.). En général, nous le faisions avec une boucle for.
Voyons ce qu’est maintenant un itérateur.

Définition
Un itérateur est un objet Python qui permet d’itérer sur une suite de valeurs avec la fonction next() jusqu’à temps
qu’elles soient épuisées. Si on itère sur une partie des valeurs seulement, l’itérateur garde en mémoire là où il s’est arrêté.
Si on le resollicite avec un next() il repartira de l’élément suivant. Une règle est toutefois importante : les valeurs ne
peuvent être parcourues qu’une seule fois.

On peut générer un itérateur avec la fonction iter() à partir de n’importe quel conteneur :
1 >>> animaux = ["chien", "chat", "souris"]
2 >>> iterateur = iter(animaux)
3 >>> iterateur
4 <list_iterator object at 0x7f917e907a30>

Une fois l’itérateur généré, on peut accéder à l’élément suivant avec la fonction next() :
1 >>> next(iterateur)
2 'chien'
3 >>> next(iterateur)
4 'chat'
5 >>> next(iterateur)
6 'souris'
7 >>> next(iterateur)
8 Traceback (most recent call last):
9 File "<stdin>", line 1, in <module>
10 StopIteration

Quand il n’y a plus de valeurs sur lesquelles itérer, la fonction next() lève une exception StopIteration. En général,
on n’utilisera pas les itérateurs de cette manière, mais plutôt avec une boucle for ce qui évitera cette levée d’exception :

9. [Link]
10. [Link]
11. [Link]
12. [Link]

328 Cours de Python / Université Paris Cité / UFR Sciences du Vivant


26.4. Itérables, itérateurs, générateurs et module itertools Chapitre 26. Remarques complémentaires

1 >>> iterateur = iter(animaux)


2 >>> for elt in iterateur:
3 ... print(elt)
4 ...
5 chien
6 chat
7 souris

On peut transformer un objet de type itérateur en un objet de type séquentiel, par exemple en tuple :
1 >>> iterateur = iter(animaux)
2 >>> tuple(iterateur)
3 ('chien', 'chat', 'souris')

Le point important est qu’une fois toutes les valeurs parcourues, l’itérateur est épuisé et ne renvoie plus rien :
1 >>> iterateur = iter(animaux)
2 >>> tuple(iterateur)
3 ('chien', 'chat', 'souris')
4 >>> tuple(iterateur)
5 ()

Ainsi, on ne pourra parcourir l’ensemble des valeurs d’un itérateur qu’une fois.
À ce stade, on pourrait se dire que la construction d’un itérateur à partir d’une liste ci-dessus est inutile puisqu’on
peut itérer directement sur la liste avec une boucle for. Toutefois, lorsqu’on réalise une telle boucle, il y a un itérateur
qui est généré implicitement même si on ne s’en rend pas compte. Pour prouver cela, essayons la fonction next() avec
une liste :
1 >>> next(animaux)
2 Traceback (most recent call last):
3 File "<stdin>", line 1, in <module>
4 TypeError: 'list' object is not an iterator

Ceci n’est pas possible car une liste n’est pas un itérateur. Alors pourquoi peut-on itérer dessus avec une boucle for ?
Et bien, c’est parce que l’objet de type liste possède une méthode dunder spéciale nommée .__iter__(). Celle-ci génère
un itérateur à partir d’elle-même permettant d’itérer sur ses éléments. L’objet itérateur ainsi généré possèdera une autre
méthode dunder spéciale .__next__() permettant de passer à l’élément suivant lorsqu’on itère dessus.

Remarque
Pour rappel, les méthodes dunder des classes ont été définies dans la rubrique 24.2.2 Méthodes magiques ou dunder
methods du chapitre 24 Avoir plus la classe avec les objets.

Lorsque vous construirez votre propre objet itérable, il faudra écrire une classe contenant ces deux méthodes dunder
et l’objet sera de facto un itérateur et itérable. Pour vous donnez une première idée, voici une classe minimale créant un
objet itérateur sur les lettres de l’alphabet :
1 class Alphabet:
2 def __init__(self):
3 [Link] = 97 # ASCII code for a.
4
5 def __iter__(self):
6 return self
7
8 def __next__(self):
9 if [Link] > 122: # ASCII code for z.
10 raise StopIteration
11 letter = chr([Link])
12 [Link] += 1
13 return letter

• Ligne 3. Dans le constructeur, on crée un attribut d’instance [Link] qui gardera l’état de l’itérateur. On
l’initialise à 97 correspondant au code ASCII de la lettre a.

Cours de Python / Université Paris Cité / UFR Sciences du Vivant 329


Chapitre 26. Remarques complémentaires 26.4. Itérables, itérateurs, générateurs et module itertools

• Lignes 5 et 6. La méthode dunder .__iter__() est très simple à écrire. Elle renvoie self correspondant à
l’itérateur lui-même. Si cette méthode n’est pas présente, l’objet n’est pas itérable.
• Lignes 8 à 13. La méthode dunder .__next__() s’occupe de passer à l’élément suivant et de garder une mémoire
de là où l’itérateur est arrivé. Cela se passe en quatre étapes : i) levée d’une exception StopIteration si on est
arrivé au bout, ii) détermination de la lettre actuelle, iii) incrémenter le [Link] de 1 pour l’itération suivante
et iv) retourner la lettre actuelle.
Si on sauve cette classe dans un fichier [Link], voici comment on pourrait l’utiliser :

1 >>> import iterator


2 >>> iter_alphabet = [Link]()
3 >>> iter_alphabet
4 <[Link] object at 0x7f308edc70b0>
5 >>> for lettre in iter_alphabet:
6 ... print(lettre)
7 ...
8 a
9 b
10 [...]
11 y
12 z
13 >>> list(iter_alphabet)
14 []

À nouveau, une fois l’itérateur épuisé, il ne renvoie plus rien. Bien sûr, cela représente un exemple très simple et la
plupart du temps on créera ses propres classes itérateurs en implémentant de nombreuses fonctionnalités et méthodes sup-
plémentaires. Pour créer un itérateur basique comme celui-ci sur l’alphabet, il est plus commode d’utiliser les générateurs
(voir rubrique Générateurs ci-dessous).

Pour aller plus loin


Pour aller plus loin sur comment fonctionne les itérateurs, vous pouvez lire ces articles de Dan Bader 13 , Trey Hunner 14
et du site RealPython 15 . Concernant la sémantique, cet article 16 de Trey Hunner explique pourquoi les objets range ne
sont pas des itérateurs.

26.4.2 Autres fonctions builtins renvoyant des itérateurs

Dans les chapitres précédents, nous avons déjà croisé des itérateurs sans le savoir, car nous ne vous l’avons pas
toujours précisé explicitement ! Dans le chapitre 5 Boucles avec la fonction enumerate(), dans le chapitre 11 Plus sur
les chaînes de caractères avec la fonction map() et dans le chapitre 12 Plus sur les listes avec la fonction zip(). Ces
trois fonctions renvoient des itérateurs qui sont épuisés une fois utilisés :

1 >>> animaux = ["chien", "chat", "souris"]


2 >>> obj_enum = enumerate(animaux)
3 >>> obj_enum
4 <enumerate object at 0x7f917ebf93a0>
5 >>> tuple(obj_enum)
6 ((0, 'chien'), (1, 'chat'), (2, 'souris'))
7 >>> tuple(obj_enum)
8 ()

13. [Link]
14. [Link]
15. [Link]
16. [Link]

330 Cours de Python / Université Paris Cité / UFR Sciences du Vivant

Vous aimerez peut-être aussi