Introduction à Python 3


Interpréteur python3

La commande de l'interpréteur de Python est python3. On peut la lancer avec en argument un fichier script (écrit en Python 3, et d'extension .py). Par exemple, si l'on a le fichier suivant hello.py, écrit avec un éditeur de code tel que emacs ou gedit...

# File: hello.py
print ("hello!")

... alors, sous un terminal de commandes, on peut l'exécuter avec la commande suivante:

$ python3 hello.py   # exécute le fichier hello.py
hello!

On peut lancer l'interpréteur en mode interactif, sans fichier en argument. Un prompt de la forme >>> apparaît. L'utilisateur peut saisir une commande. L'interpréteur l'exécute et ré-affiche le prompt. On peut alors saisir une nouvelle commande ou sortir avec >>> [CTRL-D]. Noter aussi que les flèches [UP] et [DOWN] permettent de voyager dans l'historique des commandes déjà saisies lors de la session, ce qui évite de les retaper lorsque que l'on veut les exécuter à nouveau.

$ python3
>>> print ("hello!")
hello!
>>> print ("bye!")
bye!
>>> [CTRL-D]
$

Attention, en Python, l'indentation est prise en compte pour analyser la commande. Il ne faut donc pas insérer d'espaces inutiles avant la première lettre de la commande:

$ python3
>>> print ("hello!")
hello!
>>>   print ("hello!")
[...] IndentationError: unexpected indent
>>> [CTRL-D]
$

En mode interactif, on peut aussi saisir des expressions. L'interpréteur affiche alors le résultat de leur évaluation, sans avoir besoin d'utiliser explicitement une commande d'affichage comme print(). Ce n'est pas le cas lorsque l'interpréteur exécute un fichier script.

$ python3
>>> n= 5
>>> print (4*n+3)
23
>>> 4*n+3
23
>>> [CTRL-D]
$

Types de données de base

La fonction type(value) permet de connaître le type d'une valeur:

$ python3
>>> type (5)
<class 'int'>
>>> type (3.1415)
<class 'float'>
>>> type ("Régis")
<class 'str'>
>>> type (False)
<class 'bool'>
>>> type(None)
<class 'NoneType'>
>>> [CTRL-D]
$

Opérations arithmétiques sur les types numériques (int et float)

Les types int et float supportent les opérations arithmétiques classiques, avec les priorités classiques:

Si les deux opérandes sont de type int, alors le résultat est toujours de type int, sauf pour la division flottante a / b qui produit toujours un float, même quand a est divisible par b:

$ python3
>>> 5 + 10 * 3
35
>>> type (5 + 10 * 3)
<class 'int'>
>>> 35 // 10
3
>>> 35 % 10
5
>>> 35 / 10
3.5
>>> 35 / 5
7.0
>>> type (35/5)
<class 'float'>
>>> [CTRL-D]
$

Si au moins un des opérandes est de type float, alors le résultat est toujours de type float, y compris pour la division entière a // b et son reste a % b.

$ python3
>>> 5 + 10.0 * 3
35.0
>>> type (5 + 10.0 * 3)
<class 'float'>
>>> 35 // 10.0
3.0
>>> 35 % 10.0
5.0
>>> 35 // 10.5
3.0
>>> 35 % 10.5
3.5
>>> [CTRL-D]
$

On dispose aussi des fonctions de valeur absolue abs(x), de minimum min(x1,x2,...) et de maximum max(x1,x2,...):

$ python3
$ abs (-5)
5
$ min (7, 2, 9, 4)
2
$ max (7, 2, 9, 4)
9
>>> [CTRL-D]
$

Pour les autres fonctions (exponentielles, logarithmes, fonctions trigonométriques), il faut utiliser les fonctions le module math, à importer préalablement avec la directive import math:

$ python3
$ import math
$ math.pi
3.141592653589793
$ math.cos (math.pi)
$ -1.0
$ math.e
2.718281828459045
$ math.log (math.e)
1.0
>>> [CTRL-D]
$

Enfin, il n'y a pas de problème de capacité pour les entiers en Python. Le langage gère les très grand entiers:

$ python3
>>> 1000 ** 20
1000000000000000000000000000000000000000000000000000000000000
>>> type (1000 ** 100)
<class 'int'>
>>> [CTRL-D]
$

Opérations sur les Booléens (bool)

Les trois opérations logiques sur les Booléens sont:

La conjonction a and b vaut True ssi les deux opérandes valent True. Elle vaut donc False ssi au moins un des opérandes vaut False:

$ python3
>>> True and True
True
>>> True and False
False
>>> False and True
False
>>> False and False
False
>>> [CTRL-D]
$

La disjonction a or b vaut True ssi au moins un deux opérandes valent True. Elle vaut donc False ssi les deux opérandes valent False.

$ python3
>>> True or True
True
>>> True or False
True
>>> False or True
True
>>> False or False
False
>>> [CTRL-D]
$

La négation not a vaut True ssi son opérande vaut False. Elle vaut donc False ssi son opérande vaut True:

$ python3
>>> not True 
False
>>> not False
True
>>> [CTRL-D]
$

Noter que les opérandes de a and b sont évalués de gauche à droite en "coupe-circuit". C'est-à-dire que si a vaut False, alors l'évaluateur conclut que la conjonction vaut False sans même évaluer b. L'ordre des opérandes est donc parfois important. Comparer:

$ python3
>>> number= 5 ; factor= 0
>>> factor != 0 and number % factor == 0
False
>>> number % factor == 0 and factor != 0
[...] ZeroDivisionError: integer division or modulo by zero
>>> [CTRL-D]
$

De même, les opérandes de a or b sont aussi évalués de gauche à droite en "coupe-circuit". C'est-à-dire que si a vaut True, alors l'évaluateur conclut que la conjonction vaut True sans même évaluer b. Comparer:

$ python3
>>> number= 5 ; factor= 0
>>> factor == 0 or number % factor != 0
True
>>> number % factor != 0 or factor == 0
[...] ZeroDivisionError: integer division or modulo by zero
>>> [CTRL-D]
$

L'opérateur and est prioritaire sur l'opérateur or, ce qui signifie que a or b and c est équivalent à a or (b and c):

$ python3
>>> (True or False) and False
False
>>> True or (False and False)
True
>>> True or False and False
True
>>> [CTRL-D]
$

L'opérateur not est prioritaire sur l'opérateur or ce qui signifie que not a or b est équivalent à (not a) or b (par opposition à not (a or b)):

$ python3
>>> not (False or True)
False
>>> (not False) or True
True
>>> not False or True
True
>>> [CTRL-D]
$

De même, l'opérateur not est prioritaire sur l'opérateur and ce qui signifie que not a and b est équivalent à (not a) and b (par opposition à not (a and b)):

$ python3
>>> not (True and False)
True
>>> (not True) and False
False
>>> not True and False
False
>>> [CTRL-D]
$

Dans le doute, ou simplement pour le repos du lecteur, on peut toujours parenthéser une expression explicitement pour lever une ambiguïté.

Opérations sur les chaînes (str)

On obtient la longueur d'une chaîne s avec la fonction len(s). La chaîne vide '' ou "" a une longueur nulle:

$ python3
>>> len ('abc')
3
>>> len ('')
0
>>> [CTRL-D]
$

On peut consulter le caractère d'une chaîne s à la position k avec l'opérateur crochets s[k]. Le résultat est une chaîne de longueur 1, car Il n'y a pas de type dédié aux caractères en Python. Les valeurs valides pour k vont de 0 à len(s)-1.

$ python3
>>> 'abc'[0]
'a'
>>> 'abc'[1]
'b'
>>> 'abc'[2]
'c'
>>> 'abc'[3]
[...] IndexError: string index out of range
>>> [CTRL-D]
$

On peut extraire la sous-chaîne d'une chaîne s occupant les positions j, ..., k-1 avec une généralisation l'opérateur crochets, le slice s[j:k]. La borne droite étant exclue du slice, s[j:j] donne toujours la chaîne vide.

$ python3
>>> [CTRL-D]
>>> 'régis'[0:3]
'rég'
>>> 'régis'[1:3]
'ég'
>>> 'régis'[2:3]
'g'
>>> 'régis'[3:3]
''

On obtient la concaténation de deux chaînes s1 et s2 avec l'opérateur s1 + s2:

$ python3
>>> 'ré' + 'gis'
'régis'
>>> [CTRL-D]
$

On obtient la chaîne s répétée n fois avec l'opérateur s * n ou n * s:

$ python3
>>> 'bla' * 3
'blablabla'
>>> 3 * 'bla' 
'blablabla'
>>> [CTRL-D]
$

Contrairement à d'autres objets telles que les listes, on ne peut pas modifier le contenu d'une chaîne, car le type str est immutable:

$ python3
>>> s= 'régis'
>>> s[2]= 'X'
[...] TypeError: 'str' object does not support item assignment
>>> [CTRL-D]
$

À la place, il faut fabriquer une nouvelle chaîne et la réassigner à la variable:

$ python3
>>> s= 'régis'
>>> s[0:2] + 'X' + s[3:]
'réXis'
>>> s= s[0:2] + 'X' + s[3:]
>>> s
'réXis'
>>> [CTRL-D]
$

On peut formater une chaîne à la manière de sprintf() en C, via l'opérateur format % (value, ...):

$ python3
>>> format= 'My name is %s, i am %d, my weight is %.2f kg.'
>>> s= format % ('Régis', 43, 65.5)
>>> s
'My name is Régis, i am 43, my weight is 65.50 kg.'
>>> print (s)
My name is Régis, i am 43, my weight is 65.50 kg.
>>> [CTRL-D]
$

Comparaisons sur les types de base

La plupart des types supportent les comparaisons classiques suivantes qui renvoient un Booléen:

Pour le type int, les résultats sont ceux attendus:

$ python3
>>> 4 < 5
True
>>> 4 >= 5
False
>>> 4 == 5
False
>>> 4 != 5
True
>>> [CTRL-D]
$

Pour le type float, il faut éviter d'utiliser les opérateurs == et !=, car les erreurs d'approximation dans les calculs interdisent qu'on puisse utiliser l'égalité ou la différence stricte. Par exemple, si on convertit 20.1 degrés Celcius en degrés Fahrenheit, on obtient 68.18, et si on reconvertit ce résultat en Celcius, on n'obtient pas exactement 20.1. On teste donc l'égalité flottante avec la fonction isclose() du module math:

$ python3
>>> c= 20.1 
>>> f= 9./5. * c + 32. 
>>> c2= (f - 32.) * 5./9.
>>> c, f, c2
(20.1, 68.18, 20.100000000000005)
>>> c == c2
False
>>> import math
>>> math.isclose (c, c2)
True
>>> [CTRL-D]
$

Les opérateurs fonctionnent sur les chaînes. On peut en particlulier utliser les opérateurs == et != pour tester l'égalité et la différence. Mais les comparaisons d'ordre utilisent l'ordre lexicographique basé sur les points Unicodes des caractère, ce qui peut donner des résultat suprenant (par exemple, les points Unicode lettres accentuées se trouvent ceux des lettres non-accentuées:

$ python3
>>> "I am %d" % (42) == "I am 42"
>>> "Regis" < "Rfc"
True
>>> "Régis" < "Rfc"
False
>>> [CTRL-D]
$

Types composites (list, tuple, dict, set)

Il y a quatre type composites principaux pour rassembler une collection d'objets dans un même objet:

Pour grouper syntaxiquement les éléments, la liste utilise les crochets, le tuple utilise les parenthèses, l'ensemble et le dictionnaire utilisent les accolades.

Opérations sur les tuples (tuple)

On utilise le tuple lorsque l'on veut grouper des informations, éventuellement de types différent en un seul objet pour des raisons de commodité. Lorsqu'une fonction doit retourner plusieurs valeurs, elle retourne typiquement le tuple de ces valeurs. Par exemple, une fonction qui devant retourner à la fois le nom, l'âge et le poids d'une personne pourrait retourner le tuple ('Régis', 42, 65.5), et une fonction devant retourner les coordonnées d'un point du plan pourrait retourner le tuple (10., 5.).

Comme pour les chaînes, la longueur d'un tuple t s'obtient avec len(t), et on peut accéder à un élément t[k] en indiquant son index k entre crochets :

$ python3
>>> regis= ('Régis', 42, 65.5)
>>>len (regis)
3
>>> regis[0]
'Régis'
>>> regis[1]
42
>>> regis[2]
65.5
>>> point= (10., 5.)
>>> point[0]
10.0
>>> point[1]
5.0
>>> [CTRL-D]
$

Plus généralement, on peut utiliser l'opérateur de slice [j:k] sur un tuple, de la même manière que sur les chaînes:

$ python3
>>> regis= ('Régis', 42, 65.5)
>>> regis[0:2]
('Régis', 42)
>>> regis[1:3]
(42, 65.5)
>>> [CTRL-D]
$

On peut aussi dépaqueter un tuple dans des variables:

$ python3
>>> regis= ('Régis', 42, 65.5)
>>> name, age, weight= regis
>>> name
'Régis'
>>> age
42
>>> weight
65.5
>>> point= (10., 5.)
>>> x, y= point
>>> x
10.0
>>> y
5.0
>>> [CTRL-D]
$

Comme pour les chaînes, on peut obtenir la concaténation de deux tuples avec l'opérateur d'addition:

$ python3
>>> regis= ('Régis', 42, 65.5)
>>> extra_info= ('teacher', 'Marseille')
>>> regis + extra_info
('Régis', 42, 65.5, 'teacher', 'Marseille')
>>> [CTRL-D]
$

Comme les chaînes, les tuples sont immutables. Une fois créés, ils ne sont plus modifiables:

$ python3
>>> regis= ('Régis', 42, 65.5)
>>> regis[1]= 43
[...] TypeError: 'tuple' object does not support item assignment
>>> [CTRL-D]
$

Enfin, peu utilisé, le tuple singleton ne contenant qu'un élément e se note (e,) avec une virgule finale, car (e) est juste e mis entre parenthèses. Par exemple, (0) est juste l'entier zéro, alors que (0,) est le tuple singleton donc l'unique élément est l'entier zéro:

$ python3
>>> (0)
0
>>> (0,)
(0,)
>>> type ( (0) )
<class 'int'>
>>> type ( (0,) )
<class 'tuple'>
>>> [CTRL-D]
$

Opérations sur les listes (list)

Bien qu'une liste peut en théorie contenir des éléments de types hétérogènes, une liste contient en pratique des éléments de même type, plus éventuellement l'élément None. Toutes les opérations sur les tuples sont aussi disponibles sur les listes, en particulier l'indexation et le slice:

$ python3
>>> fruits= ['banana', 'apple', 'orange']
>>> len (fruits)
3
>>> fruits[0]
'banana'
>>> fruits[1]
'apple'
>>> fruits[2]
'orange'
>>> fruits[1:3]
['apple', 'orange']
>>> [CTRL-D]
$

Mais à la différence des tuples, les listes sont modifiables. Une fois la liste créée, on peut changer la valeur d'un élément:

$ python3
>>> fruits= ['banana', 'apple', 'orange']
>>> fruits[1]= 'pear'
>>> fruits
['banana', 'pear', 'orange' ]
>>> [CTRL-D]
$

On peut ajouter un élément e à la fin d'une liste l avec la fonction list.append(l,e), ce que l'on peut aussi écrire l.append(e):

$ python3
>>> fruits= ['banana', 'apple', 'orange']
>>> fruits.append('pear')
>>> fruits
['banana', 'apple', 'orange', 'pear']
>>> [CTRL-D]
$

On peut retirer le dernier élément e d'une liste l avec la fonction e= list.pop(l), ce que l'on peut aussi écrire e= l.pop():

$ python3
>>> fruits= ['banana', 'apple', 'orange']
>>> removed= fruits.pop()
>>> fruits
['banana', 'apple']
>>> removed
'orange'
>>> [CTRL-D]
$

Plus généralement, on peut insérer un élément e à un indice quelconque k d'une liste l avec list.insert(l,k,e) ou l.insert(k,e). Et on peut retirer un élément e à un indice quelconque k d'une liste l avec e= list.pop(l,k) ou e= l.pop(k):

$ python3
>>> fruits= ['banana', 'apple', 'orange']
>>> fruits.insert (1, 'pear')
>>> fruits
['banana', 'pear', 'apple', 'orange']
>>> removed= fruits.pop (1)
>>> fruits
['banana', 'apple', 'orange']
>>> removed
'pear'
>>> [CTRL-D]
$

Lorsqu'un élémente appartient à une liste l; on peut connaître sa position k avec list.index(l,e) ou l.index(e). (ADDENDUM: La fonction déclenche une erreur ValueError lorsque l'élément n'appartient pas à la liste, et ne retourne pas -1 comme je l'avais écrit antérieurement).

$ python3
>>> fruits= ['banana', 'apple', 'orange']
>>> fruits.index ('orange')
2
>>> fruits.index ('pear')
-1
>>> 'orange' in fruits
True
>>> 'pear' in fruits
False
>>> [CTRL-D]
$

Tableaux à base de listes

Il n'existe pas de tableau à proprement parler en Python. On utilise les listes à la place. Une façon simple de créer un tableau t à n cases initialisées avec une expression expr est d'écrire t= [expr for k in range (0,n)]. Voici par exemple le tableau des 10 premiers carrés, ainsi qu'un tableau de 5 cases initialisées à None:

$ python3
>>> squares= [k*k for k in range (0,10)]
>>> squares
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> array= [None for k in range (0,5)]
[None, None, None, None, None]
>>> array[4]= 666
>>> array
[None, None, None, None, 666]
>>> [CTRL-D]
$

De même, pour créer une grille g à n lignes de m colonnes dont les cases sont initialisées avec une expression, expr, il suffit d'écrire g= [[expr for col in range(0,m)] for row in range (0,n)]. Voici par exemple une table de multiplication de 3 lignes et 4 colonnes, ainsi qu'une grille de 2 lignes et 3 colonnes, dont les cases sont initialisées avec None:

$ python3
>>> mult= [[ row*col for col in range(0,4)] for row in range(0,3)]
>>> mult
[[0, 0, 0, 0], [0, 1, 2, 3], [0, 2, 4, 6]]
>>> grid= [[ None for col in range(0,3)] for row in range(0,2)]
>>> grid
[[None, None, None], [None, None, None]]
>>> grid[1][2]= 666
>>> grid
[[None, None, None], [None, None, 666]]
>>> [CTRL-D]
$

Ne jamais être tenter d'écrire une forme telle que g= [[None]*m]*n, car on se retrouve avec la même ligne référencée n fois, au lieu d'avoir n lignes distinctes. Ce qui donnera la mauvaise surprise suivante:

$ python3
>>> grid= [[None]*3]*2  # don't do it!
>>> grid
[[None, None, None], [None, None, None]]
>>> grid[1][2]= 666  
>>> grid
[[None, None, 666], [None, None, 666]] 
>>> [CTRL-D]
$
Toutes les lignes ont été affectées, ce qui est normal puisqu'il n'y a en fait réellement qu'une seule ligne répétée n fois.

Opérations sur les ensembles (set)

Un ensemble permet de maintenir une collection où chaque élément, est garanti d'être en exemplaire unique. La taille d'un ensemble s est len (s). L'ajout d'un élément e se fait avec set.add(s,e) ou s.add(e). Il peut être ajouté plusieurs fois sans créer de doublon dans s. Son retrait se fait avec set.remove(s,e) ou s.remove(e). Il est interdit d'enlever un élément n'appartenant pas à s. On en peut donc retireer un même élément e plusieurs fois. L'appartenance se teste avec e in s:

$ python3
>>> s= {1, 3, 4}
>>> s.add(2)
>>> s
{1, 2, 3, 4}
>>> s.add(2)
>>> s
{1, 2, 3, 4}
>>> 2 in s
True
>>> s.remove(2)
>>> s
{1, 3, 4}
>>> 2 in s
False
>>> s.remove(2)
KeyError: 2
>>> [CTRL-D]
$

Pour deux ensembles s et t, on dispose des opérations d'union s|t, d'intersection s&t, de différence s-t, et de différence symétrique s^t.

$ python3
>>> s= {1, 2, 3, 4}
>>> t= {3, 4, 5, 6}
>>> s|t
{1, 2, 3, 4, 5, 6}
>>> s&t
{3, 4}
>>> s-t
{1, 2}
>>> t-s
{5, 6}
>>> s^t
{1, 2, 5, 6}
>>> [CTRL-D]
$

Remarque syntaxique: si un ensemble non-vide est créé en mettant ses éléments entre accolades, en revanche l'ensemble vide est créé avec set(), car les accolades vides {} sont réservées pour les dictionnaires vides. On peut tester qu'un ensemble s est vide, soit avec l'égalité s == set() soit avec not s:

$ python3
>>> s= set()
>>> s == set()
True
>>> not s
True
>>> t= { 1, 2 }
>>> t == set()
False
>>> not t
False
>>> [CTRL-D]
$

De manière générale, pour deux ensembles s et t, on peut tester:

$ python3
>>> t= { 1, 2, 3, 4 }
>>> s= { 1, 2, 3 }
>>> s < t
True
>>> s <= t
True
>>> s == t
False
>>> s|{4} < t
False
>>> s|{4} <= t
True
>>> s|{4} == t
True 
>>> [CTRL-D]
$

Enfin, dans un ensemble, il n'y a pas d'accès direct possible à un élément via un index:

$ python3
>>> t= { 1, 2, 3, 4 }
>>> t[0]
[...] TypeError: 'set' object does not support indexing
>>> [CTRL-D]
$

Opérations sur les dictionnaires (dict)

Un dictionnaire maintient une collection de paires key:value, où les clés key sont uniques. En général, les clés sont souvent des chaînes de caractères. Les dictionnaires sont en quelque sorte une généralisation des tableaux, où l'on peut indexer les cases par autre chose que des entiers.

Le nombre de paires d'un dictionnaire d s'obtient avec len(d). S'il contient la paire key:value, alors la syntaxe d[key] permet d'accéder à la valeur value.

$ python3
>>> d= { 'name': 'Régis', 'age': 42, 'weight': 65.5 }
>>> d
{'weight': 65.5, 'age': 42, 'name': 'Régis'}
>>> len(d)
3
>>> d['name']
'Régis'
>>> d['age']
42
>>> d['weight']
65.5
>>> [CTRL-D]
$

C'est aussi la syntaxe d[key]=value qui permet d'ajouter ou changer une valeur pour à la clé key.

$ python3
>>> d= { 'name': 'Régis', 'age': 42, 'weight': 65.5 }
>>> d['city']= 'Marseille'
>>> d['city']
'Marseille'
>>> d
{'weight': 65.5, 'age': 42, 'city': 'Marseille', 'name': 'Régis'}
>>> [CTRL-D]
$

Une paire est supprimée avec la syntaxe del d[key]. L'opération n'est légale que si la clé key est présente dans d. Cette présence se teste avec la syntaxe key in d.

$ python3
>>> d= { 'name': 'Régis', 'age': 42, 'weight': 65.5, 'city': Marseille }
>>> 'city' in d
True
>>> del d['city']
>>> 'city' in d
False
>>> d
{'weight': 65.5, 'age': 42, 'name': 'Régis'}
>>> del d['city']
[...] KeyError: 'city'
>>> [CTRL-D]
$

Variables

En Python, on ne déclare pas le type d'une variable. Une variable var est créée lors de son initialisation, c'est-à-dire la première fois que l'interpréteur rencontre une ligne de la forme var= expr1. Son type est alors celui de expr1. Mais ce type n'est pas figé dans le temps. il peut changer si on change la valeur de var avec une ligne de la forme var= expr2 et que expr2 a un type différent de celui de expr1.

$ python3
>>> var= "666"
>>> type(var)
<class 'str'>
>>> var= 666
>>> type(var)
<class 'int'>
>>> var= None
>>> type(var)
<class 'NoneType'>
>>> [CTRL-D]
$

C'est en général une très mauvaise idée de changer le type d'une variable au cours de sa vie. L'exception à la règle est la valeur None de type NoneType, qui permet de signaler que la variable n'a pas actuellement de valeur valable pour son véritable type.

Python est un langage très sensible aux fautes de frappe, à cause de la création silencieuse de variable à la vue d'une initialisation. On peut facilement créer par erreur une nouvelle variable alors que l'on voulait juste changer la valeur d'une variable existante.

$ python3
>>> var= "666"
>>> var
"666"
>>> vra= "777"   # oops!
>>> var 
"666"
>>> vra
"777"
>>> [CTRL-D]
$

Enfin, des raccourcis syntaxiques existent lorsqu'on modifie la valeur d'une variable en utilisant l'ancienne valeur comme accumulateur:

Il n'existe pas de syntaxe var++ ou var-- comme en C/C++/Java. Il faut écrire à la place var += 1 ou var -= 1. Ce sont de loin les accumulateurs les plus courants:

$ python3
>>> var= 5 
>>> var 
5
>>> var += 1
>>> var
6
>>> var -= 1
>>> var
5
>>> [CTRL-D]
$

Définition de fonction avec def

Une fonction d'une ligne de nom name et de corps body est définie avec la syntaxe def name (param1, param2, ...) : body, et le résultat est retourné avec la syntaxe return expr.

$ python3
>>> def fahrenheit(c): return c * 9./5. + 32.
...
>>> def celcius(f): return (f - 32.) * 5./9.
...
>>> fahrenheit(20.1)
68.18
>>> celcius (68.18)
20.100000000000005
>>> celcius(fahrenheit(20.1))
20.100000000000005
>>> celcius(fahrenheit(20.1)) == 20.1
False
>>> [CTRL-D]
$

Lorsque le corps de la fonction fait plus d'une ligne, on met le corps sous la directive def, et on indente le corps. Python se repère uniquement à l'indentation pour repérer l'emboîtement des blocs, donc une indentation stricte et rigoureuse est obligatoire dans ce langage. La taille d'une tabulation en Python est traditionnellement de 4 espaces.

# File: fahrenheit.py

def fahrenheit(c):
    factor= 9./5.
    shift= 32.
    return factor * c + shift

print (fahrenheit(20.1))
$ python3 fahrenheit.py
68.18
$

Conditionnelles if elif else

La syntaxe minimale pour une conditionnelle est if expr: body, et:

Les expressions Booléennes expr sont testées dans l'ordre, et le corps body correspondant à la première expression vraie, est exécutée. Le corps du else est exécuté lorsqu'aucune expression n'est vraie.

# File: mention1.py

def mention(mark):
    if   mark >= 16: return "TB"
    elif mark >= 14: return "B"
    elif mark >= 12: return "AB"
    elif mark >= 10: return "P"
    else: return "X"
 
print (13, mention(13))
print ( 8, mention(8))
$ python3 mention1.py
13 'AB'
8 'X'
$

Dans le cas particulier de l'exemple ci-dessus, vu que return sort de la fonction, les elif et else sont inutiles, et on peut aussi écrire:

# File: mention2.py

def mention(mark):
    if mark >= 16: return "TB"
    if mark >= 14: return "B"
    if mark >= 12: return "AB"
    if mark >= 10: return "P"
    return "X"
 
print (13, mention(13))
print ( 8, mention(8))
$ python3 mention2.py
13 'AB'
8 'X'
$

Boucle while

La boucle tant-que en Python s'écrit while expr: body.

# File: squares1.py

def print_squares(n):
    k= 0
    while k < n:
        print (k*k)
        k += 1

print_squares(5)
$ python3 squares1.py
0
1
4
9
16
$

Itérateurs et objets itérables

Un itérateur est un objet i qui permet de parcourir d'une série d'éléments. L'invocation répétée de la fonction next(i) permet d'obtenir l'élément suivant de la série, jusqu'à l'arrivée d'une erreur StopIteration. Les tuples, les listes, les ensembles et dictionnaires sont des objets itérables, c'est-à-dire, des objets obj qui fournissent un itérateur via la fonction iter(obj).

Exemple avec un tuple:

$ python3
>>> t= ("Régis", 42, 65.5)
>>> i= iter(t)
>>> next(i)
'Régis'
>>> next(i)
42
>>> next(i)
65.5
>>> next(i)
[...] StopIteration
>>> [CTRL-D]
Exemple avec une liste:

$ python3
>>> l= ["banana", "apple", "orange"]
>>> i= iter(l)
>>> next(i)
'banana'
>>> next(i)
'apple'
>>> next(i)
'orange'
>>> next(i)
[...] StopIteration
>>> [CTRL-D]
$
Exemple avec un ensemble:

$ python3
>>> s= { 5, 2, 1, 3 }
>>> i= iter(s)
>>> next(i)
1
>>> next(i)
2
>>> next(i)
3
>>> next(i)
5
>>> next(i)
[...] StopIteration
>>> [CTRL-D]
$
Exemple avec un dictionnaire:
$ python3
>>> d= { "name": "Régis", "age": 42, "weight": 65.5 }
>>> i= iter(d)
>>> next(i)
'weight'
>>> next(i)
'name'
>>> next(i)
'age'
>>> next(i)
[...] StopIteration
>>> [CTRL-D]
$

En pratique, on n'utilise jamais i= iter(obj) et next(i) directement. C'est la boucle for in exposée ci-dessous qui exploite ces fonctions en interne pour parcourir les éléments d'un objet itérable ou d'un itérateur.

Boucle for in

La boucle for elt in obj: body permet d'exécuter le corps de boucle body sur chaque élément elt de obj, où obj est un itérateur ou un itérable. On peut donc l'utiliser en particulier sur les tuples, les listes, les ensembles et les dictionnaires.

Sur un tuple:

$ python3
>>> t= ("Régis", 42, 65.5)
>>> for elt in t: print (elt)
... 
Régis
42
65.5
>>> [CTRL-D]
$

Sur une liste:

$ python3
>>> l= ["banana", "apple", "orange"]
>>> for elt in l : print (elt)
... 
banana
apple
orange
>>> [CTRL-D]
$

Sur un ensemble:

$ python3
>>> s= { 5, 2, 1, 3 }
>>> for elt in s : print (elt)
... 
1
2
3
5
>>> [CTRL-D]
$

Sur un dictionnaire:

$ python3
>>> d= { "name": "Régis", "age": 42, "weight": 65.5 }
>>> for key in d : print (key, d[key])
... 
weight 65.5
age 42
name "Régis"
>>> [CTRL-D]
$

L'objet itérable range

L'objet itérable le plus fréquemment utilisé est range(start,end,step) qui permet de parcourir les entiers de start inclus à end exlu, par incréments successifs de step. S'il step est non précisé, alors step=1 par défaut.

$ python3
>>> r= range(0, 5)
>>> i= iter(r)
>>> next(i)
0
>>> next(i)
1
>>> next(i)
2
>>> next(i)
3
>>> next(i)
4
>>> next(i)
[...] StopIteration
>>> [CTRL-D]
$

range est classiquement utilisée dans une boucle for in telle que la suivante:

$ python3
>>> for k in range (0, 5): print (k)
... 
0
1
2
3
4
>>> [CTRL-D]
$

Pour parcourir les entiers de 2 en 2:

$ python3
>>> for k in range (0, 5, 2): print (k)
... 
0
2
4
>>> [CTRL-D]
$

Pour parcourir les entiers dans l'ordre décroissant

$ python3
>>> for k in range (5, 0, -1): print (k)
... 
5
4
3
2
1
>>> [CTRL-D]
$

L'itérateur enumerate

Pour un objet itérable ou itérateur obj générant des éléments elt, l'itérateur enumerate(obj) génère les tuples (k, elt)k est le compteur de boucle:

$ python3
>>> fruits= [ "apple", "banana", "orange" ]
>>> for k, fruit in enumerate (fruits): print (k, fruit)
... 
0 apple
1 banana
2 orange
>>> [CTRL-D]
$

Cela évite d'avoir à maintenir soi-même le compteur de boucle, comme dans ces deux versions alternatives:

$ python3
>>> fruits= [ "apple", "banana", "orange" ]
>>> k= 0
>>> for fruit in fruits: print (k, fruit); k+= 1
... 
0 apple
1 banana
2 orange
>>> [CTRL-D]
$
$ python3
>>> fruits= [ "apple", "banana", "orange" ]
>>> for k in range (0, len(fruits)): print (k, fruits[k])
... 
0 apple
1 banana
2 orange
>>> [CTRL-D]
$

Générateurs (fonctions utilisant yield)

Reprenons la fonction d'affichage print_squares(n) vue plus haut dans la discussion sur la boucle while:

# File: squares2.py
def gen_squares(n):
    k= 0
    while k < n:
        print(k*k)
        k+=1

Si l'on remplace print(k*k) par yield k*k, on obtient une fonction retournant in générateur de carrés:

# File: squares2.py
def gen_squares(n):
    k= 0
    while k < n:
        yield k*k
        k+=1

Un générateur g fonctionne comme un itérateur, et donc on peut invoquer next(g) jusqu'à l'arrivée de l'erreur StopIteration. Les éléments générés sont ceux produits par yield:

$ python3
>>> from squares2 import * 
>>> g= gen_squares(5)
>>> next(g)
0
>>> next(g)
1
>>> next(g)
4
>>> next(g)
9
>>> next(g)
16
>>> next(g)
[...] StopIteration
>>> [CTRL-D]
$

Comme les itérateurs, on utilise les générateurs dans les boucles for in:

$ python3
>>> from squares2 import * 
>>> for s in gen_squares(5): print(s)
...
0
1
4
9
16
>>> [CTRL-D]
$

Sortie anticipée d'un tour de boucle (avec continue)

Dans une boucle (while ou for in), on peut abandonner un tour de boucle avant sa fin (et donc passer immédiatement au tour suivant s'il y en a un) avec le mot-clé continue. Celui-ci est généralement utilisé dans une conditionnelle if, appelée clause de garde, qui teste que la suite du traitement est impossible.

Par exemple, la fonction suivante prend une liste de chaînes en argument, et retourne la liste des initiales des chaînes qui commencent par une majuscule. Elle contient une boucle qui parcourt les chaînes de la liste. Le traitement principal du tour de boucle est l'ajout d'une initiale dans la liste à retourner. Mais cette initiale ne peut être ajoutée que si elle est en majuscule. Et l'initiale d'une chaîne ne peut elle-même être obtenue que si cette dernière n'est pas vide. Il faut donc traverser deux clauses de garde successives avant de pouvoir rajouter une initiale à la liste de résultat:

# File: initials1.py

def upper_initials_in (words):
    result= []
    for word in words:
        if len (word) == 0: continue       # first guard clause
        initial= word[0]
        if not initial.isupper(): continue # second guard clause
        result.append (initial)            # main action
    return result

# main
names= [ "Bob", "leon", "Marc", "", "joe", "Luc" ]
print (upper_initials_in (names))
$ python initials1.py
['B', 'M', 'L']
$

L'utilisation des clauses de garde permet d'éviter d'avoir l'action principale imbriquée dans un ou plusieurs if. Voici l'imbrication que donnerait la même fonction, sans clause de garde:

# File initials2.py

def upper_initials_in (words):
    result= []
    for word in words:
        if len (word) != 0:               # first nesting
            initial= word[0]
            if initial.isupper():         # second nesting
                result.append (initial)   # main action
    return result

# main
names= [ "Bob", "leon", "Marc", "", "joe", "Luc" ]
print (upper_initials_in (names))
$ python initials2.py
['B', 'M', 'L']
$

Noter que les conditions des clauses de gardes sont les négations logiques des conditions de la version imbriquée, puisque dans un cas, on teste l'impossibilité de poursuivre l'action, alors que dans l'autre on teste la possibilité de poursuivre l'action.

Sortie anticipée d'une boucle (avec break)

Dans une boucle (while ou for in), on peut abandonner une boucle avant l'exécution de la totalité de ses tours. avec le mot-clé break. L'exécution saute alors à l'instruction se trouvant derrière la boucle. En général, on utilise break dans une conditionnelle if.

Prenons par exemple cette fonction qui remplace toutes les valeurs old d'une liste elements par une valeur new:

# File: replace1.py

def replace_all (elements, old, new):
    for index, element in enumerate (elements):
        if element == old: elements[index]= new

# main
values= [1, 2, 3, 1, 2, 3]
print (values)
replace_all (values, 2, 0) 
print (values)
$ python replace1.py
[1, 2, 3, 1, 2, 3]
[1, 0, 3, 1, 0, 3]
$

En modifiant son code avec un break, on peut créer une fonction qui ne remplace que la première occurence de old:

# File: replace2.py

def replace_first (elements, old, new):
    found= False
    for index, element in enumerate (elements):
        if element == old: found= True; break 
    if found: elements[index]= new

# main
values= [1, 2, 3, 1, 2, 3]   
print (values)
replace_first (values, 2, 0) 
print (values)
$ python replace2.py
[1, 2, 3, 1, 2, 3]
[1, 0, 3, 1, 2, 3]
$

Sortie anticipée d'une fonction (avec return)

On peut sortir d'une fonction n'importe où avec return, y compris depuis une boucle. Par exemple, le code de la fonction précédente peut être réécrit:

# File: replace3.py

def replace_first (elements, old, new):
    for index, element in enumerate (elements):
        if element == old: elements[index]= new ; return

# main
values= [1, 2, 3, 1, 2, 3]   
print (values)
replace_first (values, 2, 0) 
print (values)
$ python replace3.py
[1, 2, 3, 1, 2, 3]
[1, 0, 3, 1, 2, 3]
$