Python (PDF, 330 Ko) - Université Paris Ouest Nanterre

Transcription

Python (PDF, 330 Ko) - Université Paris Ouest Nanterre
Université Paris Ouest - Nanterre - la Défense
Master DÉFI
2010 - 2011
Programmation en Python
Résumé de cours
Marcel Cori
1 Programmation
1.1
Les personnages
Dans l’activité de programmation, il faut distinguer trois (( personnages )) :
- l’utilisateur,
- le programmeur 1 ,
- et la machine.
Le programmeur est celui qui s’adresse à la machine afin qu’elle exécute des tâches qui
servent à l’utilisateur.
Les machines existantes, les ordinateurs, ont les caractéristiques suivantes :
- elles sont très puissantes, c’est-à-dire qu’elles ont une mémoire très importante et
qu’elles sont très rapides ;
- elles sont infaillibles, c’est-à-dire que, mises plusieurs fois dans les mêmes conditions, elles réagissent d’une manière parfaitement identique ;
- elles sont très obéissantes ;
- elles sont très bêtes.
La question fondamentale qui se pose au programmeur est de savoir exprimer des
ordres de telle manière que la machine agisse comme il le souhaite : ce sont ces ordres qui
constitueront les programmes.
L’ordinateur étant très obéissant et très bête, il fait tout ce qu’on lui dit de faire, mais
il ne fait que ce qu’on lui dit de faire. Il faut par conséquent, dans un programme, ne rien
omettre et prévoir toutes les situations possibles.
Par ailleurs, l’ordinateur ne prend aucune décision. Il ne peut trancher si on lui transmet un ordre ambigu. On doit donc tout expliciter, ne rien laisser sous-entendu.
1.2
L’informatisation des problèmes
Le travail du programmeur comporte deux phases essentielles : (1) la définition très
précise de la tâche, c’est-à-dire de la classe d’actions, que la machine aura à effectuer ; (2)
la décomposition, si nécessaire, de cette tâche en tâches plus réduites.
Ces deux phases peuvent être réalisées indépendamment de la machine et du langage
qui seront finalement utilisés.
1.2.1
La formulation des problèmes
Le programmeur doit tout d’abord savoir lui-même très exactement ce qu’il attend de
la machine, quelle tâche il désire que celle-ci exécute. Ce qui suppose au minimum et en
1. Dans certains cas, en particulier dans les situations d’apprentissage, le programmeur et l’utilisateur se
confondent. Mais en fait, c’est parce qu’une même personne joue alternativement les deux rôles.
2
premier lieu de définir quelles seront les données externes qui seront fournies à la machine
(par un utilisateur) et ce que la machine devra en sortir.
D’une certaine manière, on peut dire que la phase cruciale du traitement informatique
d’un problème réside dans la formulation de celui-ci.
Formuler un problème, cela revient à décrire l’action ou la classe d’actions que va
effectuer le programme. Un programme a un degré de généralité d’autant plus élevé que
la classe d’actions qu’il décrit est plus large.
1.2.2
La conception des algorithmes
Dans une deuxième étape, le programmeur doit pouvoir trouver de quelle façon l’ordinateur exécutera une tâche qui aura été définie : la méthode.
Plus exactement, quand on conçoit un programme, on commence par déterminer
grossièrement comment la tâche sera effectuée, on décompose en tâches plus réduites,
cette décomposition pouvant être réitérée. On obtient finalement une succession de tâches
élémentaires à exécuter, exprimées en terme d’instructions adressées à l’ordinateur.
Une suite finie d’instructions, qui décrit une action ou une classe d’actions, forme ainsi
un algorithme.
1.2.3
Des algorithmes aux programmes
Pour pouvoir transmettre des ordres à l’ordinateur, il faut connaı̂tre les tâches élémentaires que celui-ci sait exécuter, autrement dit les instructions que l’ordinateur (( comprend )). Un ensemble d’instructions compréhensibles par un ordinateur forme un langage
de programmation.
Un programme est un algorithme écrit dans un langage de programmation 2 .
1.3
1.3.1
Langages de programmation
Les langages évolués
Il serait trop ardu et fastidieux pour le programmeur de s’exprimer en (( langage
machine )) (qui est un langage binaire, c’est-à-dire composé de suites de 0 et de 1). Le
programmeur ne peut non plus s’adresser à la machine dans une langue naturelle, ambiguë
et trop imprécise. Les langages de programmation évolués sont des langages intermédiaires
entre la langue naturelle et le langage de la machine.
La sémantique des langages de programmation est très rigide. Le langage correspond
très exactement à des tâches que la machine sait effectuer. En définissant le langage, on
définit sa sémantique.
2. On distingue les algorithmes des programmes par le fait que les algorithmes ne sont pas nécessairement écrits
dans un langage de programmation. Un même algorithme peut être traduit en différents langages de programmation.
3
1.3.2
La traduction des programmes
L’ordinateur, face à un programme écrit en un langage qui n’est pas le sien, doit
traduire ce programme afin de l’exécuter. Il y a deux types de méthodes de traduction :
- l’interprétaton : dès qu’une instruction est traduite, elle est exécutée ;
- la compilation : le programme est entièrement traduit avant toute exécution.
En Python, les deux processus sont en œuvre.
Remarquons que lorsqu’un programmeur soumet à la machine un programme écrit
dans un langage de programmation, il se comporte en fait comme l’utilisateur d’un logiciel,
c’est-à-dire d’un programme, écrit par un autre programmeur. Ce logiciel, qui est un
interpréteur ou un compilateur, effectue la traduction du programme écrit en langage
intermédiaire dans le langage machine.
1.3.3
Les erreurs de syntaxe
Si l’ordinateur ne parvient pas à traduire le message qui lui a été communiqué, cela
voudra dire qu’il y aura eu, de la part du programmeur, une erreur de syntaxe dans le
langage de programmation : la machine ne comprend pas les ordres qu’on lui donne.
Dans un processus de compilation, lorsque la machine rencontre une erreur de syntaxe,
elle ne peut rien effectuer de ce qu’on lui a demandé de faire. Mais un avantage de la
compilation est que l’ordinateur n’est pas obligé de retraduire les ordres qu’on lui donne
à chaque fois qu’il doit les exécuter, d’où un gain de temps.
1.4
1.4.1
Exécution des programmes
Un processus temporel
L’exécution d’un programme est le processus déclenché par le programme sur la machine. Ce processus est temporel, en ce sens que la réalité (et notamment la réalité interne
de la machine) est modifiée au cours du temps.
On notera que l’exécution d’un programme peut être simulée par un être humain.
Ainsi, le programmeur, pour savoir si un programme écrit sur une feuille de papier est
correct, peut le (( faire tourner à la main )).
1.4.2
Les différentes exécutions
Un programme donné, invariable, peut connaı̂tre plusieurs exécutions différentes. Ce
sont les données externes du programme, fournies en général par l’utilisateur, qui varient
d’une exécution à l’autre. L’ensemble des jeux de données externes possibles définit la
classe d’actions que peut effectuer le programme.
1.4.3
Erreurs à l’exécution
Il se peut qu’un programme, syntaxiquement correct, donne lieu à des erreurs à
l’exécution : soit la machine se bloque et ne parvient à aucun résultat si ce n’est un message
4
d’erreur, soit elle fournit des résultats qui ne correspondent pas à ce que le programmeur
a cru lui avoir demandé de faire, soit la machine semble (( tourner )) indéfiniment. Ces
erreurs ne proviennent bien entendu pas de l’ordinateur (qui fait exactement ce qu’on lui
dit de faire), mais du programmeur. Plusieurs hypothèses sont alors possibles :
- l’exécution du programme nécessite un temps trop élevé, ce temps pouvant éventuellement être infini ;
- le programme comporte des instructions dont la succession est incohérente ;
- ce que le programmeur a écrit est parfaitement cohérent, mais ne correspond pas
à ce qu’il voulait que l’ordinateur fasse ;
- ce que le programmeur a écrit ne voulait rien dire (tout en étant syntaxiquement
correct) ;
- l’utilisateur fournit des données externes qui sortent de ce que le programmeur
avait envisagé.
Dans ce dernier cas, cela ne signifie pas que le programme est erroné. Mais que la
classe des problèmes traités n’inclut pas certaines données externes.
1.5
Environnement des programmes
Un programme, avant d’être exécuté, doit être composé et stocké sur un support
matériel (en général le disque dur de la machine).
Le programmeur, à cette fin, utilise un (( éditeur de programmes )). Ce qui le place,
provisoirement, dans la même situation qu’un utilisateur face à l’ordinateur, et plus
précisément que l’utilisateur d’un logiciel de traitement de textes.
Remarquons que, même s’il est (( vendu avec )), l’éditeur de programmes est totalement
indépendant du langage de programmation.
Enfin, le programmeur doit connaı̂tre les commandes qui lanceront la traduction
et l’exécution du programme, commandes qui sont, au moins partiellement, elles aussi
extérieures au langage de programmation.
5
2 Instructions élémentaires
2.1
2.1.1
Aspects pratiques
Python interactif
En Python, on peut transmettre à l’ordinateur des instructions immédiatement exécutables. Ainsi, quand on a ouvert une session Python, le (( prompt )) affiché est le suivant :
>>>
On peut alors demander à la machine d’effectuer certaines instructions, dont par
exemple des calculs. C’est ainsi qu’on peut obtenir :
>>> 5+3
8
>>> "bon"+"jour"
’bonjour’
Les instructions à exécuter sont écrites sur une ligne, appelée ligne de commande.
2.1.2
Enregistrement d’instructions dans des fichiers
On peut stocker une suite d’instructions (appelée un script) dans un fichier 3 , qui doit
être un fichier texte, et avoir l’extension .py. Par exemple : machin.py. Pour que les instructions stockées soient exécutées, à la ligne de commande on écrit l’instruction :
>>> import machin
ou, si on veut au cours d’une même session exécuter à nouveau les instructions stockées
dans le fichier (par exemple après une modification de ces instructions) :
>>> reload(machin)
Si on veut que les résultats des calculs demandés par les instructions stockées sur le
fichier apparaissent à l’écran, il faut utiliser l’instruction print. Par exemple :
print "bon"+"jour"
print 3+5
3. Le fichier doit être dans un répertoire accessible, et pour cela il faut que le chemin (path) d’accès en soit
connu par le système. On peut rendre accessible un chemin par une suite de deux instructions telle que :
import sys
sys.path = sys.path + ["C:\\Documents\\progPyth"]
6
2.1.3
Commentaires
Les commentaires sont des énoncés explicitant la raison d’être de certaines instructions
ou groupes d’instructions, destinés non pas à la machine, mais au programmeur lui-même
ou à d’autres programmeurs qui auraient besoin de comprendre le fonctionnement du
programme, afin par exemple de le modifier.
En Python, tout ce qui sur une ligne d’un programme suit le symbole (( # )) est ignoré
par la machine. De même, dans un programme stocké sur fichier on peut insérer des
lignes entières entre guillemets ou entre apostrophes, qui n’auront aucune influence sur le
programme.
Dans ce polycopié, nous utiliserons parfois les commentaires à des fins pédagogiques.
2.2
2.2.1
Données internes
Les constantes
Les constantes sont des données internes qui ont une valeur fixée une fois pour toutes
par le programmeur. Cette valeur ne change ni d’une exécution à l’autre du programme,
ni au cours d’une exécution du programme. Par exemple 245 ou ’bonjour’ sont des
constantes.
Une constante, outre sa valeur, possède un type. Par exemple 245 est une constante
de type nombre entier, 20.66 est une constante de type nombre réel, "a", ’bonjour’ ou
"245" sont des constantes de type suite de caractères (ou chaı̂ne).
On peut noter indiféremment les suites de caractères entre apostrophes (quotes) ou
entre guillemets. Ce qui permet d’avoir comme constantes : "aujourd’hui" ou
’Paul dit "Bonjour!" au facteur’
Certains caractères sont notés de manière particulière :
’\n’ nouvelle ligne
’\\’ backslash
’\’’ quote
’\"’ guillemet
2.2.2
Variables et affectations
Les différentes valeurs, qu’elles soient données telles quelles ou qu’elles soient calculées,
peuvent être mémorisées afin d’être réutilisées. On leur associe à cette fin un nom, qui
correspond en pratique à un emplacement dans la mémoire de l’ordinateur (qu’on désigne
parfois par le terme case).
Les noms, qui sont fixés par le programmeur, commencent obligatoirement par une
lettre (et pas par un chiffre). Ceci permet de les distinguer des constantes de type nombre.
En revanche, on accepte des noms comprenant des chiffres en dehors de la première place,
tels que a1 ou h2o.
7
On peut associer une valeur à un nom à l’aide de l’instruction d’affectation, qui a la
forme générale suivante :
hnomi = hvaleuri
une valeur pouvant être donnée par une constante, un nom, ou une expression (c’est-à-dire
un calcul).
Par exemple :
>>> y = 12 # affectation 1
>>> x = 18 # affectation 2
>>> y = x # affectation 3
>>> z = y + 25 # affectation 4
>>> z
43
>>> y
18
>>> x
18
Dans la mesure où la valeur associée à un nom peut changer au cours du processus
d’exécution d’un programme, ou d’une exécution à l’autre d’un programme, on appelle la
donnée interne désignée par un nom une variable 4 .
On observe que, dans une instruction d’affectation, s’il y a toujours un nom unique à
gauche du signe (( = )), des noms peuvent également apparaı̂tre à droite de ce signe (affectations 3 et 4). On peut dire que le nom représente un contenant quand il est à gauche du
signe d’affectation (la case mémoire), et un contenu, c’est-à-dire une valeur, quand il est à
droite de ce signe. Quand un nom représente un contenu, il faut qu’il ait reçu aupraravant
une valeur, autrement dit que la variable qu’il représente ait été initialisée.
A l’issue de l’exécution d’une instruction d’affectation telle que l’affectation 3 ci-dessus,
la valeur de x est inchangée, tandis que la valeur de y est changée : elle est désormais égale
à la valeur de x.
Si on veut échanger les contenus des cases a et b, on est par conséquent tenu d’utiliser
une troisième case, ou case auxiliaire, selon la méthode suivante :
>>> c = a
>>> a = b
>>> b = c
Enfin, en Python, des affectations multiples sont possibles, sous les formes suivantes :
>>> x = y = 7
>>> a, b = "bonjour", 56
4. Par convention, en Python on fait commencer les noms des variables par des minuscules. Les classes commenceront par des majuscules. Dans certains langages de programmation la différence majuscules/minuscules
n’est pas significative.
8
>>> x
7
>>> b + y
63
2.2.3
Récapitulation
Les constantes comme les variables sont des données internes. Une donnée interne a
un type, une valeur et éventuellement un nom. Le nom d’une donnée est fixe, toutefois
une même donnée peut être désignée par plusieurs noms dans un même programme.
La valeur des variables peut varier d’une exécution à l’autre du programme ou au cours
d’une exécution du programme. Le type des variables peut également varier en Python.
A l’exécution d’un programme, il faut que la première fois que l’on rencontre un nom
désignant une variable, ce soit en tant que contenant, autrement dit que la variable qu’il
désigne soit initialisée. L’initialisation s’effectue de la manière la plus simple par une
instruction d’affectation, mais cela pourra se faire également d’autres manières.
Si on fait allusion à un contenu sans qu’il ait été initialisé, cela provoque une erreur
(erreur à l’exécution due au fait que les instructions ne se succèdent pas comme il le
faudrait). La machine va refuser d’aller plus loin.
2.3
La communication entre la machine et l’utilisateur
Considérons la suite d’instructions suivante 5 :
Programme 2.1
print "Quel est votre prenom ?"
prenom = raw input( )
print "Bonjour " + prenom
print "Nous vous souhaitons la bienvenue"
2.3.1
Écriture
L’instruction d’écriture permet à la machine de communiquer à l’utilisateur une valeur
désignée dans le programme par une constante, un nom (ou une expression comme on le
verra plus loin).
Elle a la forme suivante :
print <valeur>
La valeur spécifiée est affichée à l’écran.
Dans le cas présent, les valeurs à afficher sont soit des constantes
"Quel est votre prenom ?", "Nous vous souhaitons la bienvenue",
soit une expression calculée "Bonjour" + prenom
5. Quand nous numérotons un programme, cela signifie que l’on suppose qu’il est enregistré sur un fichier.
9
2.3.2
Lecture
L’instruction de lecture permet à la machine de prendre connaissance d’une donnée
externe qui lui est communiquée par l’utilisateur.
raw input( ) correspond à la valeur (de type suite de caractères) de la prochaine
donnée qui sera transmise par l’utilisateur à la machine. Si on veut conserver cette valeur,
on l’affecte à une variable (ce qui permet, éventuellement, d’initialiser la variable).
Cette donnée, le prénom de l’utilisateur du programme, est une suite de caractères
non connue à l’avance par le programmeur ; elle est fournie par l’utilisateur à chaque
exécution du programme. La valeur de la variable varie bien ici d’une exécution à l’autre
du programme.
On peut regrouper les deux premières instructions du programme ci-dessus en une
seule, comme suit :
prenom = raw input("Quel est votre prenom ? ")
print "Bonjour " + prenom
print "Nous vous souhaitons la bienvenue"
On peut même regrouper les trois premières instructions en une :
print "Bonjour " + raw input("Quel est votre prenom ? ")
print "Nous vous souhaitons la bienvenue"
Si on veut entrer des données de type nombre entier ou nombre réel, on peut procèder
comme suit :
i = int(raw input("Donner un nombre entier "))
x = float(raw input("Donner un nombre reel "))
On peut également entrer des données de typs divers en utilisant input( ) :
i = input("Donner un nombre entier ")
x = input("Donner une suite de caractères")
La suite de caractères est alors à taper entre apostrophes ou accolades.
2.4
2.4.1
Tests et instructions conditionnelles
Exemple introductif
Considérons le programme suivant, écrit dans une perspective d’enseignement assisté
par ordinateur (EAO) : l’utilisateur-élève doit répondre à une question qui lui est posée
par la machine. Selon cette réponse, la machine lui dit s’il s’est trompé ou pas.
Programme 2.2
"objet: repondre a une question simple"
print "Quelle est la capitale de la France?"
reponse = raw input( )
10
if reponse == "Paris" :
print "c’est exact"
else :
print "ce n’est pas la bonne reponse"
2.4.2
Tests
Ce programme contient un test :
reponse == "Paris"
Un test est une question à laquelle la machine doit pouvoir répondre par oui ou par
non. Nous considérons pour l’instant que la forme d’un test est la suivante :
hvaleuri hopérateur relationneli hvaleuri
les opérateurs relationnels étant : “==”, “!=” (différent), “<” (inférieur), “<=” (inférieur
ou égal), “>” (supérieur), “>=” (supérieur ou égal).
2.4.3
Instructions conditionnelles
Une instruction conditionnelle est une instruction qui contient d’autres instructions,
lesquelles sont ou ne sont pas exécutées, en fonction du résultat d’un test.
Ainsi l’instruction if, dont les deux formes sont les suivantes :
if hconditioni : hinstructioni
if hconditioni : hinstructioni
else : hinstructioni
Nous explicitons ce que font ces instructions conditionnelles, dans un contexte donné,
de manière graphique :
<instruction 0>
if <test> : <instruction 1>
<instruction 3>
11
?
instruction 0
?
oui
test
non
?
instruction 1
-
instruction 3 ?
<instruction 0>
if <test> : <instruction 1>
else : <instruction 2>
<instruction 3>
?
instruction 0
?
oui
test
non
?
?
instruction 1
instruction 2
-
instruction 3 ?
En Python, au lieu d’écrire ceci :
if <test 1> : <instruction 1>
else : if <test 2> : <instruction 2>
on peut utiliser l’écriture abrégée ci-desous, qui est équivalente :
12
if <test 1> : <instruction 1>
elif <test 2> : <instruction 2>
2.4.4
Les blocs
Considérons une modification du programme d’enseignement assisté par ordinateur
ci-dessus : cette fois-ci on demande à l’utilisateur le nom du Président de la République,
et au cas où le nom est exact, on lui demande le prénom du Président.
Programme 2.3
"objet: repondre a une question un peu moins simple"
print "Quel est le nom du President de la Republique?"
reponse = raw input( )
if reponse == "Sarkozy" :
print "quel est son prenom"
reponse = raw input( )
if reponse == "Nicolas" :
print "c’est exact"
else : print "ce n’est pas le bon prenom"
else : print "ce n’est pas la bonne reponse"
Afin d’exécuter plusieurs instructions dans le cas d’une première réponse correcte, on
est obligé de les enclore dans un bloc. En Python, un bloc est délimité par une indentation.
On remarquera que dans cet exemple la variable reponse pourra contenir successivement deux valeurs distinctes au cours d’une même exécution du programme.
13
3 Opérations
3.1
3.1.1
Les opérations et leurs notations
Définition
Une opération associe à un certain nombre d’objets d’un type déterminé (les opérandes)
un objet de type également déterminé (le résultat). Une opération permet ainsi de former
une nouvelle valeur à partir d’autres valeurs. Par exemple, l’addition de nombres entiers
associe à deux nombre entiers un troisième nombre entier, selon un processus bien connu.
Une opération est dite n-aire si elle comprend n opérandes. n est en ce cas l’arité de
l’opération. Ainsi, l’addition est une opération 2-aire, ou binaire.
Le type des opérandes et du résultat définissent le type de l’opération. Nous dirons par
exemple que l’addition de nombres entiers est une opération de type
hnombre entier, nombre entier → nombre entieri.
Si l’on ne respecte pas les contraintes relatives au type dans l’écriture d’une opération,
cela déclenche une erreur. En Python, l’erreur se produit à l’exécution. Par exemple :
a = 27 / "bonjour"
3.1.2
Sémantique des opérations
Le fait de connaı̂tre le type des opérandes d’une opération ne suffit pas pour en
connaı̂tre le domaine. Ainsi, dans la division de nombres entiers, le deuxième opérande
est de type nombre entier, mais la division échoue si la valeur de cet opérande est zéro.
C’est à l’exécution d’un programme que se manifestent les contraintes relatives au domaine dans l’écriture d’une opération. Par exemple, les instructions qui suivent induisent
une erreur à l’exécution :
b = 0
a = 27 / b
Par ailleurs, il ne faut pas confondre l’opération théorique (l’addition, par exemple,
qui permet d’obtenir des nombres si grands soient-ils) et l’opération réelle, qui fonctionne
sur des variables dont la taille est limitée par les caractéristiques physiques de la machine
à laquelle on s’adresse.
3.1.3
Opérateurs
Pour les opérations binaires (et éventuellement unaires), on utilise des opérateurs, selon la notation suivante :
<opérande> <opérateur> <opérande>
<opérateur> <opérande>
14
Le même opérateur peut servir à désigner plusieurs opérations différentes. Ainsi, l’opérateur (( + )) désigne à la fois l’addition de nombres entiers et l’addition de nombres réels
(et, comme on le verra par la suite, la concaténation de suites de caractères). C’est le type
des objets auxquels s’applique l’opérateur qui permet à la machine de savoir de quelle
opération il s’agit.
3.1.4
Notation fonctionnelle
Certaines opérations sont écrites non pas à l’aide d’opérateurs, mais en utilisant la
notation fonctionnelle : un nom désigne l’opération, et la liste des opérandes est placée à
la suite, entre parenthèses. Dans la liste, les opérandes sont séparés les uns des autres par
une virgule.
Par exemple, les opérations max(x,y), len(x), find(x,y) et range(x) que nous introduisons plus loin 6 .
Toutes les opérations pourraient être écrites à l’aide de la notation fonctionnelle, mais
pour les opérations binaires, et éventuellement unaires, l’usage des opérateurs est plus
commode.
La notation fonctionnelle est néanmoins obligatoire pour les opérations d’arité supérieure
à 2. Elle sera également utile pour les opérations définies par le programmeur 7 .
3.1.5
Notation objet
La notation objet sera présentée plus loin. Par exemple x.index(y) et x.count(y)
sont des opérations notées à l’aide de la notation objet 8 .
3.2
3.2.1
Les opérations arithmétiques
Les quatre opérations
(( + )) (pour l’addition), (( - )) (pour la soustraction), (( * )) (pour la multiplication) et
(( / )) (pour la division) sont des opérateurs qui définissent des opérations arithmétiques.
La division de deux valeurs entières produit un résultat entier, alors que la division
d’une valeur entière par une valeur réelle, ou d’une valeur réelle par une valeur entière
donne une valeur réelle. Cela signifie que l’opérateur (( / )) définit plusieurs opérations
arithmétiques :
>>> 5/2
2
>>> 5./2
2.5
Donnons un exemple de programme utilisant ces opérations :
6. Voir ci-dessous, pages 16, 22, 26 et 32.
7. Cf. plus loin, page 35 et suivantes.
8. Voir page 25.
15
Programme 3.1
annee = 2010
print "quel est ton age?"
age = int(raw input())
naissance = annee - age
print "tu es ne en ", naissance
age = age + 1 # incrémentation de la variable age
print "l’annee prochaine, tu auras ", age, " ans!"
L’instruction age = age + 1 permet d’incrémenter la variable age. On prend la valeur de la variable, on lui ajoute la valeur constante 1, et on affecte cette nouvelle valeur
à la variable age. La variable age a ainsi sa valeur qui est modifiée au cours de chaque
exécution du programme.
Les instructions d’incrémentation étant très fréquentes en programmation, en Python
il existe une notation abrégée pour les représenter. Ainsi peut-on écrire à la place de l’instruction d’incrémentation ci-dessus :
age += 1
Plus généralement, on peut écrire :
a += b
ce qui équivaut à :
a = a + b
Une telle instruction est appelée une affectation complexe. Les affectations complexes
sont possibles avec les autres opérateurs arithmétiques : (( -= )), (( *= )) et (( /= )). Elles sont
valides quelle que soit l’opération décrite par ces opérateurs.
3.2.2
Autres opérations
L’opérateur (( - )) donne lieu à une opération unaire, qui détermine l’opposé d’un
nombre :
>>> x, y = 5, -3.5
>>> - x
-5
>>> - y
3.5
Il y a la possibilité de trouver le plus petit ou le plus grand de deux nombres 9 à l’aide
des opérations min et max :
>>> max(5,12)
9. Ces opérations se généralisent à un nombre quelconque de nombres, ou de suites de caractères. Elles peuvent
également s’appliquer à une liste.
16
12
>>> min (5,12)
5
On peut engendrer des nombres aléatoires, à condition d’importer le module random :
>>> import random
>>> n = randint(0,100)
>>> n
47
Le nombre sera compris (au sens large) entre 0 et 100.
3.3
Opérations sur les caractères
On peut obtenir le code ASCII d’un caractère et, à l’inverse, obtenir un caractère à
partir de son code ASCII :
>>>ord("a")
97
>>> chr(97)
’a’
3.4
Les opérations logiques
Quand on examine si une condition est vérifiée ou pas, on effectue en réalité une
opération dont le résultat est soit la valeur True soit la valeur False 10 . Ainsi, l’égalité
entre suites de caractères est une opération qui à deux suites de caractères fait correspondre une valeur de type logique. C’est pourquoi, on peut écrire :
a = b == ’salut’
Il existe des opérations qui peuvent être appliquées aux valeurs de type logique. Ces
opérations sont notées à l’aide d’opérateurs.
not est une opération qui à deux donnés de type logique fait correspondre une valeur
logique ; and et or sont deux opérations qui à deux donnés de type logique font correspondre une valeur logique.
Les tables de ces opérations sont les suivantes :
not
a
0 1
not a 1 0
10. Dans les versions plus anciennes de Python les valeurs étaient 1 (si oui) et 0 (si non).
17
and
a \ b 0 1
0
1 0
1
0 1
a \ b 0 1
0
0 1
1
1 1
or
On peut vérifier, à l’aide de ces tables, que
not (a or b) est équivalent à (not a) and (not b) et que
not (a and b) est équivalent à (not a) or (not b).
Nous allons donner un exemple de programme utilisant ces opérations. On pose encore
une question à l’utilisateur, question qui comporte deux réponses, ces réponses pouvant
être données dans un ordre quelconque.
Programme 3.2
print "Qui sont les jumeaux qui ont fonde Rome ?"
print "Premier jumeau :"
reponse = raw input( )
r1 = reponse == "Remus"
r2 = reponse == "Romulus"
if r1 or r2 :
print "Deuxieme jumeau:"
reponse = raw input( )
r1 = r1 or reponse == "Remus"
r2 = r2 or reponse == "Romulus"
if r1 and r2 : print "c’est bon"
else : print "ce n’est pas la bonne reponse"
3.5
3.5.1
Expressions et priorités
Les expressions
On construit des expressions, qui peuvent être complexes, utilisant plusieurs opérateurs,
comme par exemple :
a + b * c + 3 / d
Une valeur, par conséquent, peut être désignée dans un programme de trois manières
différentes : par une constante, par un nom ou par une expression.
Partout où, dans un programme, on attend une valeur, on peut par conséquent avoir
une expression. Aux deux dernières instructions exécutables du programme 3.1 ci-dessus,
on peut ainsi substituer:
print "l’annee prochaine, tu auras ", age + 1 , " ans!"
18
3.5.2
Nécessité de fixer des règles de priorité
L’utilisation des opérateurs peut introduire une ambiguı̈té dans l’ordre d’exécution des
opérations11 . Par exemple, pour
a + b * c
on ne sait s’il faut d’abord effectuer l’addition ou la multiplication.
C’est pourquoi on donne un ordre de priorité entre opérateurs. Ainsi, en Python, la
multiplication sera prioritaire par rapport à l’addition. Cela signifie que la multiplication
sera effectuée avant l’addition.
Par conséquent si, dans l’exemple précédent, on veut effectuer l’addition avant la multiplication, on utilise des parenthèses:
(a + b) * c
3.5.3
Règles de priorité en Python
Les opérateurs * et / sont prioritaires par rapport aux opérateurs + et -.
Les opérateurs arithmétiques sont prioritaires par rapport aux opérateurs relationnels.
Les opérateurs relationnels sont prioritaires par rapport aux opérateurs logiques.
L’opérateur not est prioritaire par rapprot aux opérateurs or et and.
L’opérateur and est prioritaire par rapport à l’opérateur or.
De ces règles de priorité on déduit le bon usage des parenthèses.
3.5.4
Ordre d’exécution des opérations
En cas de priorités égales, ou de l’utilisation multiple d’un même opérateur, les opérations
sont effectuées de gauche à droite.
C’est pourquoi on obtient :
>>> 8 / 4 / 2
1
>>> 8 / (4 / 2)
4
11. La notation fonctionnelle exclut, quant à elle, tout risque d’ambiguı̈té.
19
4 Séquences : listes, suites de caractères
4.1
4.1.1
Structures de données
Introduction
Les types nombre entier, nombre réel ou caractère sont des types primitifs. Une
donnée de type primitif est une donnée simple. On peut définir des structures de données
plus complexes que les données simples. Ces structures de données sont obtenues en regroupant des données simples.
Parmi les structures de données, certaines consistent à mettre des éléments les uns à
la suite des autres. Les listes, comme les suites de caractères correspondent à ce type.
Certaines opérations sur les listes et les suites de caractères sont semblables. Ce qui fait
qu’en Python sont définies des opérations identiques pour les unes et les autres. Nous
appellerons ces structures des séquences.
4.1.2
Les listes
Une liste est composée d’objets quelconques mis les uns à la suite des autres. Un même
objet peut avoir plusieurs occurrences dans une liste. Par exemple :
>>> a = [12,20,3,17,12,14]
est une liste de nombres. Une liste peut être vide :
>>> a = [ ]
4.1.3
Les suites de caractères
Une suite de caractères, comme son nom l’indique, est composée de caractères mis les
uns à la suite des autres. Par exemple :
>>> mot = "bonjour"
>>> suite vide = ""
Il ne faut pas confondre une suite de caractères avec la liste composée des mêmes
caractères :
>>> liste = [’b’, ’o’, ’n’, ’j’, ’o’, ’u’, ’r’]
4.2
4.2.1
Opérations communes à toutes les séquences
Accès aux éléments
Il est possible d’accéder à chacun des éléments d’une séquence, par l’intermédiaire d’un
indice, c’est-à-dire de la place occupée par l’élément dans la séquence :
hséquencei [hvaleuri]
20
La première valeur possible renvoyant à un élément de la séquence, autrement dit le
premier indice possible, est 0. C’est pourquoi on obtient :
>>> a = [12,20,3,17,12,14]
>>> a[2]
3
>>> a[0]
12
Si on cherche à accéder à un élément de la séquence par un indice qui est en dehors
des valeurs possibles, cela produit une erreur.
Plus exactement, l’indice donne une position intermédiaire entre deux éléments. Ce
qui permet d’accéder à des sous-séquences, de la manière suivante :
>>> mot = "Bonjour"
>>> mot[2:4]
’nj’
>>> mot[0:7]
’Bonjour’
>>> mot[2:]
’njour’
>>> mot[:4]
’Bonj’
Les nombres négatifs permettent de compter à partir du dernier élément d’une séquence,
le dernier élément étant repéré par l’indice -1.
>>> b = [12,20,3,17,12,14,15]
>>> b[-1]
15
>>> b[-4]
17
>>> b[-4:-1]
[17, 12, 14]
>>> b[-4:-0]
[17, 12, 14, 15]
>>> b[-4:]
[17, 12, 14, 15]
>>> b[:-2]
[12, 20, 3, 17, 12]
21
4.2.2
Opérations
L’opération len (longueur) est une opération unaire 12 ; elle associe à toute séquence
un nombre entier : le nombre d’éléments dont la séquence est constituée. Par exemple :
>>>
>>>
>>>
7
>>>
5
b = [12,20,3,17,12,14,15]
mot = ’Salut’
len(b)
len(mot)
La concaténation est une opération binaire ; elle associe à deux séquences de même
nature une autre séquence, obtenue par la concaténation (mise bout à bout) des deux
séquences. On note cette opération à l’aide de l’opérateur (( + )).
On remarquera également l’utilisation de l’opérateur de multiplication qui représente
une opération associant à une séquence et un nombre entier une nouvelle séquence obtenue par répétition de concaténations.
>>> [12,20,3] + [17,12,14,15]
[12, 20, 3, 17, 12, 14, 15]
>>> "bon" + "jour"
’bonjour’
>>> ’bon’ * 3
’bonbonbon’
>>> [12,20,3] * 3
[12, 20, 3, 12, 20, 3, 12, 20, 3]
>>> ’bon’ * 0
’’
4.2.3
Tests
Tous les opérateurs relationnels s’appliquent aux séquences. En particulier, les opérateurs
d’infériorité ou de supériorité effectuent des tests sur l’(( ordre alphabétique )).
>>> [22,20,3] == [17,12,14,15]
False
>>> [22,20,3] >= [17,12,14,15]
True
Il existe également l’opérateur (( in )) qui vérifie si un élément appartient ou pas à une
séquence.
12. On remarquera que cette opération est désignée par la notation fonctionnelle.
22
>>>12 in [17,12,14,15]
True
>>> ’a’ in ’bonjour’
False
4.3
4.3.1
Propriétés spécifiques des listes
Modifiabilité
Contrairement aux suites de caractères, les listes sont modifiables en Python. On peut
ainsi changer la valeur d’un élément d’une liste sans toucher aux autres, en effectuant une
affectation partielle, de la manière suivante :
>>> b = [12, 20, 3, 17]
>>> b[2] = 18
>>> b
[12, 20, 18, 17]
On peut également changer plusieurs éléments d’une liste à la fois, changer la longueur
d’une liste en insérant des éléments, en supprimant des éléments, ou en remplaçant un
certain nombre d’éléments de la liste par un (autre) nombre d’éléments de la liste :
>>> b = [12,20,18,17,2,12]
>>> b[2:4] = [25,35]
>>> b
[12, 20, 25, 35, 2, 12]
>>> b[1:1] = [50,60,70]
>>> b
[12, 50, 60, 70, 20, 25, 35, 2, 12]
>>> b[5:7] = [ ]
>>> b
[12, 50, 60, 70, 20, 2, 12]
>>> b[2:4] = [1,5,9]
>>> b
[12, 0, 1, 5, 9, 20, 2, 12]
Remarque : Une instruction d’affectation dont la partie droite est formée par une variable représentant une liste produit l’identification des variables 13 . Ce qui fait que si on
modifie une des listes (autrement que par une instruction d’affectation non partielle à la
variable liste), l’autre est également modifiée :
>>> b = [12,20,18,1]
13. En fait, ce sont les adresses en mémoire des variables qui sont identifiées.
23
>>> c = b # identification des listes c et b
>>> c
[12, 20, 18, 1]
>>> b[1] = 55 # modification de b qui affecte c
>>> b
[12, 55, 18, 1]
>>> c
[12, 55, 18, 1]
c[2:3] = [ ] # modification de c qui affecte b
>>> b
[12, 55, 1]
>>> b = [12,20,18,1] # réinitialisation de b, qui ne touche pas c
>>> c
[12, 55, 1]
On peut en revanche affecter à c une copie de b sans que se produise une identification
des variables :
>>> b = [12,20,18,1]
>>> c = b[ : ] # affectation à c d’une copie de b
>>> c
[12, 20, 18, 1]
>>> b[1] = 55 # modification de b qui ne touche pas c
>>> b
[12, 55, 18, 1]
>>> c
[12,20,18,1]
c[2:3] = [ ] # modification de c qui ne touche pas b
>>> b
[12, 55, 18, 1]
4.3.2
Hétérogénéité
En Python, une liste peut être hétérogène, c’est-à-dire contenir des éléments de types
différents, et on peut même remplacer un élément d’un type donné par un élément d’un
autre type :
>>> b = [12,20,’a’,3,"bonjour",2]
>>> b[3]
’a’
>>> b[1] = [’salut’]
>>> b
[12, ’salut’, ’a’, 3, ’bonjour’, 2]
24
4.3.3
Liste comme élément d’une liste
Une liste peut admettre d’autres listes comme éléments :
>>> b = [12,20,3,6]
>>>c = [1,b,4]
>>> c
[1, [12, 20, 3, 6], 4]
>>> c[1]
[12, 20, 3, 6]
>>> c[1][2]
3
>>> len(c)
3
>>> len(c[1])
4
4.3.4
Autres instructions
Les opérations index et count, notées comme des méthodes, donnent respectivement
la position du premier élément qui a une valeur donnée (cela produit une erreur si aucun élément de la liste n’a cette valeur), ou le nombre d’éléments qui ont la valeur donnée :
>>>
>>>
1
>>>
2
>>>
0
a = [’a’,’b’,’c’,’b’,’e’,’f’,’c’]
a.index(’b’)
a.count(’b’)
a.count(’d’)
L’instruction del permet d’enlever certains éléments d’une liste, selon les indices :
>>> a = [’a’,’b’,’c’,’d’,’e’,’f’]
>>> del a[0]
>>> a
[’b’,’c’,’d’,’e’,’f’]
del a[2:4]
a
[’b’,’c’,’f’]
Les méthodes qui suivent modifient la liste à laquelle elles s’appliquent. insert ajoute
dans une position donnée, un élément à la liste. append place un élément dans la liste
en dernière position, remove enlève la première occurrence de l’element qui a une valeur
donnée (et produit une erreur si aucun élément n’a cette valeur), sort trie la liste par
25
ordre alphabétique, reverse inverse l’ordre des éléments de la liste :
>>> a = [’a’,’b’,’c’,’b’,’e’,’f’,’c’]
>>> a.insert(2,’d’)
>>> a
[’a’,’b’,’d’,’c’,’b’,’e’,’f’,’c’]
>>> a.append(’g’)
>>> a
[’a’,’b’,’d’,’c’,’b’,’e’,’f’,’c’,’g’]
>>> a.remove(’b’)
>>> a
[’a’,’d’,’c’,’b’,’e’,’f’,’c’,’g’]
>>> a.sort( )
>>> a
[’a’,’b’,’c’,’c’,’d’,’e’,’f’,’g’]
>>> a.reverse( )
>>> a
[’g’,’f’,’e’,’d’,’c’,’c’,’b’,’a’]
4.4
Propriétés spécifiques des suites de caractères
4.4.1
Non-modifiabilité
Les affectations partielles sont interdites sur les suites de caractères. Si on veut changer le caractère d’ordre 4 dans la suite a ci-dessous, il faut procéder comme suit, à l’aide
d’une affectation non partielle :
>>> a = "chansons"
>>> a = a[:4] + ’t’ + a[5:]
>>> a
’chantons’
4.4.2
Le module string
L’importation du module string permet d’utiliser un certain nombre d’opérations
spécifiques et d’avoir à disposition des constantes prédéfinies.
4.4.2.1
Opérations
string.find(a,b) associe aux suites de caractères a et b une valeur entière : -1 si b
n’est pas une sous-suite de a, l’indice correspondant à la première occurrence de b dans
a sinon. string.find(a,b,n) où n est une valeur numérique commence la recherche à
l’indice n. Par exemple :
>>> string.find("ananas","na")
26
1
>>> string.find("ananas","na",2)
3
string.upper(a) associe à la suite de caractères a une autre suite obtenue en transformant toutes les lettres minuscules de a en les lettres majuscules correspondantes, les
autres caractères étant inchangés. De même string.lower(a) transforme les majuscules
en minuscules.
>>> string.lower("Marcel Cori")
’marcel cori’
>>> string.upper("Marcel Cori")
’MARCEL CORI’
string.split(phrase) associe à une suite de caractères (censée représenter une
phrase) une liste de suites de caractères (chacune d’elles représentant un mot).
>>> string.split("le petit chien dort dans sa niche")
[’le’, ’petit’, ’chien’, ’dort’, ’dans’, ’sa’, ’niche’]
>>> string.split("le fox-terrier dort dans sa niche")
[’le’, ’fox-terrier’, ’dort’, ’dans’, ’sa’, ’niche’]
>>> string.split("Paul, aujourd’hui, dort dans sa niche.")
[’Paul,’, ’aujourd’hui,’, ’dort’, ’dans’, ’sa’, ’niche.’]
>>> string.split(’Paul me dit " comment vas-tu ? " et sort.")
[’Paul’, ’me’, ’dit’, ’"’, ’comment’, ’vas-tu’, ’?’, ’"’, ’et’, ’sort.’]
string.join effectue l’opération inverse :
>>> string.join([’le’, ’petit’, ’chien’, ’dort’, ’dans’, ’sa’, ’niche’])
’le petit chien dort dans sa niche’
4.4.2.2
Constantes
Le module string fournit également un certain nombre de suites constantes qui
peuvent être utiles dans certains programmes :
>>> string.lowercase
’abcdefghijklmnopqrstuvwxyz’
>>> string.uppercase
’ABCDFGHIJKLMNOPQRSTUVWXYZ’
>>>string.letters
’abcdefghijklmnopqrstuvwxyzABCDFGHIJKLMNOPQRSTUVWXYZ’
>>> string.digits
’0123456789’
27
Enfin, string.whitespace fournit la suite ’\t\n\x0b\x0c\r ’ correspondant à différents
séparateurs.
4.5
Les n-uplets
Les n-uplets (ou tuples en anglais) sont des séquences non modifiables qui, comme les
listes, peuvent être hétérogènes, avoir pour éléments des listes ou d’autres n-uplets.
Un n-uplet constant s’écrit entre parenthèses, mais quelquefois on peut omettre les
parenthèses :
>>> a = 1,2,3,4
>>> a
(1, 2, 3, 4)
Pour écrire un n-uplet constant composé d’un élément unique, il faut terminer par une
virgule :
>>> a = "bonjour",
>>> b = (1)
>>> a
(’bonjour’,)
>>> b
1
Les opérations possibles sur les n-uplets sont les opérations sur les séquences :
>>> a = 1,2,3,4,5,6
>>> a[2:4]
(3, 4)
4.6
4.6.1
Les dictionnaires
Définition
Un dictionnaire regroupe un ensemble non ordonné d’éléments accessibles à l’aide d’une
clé. La clé est de n’importe quel type non transformable (nombres, suites de caractères,
n-uplets). Les clés d’un dictionnaire doivent toutes être différentes.
Un dictionnaire constant est présenté sous la forme de couples <clé> : <valeur>,
c’est-à-dire d’enregistrements, entre accolades. On accède à chaque élément à l’aide de la
clé.
>>> dico = {’chien’ : ’nom’, ’manger’:’verbe’, ’chat’:’nom’,’le’:’art’}
>>> dico[’chat’]
’nom’
28
4.6.2
Modification du dictionnaire
On ajoute et on modifie des enregistrements à l’aide de la même insruction :
>>> dico[’manger’] = ’inf’
>>> dico[’noir’] = ’adj’
>>> dico
{’chat’:’nom’, ’noir’: ’adj’, ’le’:’art’, ’chien’ : ’nom’, ’manger’:’inf’ }
On supprime des enregistrements à l’aide de l’instruction del :
>>> del dico[’chien’]
>>> dico
{’chat’:’nom’, ’noir’: ’adj’, ’le’:’art’, ’manger’:’inf’ }
4.6.3
Autres instructions
On peut obtenir le nombre d’enregistrements d’un dictionnaire, la liste des clés, la
liste des valeurs, vérifier si une clé appartient bien au dictionnaire, créer la copie d’un
dictionnaire, modifier un dictionnaire en lui adjoignant les enregistrements d’un autre
dictionnaire :
>>> dico = {’chat’:’nom’, ’noir’: ’adj’, ’le’:’art’, ’chien’ : ’nom’ }
>>> len(dico)
4
>>> dico.keys()
[’chat’, ’noir’, ’le’, ’chien’]
>>> dico.values()
[’nom’, ’adj’, ’art’]
>>> dico.has key(’chien’)
1
>>> dic1 = dico.copy()
>>> dic2 = {’rat’:’nom’, ’le’:’det’ }
>>> dic1.update(dic2)
>>> dic1
{’chat’:’nom’, ’noir’: ’adj’, ’le’:’det’, ’chien’ : ’nom’, ’rat’:’nom’ }
On remarque que quand il y a conflit pour une clé donnée avec l’instruction update,
c’est la valeur associée au dictionnaire adjoint qui l’emporte sur la valeur d’origine.
29
5 Programmes itératifs
5.1
Exemple introductif
Supposons que l’on envisage le problème d’enseignement assisté en posant une question
à un élève, jusqu’à ce qu’il donne la bonne réponse. Cela peut s’effectuer de la manière
suivante :
Programme 5.1
reponse = ""
print "Quelle est la capitale de la France?"
while reponse != "Paris" :
reponse = raw input( )
if reponse != "Paris" :
print "ce n’est pas la bonne reponse, recommencez"
print "c’est cela"
5.2
L’instruction while
Dans l’algorithme qui précède, on utilise une instruction while. Cette instruction a la
forme générale suivante:
while htesti : hinstructioni
Elle permet d’itérer une instruction, c’est-à-dire de la répéter un certain nombre de
fois (ici non connu à l’avance 14 ).
C’est le test qui permet de savoir jusqu’à quand on répète l’instruction.
Il est clair que l’instruction qui est itérée peut être remplacée par un bloc d’instructions 15 (ce qui est le cas dans le présent exemple).
Nous explicitons
graphique :
hinstruction
while htesti
hinstruction
ce que fait l’instruction while, dans un contexte donné, de manière
1i
: hinstruction 2i
3i
14. Dans le cas présent, on n’est pas certain que le programme s’arrêtera un jour.
15. Voir ci-dessus page 13.
30
?
instruction 1
non
?
test
oui
?
?
instruction 3
instruction 2
?
5.3
L’instruction for
Quand la machine va effectuer une itération sur tous les éléments d’une séquence,
ou quand on peut savoir, au moment de commencer à exécuter une itération, combien
de fois elle répétera son instruction ou son bloc d’instructions, il est préférable d’utiliser
l’instruction for.
Cette instruction a la forme générale suivante:
for hnomi in hséquencei : hinstructioni
Si on utilisait à la place l’instruction while, on serait obligé d’écrire un plus grand
nombre de lignes d’instructions, comme suit :
l = len(hséquencei)
i = 0
while i < l :
hnomi = hséquencei[i]
hinstructioni
i = i + 1
Nous allons donner un exemple d’utilisation de l’instruction for : la recherche du
nombre d’occurrences de la lettre (( e )) dans une phrase.
Programme 5.2
print "Donner une phrase :"
phrase = raw input( )
k = 0 # Initialisation de k
for lettre in phrase :
if lettre == ’e’ :
31
k = k + 1 # Incrémentation de k
print "il y a ",k," e dans la phrase"
Dans cet algorithme, on utilise un compteur, k, c’est-à-dire une variable entière qui est
initialisée (ici, à 0), puis incrémentée (par l’instruction k = k+1) au fur et à mesure que
c’est nécessaire.
Remarque : Il est souvent utile d’itérer des instructions pour la suite des n premiers
nombres entiers. En ce cas on utilise l’opération range qui forme une telle liste à partir
du nombre n :
>>> range(5)
[0, 1, 2, 3, 4]
5.4
5.4.1
Applications
Calcul d’une somme
Le calcul d’une somme d’un certain nombre de quantités (pas nécessairement connu à
l’avance) s’effectue en généralisant ce qui a été fait pour les compteurs.
On initialise une variable (par exemple de nom somme), en général à la valeur 0. Puis,
on ajoute à cette variable les différentes valeurs qu’il faut lui ajouter, à l’aide d’une
instruction while ou d’une instruction for.
Considérons par exemple le problème suivant: un enseignant va communiquer à la
machine toutes les notes obtenues par ses élèves. Quand il a fini, il tape un nombre
négatif à la place d’une note. Il veut connaı̂tre la moyenne de ces notes.
Programme 5.3
print "Donner votre premiere note"
note = float(raw input( ))
somme = 0
k = 0 #Initialisation de k
while note>= 0 :
somme = somme + note
k = k + 1
print "Donner la note suivante, negatif si termine"
note = float(raw input( ))
print "la moyenne est : ",somme/k
5.4.2
Calcul d’un maximum ou d’un minimum
Soit maintenant le problème suivant: le professeur entre le nom de tous ses élèves (dans
un ordre quelconque). Quand il a fini, il entre la suite zzz. La machine lui renvoie le nom
du premier élève dans l’ordre alphabétique.
32
Programme 5.4
print "Donner le nom du premier eleve"
eleve = raw input( )
premier = "zzzz" # Initialisation du minimum
while eleve ! = "zzz" :
if eleve < premier : # test
premier = eleve # changement du premier provisoire
print "Donner le nom suivant, zzz si termine"
eleve = raw input( )
print "le premier par ordre alphabetique est : ", premier
On a ici résolu un problème de recherche d’une valeur minimum parmi un nombre
quelconque de valeurs. Le problème de la recherche d’une valeur maximum serait résolu
d’une manière similaire. Dans les deux cas, on effectue des comparaisons entre valeurs
prises deux par deux.
On utilise une variable qui contient à un moment donné le minimum (ou le maximum)
provisoire (ici premier). On initialise convenablement cette variable (selon le problème).
Puis on compare chacun des candidats possibles à être le minimum ou le maximum à
la valeur du minimum ou du maximum provisoire. Si nécessaire, on modifie cette valeur
provisoire.
En fin d’itération, cette valeur sera le minimum ou le maximum définitif, c’est-à-dire
le résultat de l’algorithme.
5.4.3
Algorithmes de tri
Les algorithmes de tri rangent les éléments d’une liste dans un ordre croissant ou
décroissant.
Il existe diverses méthodes pour effectuer des tris. Nous en proposons une ci-dessous
à titre d’exemple. Il s’agit d’un tri de nombres réels dans l’ordre croissant.
Programme 5.5
print "combien de nombres y aura-t-il a trier?"
n = int(raw input( ))
tn = [ ]
for i in range(n) :
print "nombre numero ",i
tn = tn + [int(raw input( ))]
for i in range(n-1) :
if tn[i] > tn[i+1] :
inverse = 1
j = i
while inverse and j>=0 :
a = tn[j+1]
33
tn[j+1] = tn[j]
tn[j] = a
j = j-1
if j>=0 : inverse = tn[j]>tn[j+1]
print "Les nombres, dans l’ordre croissant, sont :"
for x in tn :
print x
5.4.4
Instructions itératives imbriquées
Il est possible que, parmi les instructions qui sont à itérer, figure une instruction
d’itération (qui elle-même peut contenir une instruction d’itération, etc.).
On va donner un exemple d’imbrication d’instructions itératives: le jeu du (( pendu )).
La machine (( pense )) à un mot, et l’utilisateur doit essayer de le deviner en proposant
successivement des lettres. S’il propose plus de dix lettres erronées, il a perdu. Au fur et
à mesure de ses propositions, la machine affiche les lettres qu’il a trouvées dans le mot à
leur bonne position.
Programme 5.6
a = "anticonstitutionnellement"
b = ""
for x in a : b = b + " "
print b
k = 0
while a ! = b and k <= 10 :
print "Proposez une lettre"
lettre = raw input( )
bon = 0
c = ""
i = 0
for x in a :
if x == lettre :
bon = 1
c = c + lettre
else : c = c +b[i]
i = i+1
b = c
if bon : print b
else : k = k+1
if k > 10 : print "vous etes pendu"
else : print "vous avez trouve"
34
6 Les fonctions
6.1
Ecriture des fonctions
Le programmeur peut définir lui-même des opérations qui seront ajoutées à l’ensemble
des opérations primitives que l’ordinateur sait effectuer, et ensuite utiliser ces nouvelles
opérations de la même manière que les opérations primitives.
Par exemple, si l’on veut définir, en se servant des opérations de base sur les suites
de caractères, une opération qui sélectionne le dernier caractère d’une suite, on écrit une
fonction, de la manière suivante:
Programme 6.1
def dernier(a) :
return a[-1]
a est le paramètre (unique dans le cas présent) de la fonction dernier. Les paramètres
sont des noms. Ils représentent des variables que l’on n’a pas à initialiser. Ils sont les
opérandes génériques de l’opération définie par le programmeur.
La fonction se divise en un en-tête, qui sert à donner un nom à la fonction, et à indiquer quels sont ses paramètres, et un ensemble d’instructions exécutables (ici une seule).
Le résultat de la fonction est transmis à l’aide de l’instruction return <valeur>. Notons
l’indentation des instructions exécutables par rapport à l’en-tête.
L’exemple qui suit est une fonction à plusieurs paramètres (trois). Il s’agit de définir
l’opération copie qui va extraire d’une suite de caractères la sous-suite commençant au
p-ième caractère et qui comprend n caractères. S’il n’y a pas p caractères à partir du
p-ième, on s’arrête au dernier caractère de la suite de départ.
Programme 6.2
def copie(a, p, n) :
segment = ""
l = len(a)
j = p
i = 1
while i ≤ n and j < l :
segment = segment + a[j]
i = i + 1
j = j + 1
return segment
Le programme formé par les instructions d’une fonction qui définit une opération peut
comporter autant d’instructions que nécessaire, mais il ne comporte pas d’instruction
35
d’entrée ou d’instruction de sortie.
6.2
Appel des fonctions
Une fonction, une fois écrite, peut être appelée afin que l’opération qu’elle définit soit
exécutée. Cet appel peut se faire à la ligne de commandes, il peut être intégré dans d’autres
suites d’instructions, éventuellement dans l’écriture d’autres fonctions.
A chaque paramètre on fait correspondre un argument qui doit être une valeur dont le
type est compatible avec les opérations qui mettent en jeu la paramètre correspondant.
Un argument est un opérande effectif de l’opération qui est exécutée.
Par exemple, en supposant que les deux fonctions ci-dessus ont été enregistrées dans
un fichier chaine.py :
>>>
>>>
’r’
>>>
>>>
>>>
’ur
import chaine
chaine.dernier("bonjour")
prenom = "Marie"
sousSuite = chaine.copie("bonjour "+prenom, 5, 6)
sousSuite
Mar’
On peut renommer une fonction afin de ne pas systématiquement mentionner le nom
du module dans lequel elle est enregistrée à chaque appel.
>>> dern = chaine.dernier
>>> sousChaine = chaine.copie
>>> dern("Marie")
’e’
>>> sousChaine("bonjour ", 4, 9)
’our’
On peut également n’importer que la fonction à laquelle on s’intéresse à partir d’un
module donné, auquel cas on ne mentionne pas le nom du module.
>>> from chaine import dernier
6.3
Généralisation des fonctions
Les fonctions peuvent regrouper des ensembles d’instructions qui ne définissent pas
spécialement une opération. En ce cas, toutes les instructions sont autorisées, y compris
les instructions d’entrée et de sortie.
Par exemple, le jeu du pendu défini ci-dessus en 5.4.4 peut être écrit sous la forme
d’une fonction :
36
Programme 6.3
def pendu( ) :
"jouer au pendu"
a = "anticonstitutionnellement"
b = ""
for x in a : b = b + " "
print b
k = 0
while a ! = b and k <= 10 :
print "Proposez une lettre"
lettre = raw input( )
bon = 0
c = ""
i = 0
for x in a :
if x == lettre :
bon = 1
c = c + lettre
else : c = c +b[i]
i = i+1
b = c
if bon : print b
else : k = k+1
if k > 10 : print "vous etes pendu"
else : print "vous avez trouve"
Dans de tels cas, la fonction ne renvoie pas de résultat. En fait elle renvoie la valeur
None, que l’on peut voir apparaı̂tre par exemple à l’aide de l’instruction print.
6.4
6.4.1
Compléments
Valeurs par défaut des paramètres
Il est possible d’appeler une même fonction avec un nombre variable d’arguments. À
cette fin, on écrit la fonction avec une valeur par défaut affectée à certains paramètres.
Si l’argument correspondant à un paramètre dont on a indiqué la valeur par défaut est
spécifié dans l’appel, on ne tient pas compte de cette valeur par défaut. Sinon, c’est cette
valer qui sert à initialiser le paramètre.
Considérons, à titre d’exemple, cette fonction censée déterminer le pluriel des noms,
qui rajoute un (( s )) en fin des noms dans les cas standards, mais peut affecter une autre
terminaison si on le demande.
Programme 6.4
def pluriel(nom, term= "s") :
37
return nom + term
L’appel de cette fonction peut donner les résultats suivants 16 :
>>> pluriel(’table’)
’tables’
>>> pluriel(’chapeau’, ’x’)
’chapeaux’
Le paramètre term reçoit ici la valeur par défaut (( s )). Cette valeur peut être écrasée
par une autre valeur selon la façon dont on appelle la fonction.
On peut même avoir plusieurs paramètres qui reçoivent une valeur par défaut, et
appeler la fonction en écrasant l’un de ces paramètres. Il y a néanmoins intérêt à ordonner
les paramètres de telle façon que les derniers de la liste soient ceux dont il y a le moins
de risque de vouloir écraser les valeurs.
Par exemple, on peut modifier la fonction pluriel définie ci-dessus, en envisageant
le cas où on indique un nombre de caractères à supprimer en fin de chaı̂ne avant de
concaténer la terminaison. Ce qui pourrait s’écrire de la manière suivante :
Programme 6.5
def pluriel(nom, term= "s", nb = 0) :
if nb != 0 : nom = nom[ : -nb]
return nom + term
Les appels donneraient alors :
>>> pluriel(’chapeau’, ’x’)
’chapeaux’
>>> pluriel(’cheval’, ’ux’, 1)
’chevaux’
>>> pluriel(’souris’, nb = 1)
’souris’
6.4.2
Outils de programmation fonctionnelle
Trois opérations peuvent admettre des fonctions comme opérandes.
6.4.2.1
L’opération filter
L’opération filter, appliquée à une fonction (à un paramètre unique) et à une liste
de valeurs, renvoie la sous-liste des valeurs pour laquelle la fonction prend la valeur True
(ou une valeur différente de 0).
Par exemple :
>>> def f(x) :
... return len(x) > 4
16. Nous ne mentionnons pas ici, à fin de brièveté, les instructions nécessaires à l’importation de la fonction.
38
...
>>> filter(f,["Pierre", "Luc", "Marie", "Max", "Paul", "Sylvie"])
[’Pierre’, ’Marie’, ’Sylvie’]
6.4.2.2
L’opération map
L’opération map, appliquée à une fonction (à un paramètre unique) et à une liste de
valeurs, renvoie la liste des résultats de l’application de la fonction.
Par exemple, en considérant la fonction pluriel définie ci-dessus :
>>> map(pluriel,["chat","chien","lapin"])
[’chats’, ’chiens’, ’lapins’]
L’opération map peut également être appliquée à une fonction à plusieurs paramètres.
Il faudra qu’il y ait autant de listes de valeurs qu’il y a de paramètres à la fonction. Par
exemple :
>>> map(pluriel,["chat","chapeau","souris"], ["s", "x", ""])
[’chats’, ’chapeaux’, ’souris’]
6.4.2.3
L’opération reduce
L’opération reduce permet d’itérer une fonction à deux paramètres sur une liste de
valeurs. Par exemple :
>>>
...
...
>>>
19
>>>
20
>>>
0
>>>
2
def add(x,y) :
return x+y+1
reduce(add, [2,4,7,3])
reduce(add, [2,4,7,3],0)
reduce(add, [],0)
reduce(add, [2])
Remarquons la possibilité de donner une valeur initiale à l’itération (par le troisième
argument de map), qui change le nombre d’itérations et permet d’effectuer l’itération pour
une liste vide.
39
7 Entrées et sorties
7.1
7.1.1
Compléments sur les entrées/sorties
Conversions
On peut convertir divers types de données, et notamment des données numériques, en
suite de caractères, en utilisant des apostrophes inverses (ou accents graves) :
>>> ‘12‘
’12’
>>> x = 4.5
>>> ‘x‘
’4.5’
>>> ‘2./3‘
’O.66666666666666663’
>>> l = [2,"bonjour",4.5]
>>> ‘l‘
"[2, ’bonjour’, 4.5]"
Comme on le voit, ce procédé s’applique aux listes. Il s’applique aussi aux n-uplets.
7.1.2
Formatage des sorties
On peut formater adéquatement les sorties en utilisant les opérations du module
string suivantes : rjust, ljust, center, zfill : rjust(x,n) justifie à droite la suite x
sur n caractères ; ljust(x,n) justifie à gauche la suite x sur n caractères 17 ; center(x,n)
centre la suite x sur n caractères ; zfill(n) remplit avec des zéros sur la gauche pour
obtenir un total de n caractères.
>>> import string
>>> x = 12
>>> print string.rjust(‘x‘,4)
12
>>> print string.ljust(‘x‘,6) + "est le resultat"
12
est le resultat
>>> print string.center(‘x‘,4)
12
>>> print string.center(‘x‘,5)
12
>>> print string.zfill(x,4)
0012
17. Cette opération n’est utile qu’en cas de plusieurs écritures sur une même ligne.
40
7.2
Fichiers
7.2.1
Introduction
Dans le dialogue informatique, il peut arriver qu’il y ait plus de trois personnages (Cf.
1.1). Ainsi, il peut y avoir plusieurs utilisateurs qui communiquent des données externes à
la machine (ou auxquels la machine communique des données internes) par l’intermédiaire
de fichiers.
Un fichier est constitué par une liste d’éléments, tous de même type, (( externe )) à la
machine. Cette liste est stockée sur un support physique tel que disquette ou disque dur.
Elle est repérée sur ce support par une suite de caractères que nous appellerons nom réel
du fichier.
On peut connaı̂tre tous les noms réels des fichiers qui figurent sur un support externe
en consultant les répertoires du support.
7.2.2
Ouverture et fermeture des fichiers
Un programme peut être écrit sans que l’on connaisse à l’avance les noms réels des
fichiers qui seront utilisés. Les fichiers sont désignés dans le programme par des variables
de type fichier. Une même variable de type fichier peut représenter plusieurs fichiers
différents lors du déroulement d’un programme et, inversement, plusieurs variables de
type fichier peuvent représenter le même fichier.
A l’exécution du programme, un fichier réel est mis en correspondance avec son image
dans le programme par l’instruction open :
<fichier> = open(<nom réel du fichier>, <option>)
Le nom réel du fichier est une valeur de type suite de caractères. L’option est une
suite de caractères qui a une des valeurs suivantes :
’r’
’w’
’a’
’r+’
lecture
écriture
écriture à la suite du fichier (append)
lecture et écriture
Avec l’option ’r’ ou ’r+’, si à la valeur représentant le nom réel du fichier ne correspond aucun fichier du support physique en communication avec la machine (répertoire
courant), cela produit une erreur.
Il faut également faire attention au fait qu’un fichier ouvert avec l’option ’w’ a son
contenu antérieur qui est détruit.
Après qu’un fichier a été utilisé, il convient de le fermer à l’aide de l’instruction
close( ) :
f.close( )
41
7.2.3
Les fichiers textes
Un cas particulier de fichier est le fichier texte. Les données stockées sur un fichier
texte sont considérées comme des caractères mis les uns à la suite des autres.
Les fichiers qui ne sont pas des fichiers textes sont en Python des fichiers binaires. L’ouverture des fichiers binaires s’effectue en mettant dans l’option d’ouverture le caractère
’b’. Par exemple ’rb’, ’wb’ ou ’r+b’.
Dans ce qui suit on ne considérera que les fichiers textes.
7.3
7.3.1
Lecture et écriture dans un fichier
Accès séquentiel
On accède au premier caractère, puis à tous les autres les uns à la suite des autres.
f.read( ) fournit la suite des caractères qui constituent le fichier f. À la deuxième
exécution de cette instruction, on obtient une suite de caractères vide.
f.read(n) fournit la suite des n premiers caractères du fichier f. Lors d’une deuxième
exécution de cette instruction, on obtient la suite des n caractères suivants, et ainsi de
suite, sauf si on rouvre le fichier, auquel cas on est repositionné au début. S’il n’y a pas
assez de caractères à lire, on obtient la suite des caractères restant, puis la suite vide.
f.readline( ) fournit la suite de caractères à partir du caractères à lire jusqu’au
premier symbole de fin de ligne rencontré. Attention, le symbole de fin de ligne figure
comme dernier caractère de la suite lue. Si on veut éviter ce symbole, on peut écrire une
instruction telle que celle qui suit :
a = f.readline( )[:-1]
Lors d’une deuxième exécution de cette instruction, on obtient la ligne suivante, et ainsi
de suite. Quand il n’y a plus rien à lire, on obtient la suite de caractères vide.
f.readlines( ) fournit une liste de suites de caractères, chaque suite étant une ligne
du fichier.
f.write(<suite de caractères>) écrit sur le fichier la suite de caractères considérée,
à la position qui dépend de l’option d’ouverture du fichier et des instructions précédemment
exécutées. Dans l’option r+, il est possible d’écraser certains caractères déjà présents dans
le fichier.
f.writelines(<liste de suites de caractères>) écrit sur le fichier les unes à la
suite des autres les différentes suites de la liste. Si on veut que les suites soient sur des
lignes distinctes, il faut penser à intercaler le caractère \n.
42
7.3.2
Accès direct
On peut se positionner n’importe où dans le fichier pour accéder à des caractères ou
écrire des caractères.
f.tell( ) fournit un nombre entier qui représente la position courante (en octets)
dans le fichier, la première position étant la position 0.
f.seek(<position>,<option>) permet de se positionner dans le fichier.
Si l’option est 0, on se positionne à partir du début (en ce cas, l’option peut être
omise). Si l’option est 1, on se positionne à partir de la position courante. Si l’option est
2, on se positionne à partir de la fin.
Les instructions de lecture et d’écriture sont les mêmes que dans le cas de l’accès
séquentiel. Par exemple :
>>> f = open(’truc’,’r+’)
>>> f.write(’0123456789’)
>>> f.close()
>>> f = open(’truc’,’r+’)
>>> f.seek(4)
>>> f.write(’abc’)
>>> f.close()
>>> f = open(’truc’,’r+’)
>>> f.seek(-6,2)
>>> f.read(5)
’abc78’
>>> f.read(2)
’9’
>>> f.close()
43
8 Objets, classes, méthodes
8.1
Les classes
Les classes décrivent des types d’objets :
- les attributs de ces objets, sous la forme de variables,
- les actions qui s’appliquent à ces objets, sous la forme de méthodes.
8.1.1
La définition d’une classe
Ci-dessous est définie la classe Etudiant, sans aucune méthode :
class Etudiant :
nom = prenom = ""
naissance = 1970
note1 = note2 = 10
Cette classe comporte cinq attributs : le nom, le prénom, l’année de naissance, ainsi
que deux notes. Une valeur par défaut est affectée aux différents attributs.
8.1.2
La création et la manipulation des objets
Une fois une classe définie, on peut créer des objets appartenant à cette classe, ou
instances de la classe. Cela s’effectue grâce à l’instruction :
>>> x = Etudiant( )
Les différents attributs de l’objet sont initialisés aux valeurs par défaut.
On peut accéder à ces valeurs et les modifier de la manière suivante:
>>> Etudiant.note1
10
>>> x.note2
10
>>> x.note1 = 15
>>> Etudiant.note1
10
>>>x.note1
15
>>> Etudiant.note2 = 0
>>> x.note2
0
>>> Etudiant.note1 = 19
44
>>> x.note1
15
On remarque que tant qu’un attribut d’une instance n’est pas initialisé, il garde la
valeur corrspondante de la classe, et est donc sensible aux modifications de la valeur
de l’attribut de classe. Mais, une fois qu’il a été initialisé, il n’est plus sensible à ces
modifications.
Par ailleurs, des attributs peuvent être créés et initialisés, pour la classe et les instances
de la classe, en dehors de la définition de la classe :
>>>
>>>
>>>
17
>>>
11
8.2
8.2.1
x.note3 = 11
Etudiant.note4 = 17
x.note4
x.note3
Les méthodes
La définition des méthodes
Considérons à nouveau la classe Etudiant, mais cette fois définie avec quatre méthodes.
class Etudiant :
nom = prenom = ""
naissance = 1970
note1 = note2 = 10
def age(self,anneeCourante):
return anneCourante - self.naissance
def moyenne(self):
if self.note1< self.note2:
return note2
else : return (note1+note2)/2.
def egal(self,z)
if self.nom == z.nom and self.prenom == z.prenom : return True
else : return False
def imprime(self):
print self.prenom + " " + self.nom + ", ne en ", self.naissance
if self.note1 < 10 and self.note2 <10 : return
print "premiere note : ", note1
print "deuxieme note : ", note2
Une méthode s’écrit comme une fonction, sauf qu’un premier paramètre, appelé ici
toujours self, est obligatoire.
45
Remarquons l’utilisation de l’instruction return dans la définition de la méthode
imprime afin de sortir de l’exécution de la méthode avant la dernière instruction.
8.2.2
L’appel des méthodes
Ci-dessous une suite d’instructions qui permettent d’initialiser un objet de type
Etudiant et d’accéder aux méthodes définies dans la classe Etudiant.
>>> x = Etudiant( )
>>> x.note1 = 15
>>> x.note2 = 9
>>> if x.moyenne( ) >= 10. : print "l’etudiant est recu""
... else : print "l’etudiant est colle""
...
l’etudiant est recu
>>> Etudiant.moyenne(x)
12.
>>> x.nom = "Dupont"
>>> x.prenom = "Jean"
>>> x.naissance = 1989
>>> x.age(2011)
24
>>> x.print()
Jean Dupont ne en 1989
premiere note : 15
deuxieme note : 9
On remarquera qu’une même méthode peut être appelée comme une méthode, ou
comme une fonction de la classe. Auquel cas le premier argument de la fonction doit être
une instance de la classe.
8.2.3
Méthodes modifiant l’objet
Une méthode peut modifier l’objet auquel elle s’applique. Considérons ci-dessous la
classe Adjectif, accompagnée de deux méthodes :
class Adjectif:
valeur = ""
def feminin(self):
return self.valeur+"e"
def feminise(self):
self.valeur = self.valeur + "e"
46
L’appel des méthodes peut donner lieu aux instructions qui suivent :
>>> a = Adjectif( )
>>> a.valeur = "petit"
>>> a.feminin( )
’petite’
>>> a.valeur
petit
>>> a.feminise( )
>>> a.valeur
’petite’
8.3
Phénomènes d’héritage
8.3.1
Extensions et sous-classes
Les étudiants peuvent être considérés comme des personnes particulières. L’extension
d’une classe revient à définir une sous-classe. En effet, en ajoutant des champs et des
méthodes supplémentaires à une classe donnée, on construit une classe plus spécifique.
Pour indiquer qu’une classe est l’extension d’une autre classe, on fait suivre le nom de
la classe par le nom de la classe dont elle descend entre parenthèses.
class Personne :
nom = prenom = ""
naissance = 1950
def age(anneeCourante):
return anneeCourante - naissance
def egal(z):
if self.nom == z.nom and self.prenom ==
else : return false
def imprime(self):
print self.prenom + " " + self.nom + ",
class Etudiant(Personne):
naissance = 1970
note1 = note2 = 0
def moyenne( ):
if self.note1< self.note2:
return note2
else : return (note1+note2)/2.
def imprime(self):
print self.prenom + " " + self.nom + ",
if self.note1 < 10 and self.note2 <10 :
print "premiere note : ", note1
print "deuxieme note : ", note2
47
z.prenom : return true
ne en ", self.naissance
ne en ", self.naissance
return
On remarque que les attributs et méthodes sont indiqués au niveau adéquat de la
hiérarchie, la classe Etudiant héritant des attributs et des méthodes de la classe Personne.
La séquence d’instructions ci-dessous est alors possible :
>>>
>>>
>>>
>>>
24
8.3.2
e = Etudiant( )
e.nom = "Dupont"
e.naissance = 1980
e.age(2004)
Exceptions à l’héritage
Deux attributs peuvent apparaı̂tre dans la classe et dans son extension (par exemple
l’attribut naissance n’est pas initialisé avec les mêmes valeurs dans la classe Personne
et dans la classe Etudiant). En ce cas, on accède à l’une ou l’autre des valeurs selon le
type d’objet en cause.
De même, quand une méthode associée à une sous-classe a le même nom et le même
nombre de paramètres qu’une autre méthode associée à la classe qu’elle étend, il y a
recouvrement des méthodes. Cela correspond à une exception à l’héritage des propriétés.
Ainsi ci-dessus la méthode imprime.
8.3.3
Héritage multiple
En Python, l’héritage multiple est autorisé : une sous-classe peut être l’extension de
plusieurs classes. On place entre parenthèses, séparées par des virgules, les classes dont
hérite une classe donnée. L’ordre dans lequel sont données ces classes a une importance.
On peut ainsi définir la classe Travailleur, descendant de Personne, puis la classe
EtudiantTravailleur, descendant de Travailleur et de Etudiant :
class Travailleur(Personne) :
naissance = 1960
salaire = 1200
class EtudiantTravailleur(Travailleur,Etudiant):
note1 = 10
En ce cas les attributs et méthodes sont hérités des différents ascendants possibles. En
cas de conflit, c’est l’ordre des ascendants dans la liste qui permet de trancher. Ainsi, un
EtudiantTravailleur va avoir par défaut l’année de naissance d’un Travailleur plutôt
que celui d’un Etudiant, car Travailleur précède Etudiant dans la liste des ascendants. De même, c’est la méthode imprime de la classe Personne qui va s’appliquer à un
EtudiantTravailleur, car c’est la méthode qui s’applique, par héritage, à Travailleur
qui précède Etudiant dans la liste.
>>> e = EtudiantTravailleur( )
48
>>> e.naissance
1960
>>> e.note1
10
>>> e.note2
0
>>> e.imprime( )
, ne en 1960
8.4
Les constructeurs
Quand on écrit une instruction telle que
>>> e = Etudiant( )
on fait appel à une méthode implicite de la classe Etudiant. Cette méthode est un
constructeur.
Mais le constructeur peut être défini explicitement, lors de la définition de la classe :
on définit la méthode init . Ceci permet de construire des objets dont les attributs
initiaux seront différents selon la valeur des arguments lors de l’appel du constructeur.
Par exemple, dans la définition ci-dessous de la classe Etudiant, le constructeur permet
de construire un étudiant dont la valeur du nom et du prénom sont communiqués par
l’intermédiaire d’un suite de caractères unique (où le nom et le prénom sont séparés par
un espace).
import string
class Etudiant :
def init (self, identite) :
n = string.find(identite, " ")
self.nom = identite[n+1:]
self.prenom = identite[:n]
self.naissance = 1970
self.note1 = self.note2 = 10
def age(self,anneeCourante):
return anneCourante - naissance
def moyenne(self):
if self.note1< self.note2:
return note2
else : return (note1+note2)/2.
def egal(self,z) :
if self.nom == z.nom and self.prenom == z.prenom : return 1
else : return 0
def imprime(self):
print self.prenom + " " + self.nom + ", ne en ", self.naissance
if self.note1 < 10 and self.note2 <10 : return
print "premiere note : ", note1
49
print "deuxieme note : ", note2
L’appel du constructeur peut alors s’effectuer de la manière suivante :
>>> x = Etudiant("Jean Dupont")
>>> x.nom
’Dupont’
>>> x.age(2009)
34
Une même classe ne peut posséder plusieurs constructeurs, mais avec le procédé des
valeurs par défaut des paramètres on peut appeler un constructeur de différentes manières.
Par exemple :
import string
class Etudiant :
def init (self, identite= " ", n = 1970) :
n = string.find(identite, " ")
self.nom = identite[n+1:]
self.prenom = identite[:n]
self.naissance = n
self.note1 = self.note2 = 10
L’appel du constructeur pourrait alors donner lieu à :
>>> x = Etudiant("Jean Dupont")
>>> y = Etudiant()
>>> z = Etudiant("Jean Dupont", 1982)
>>> y.nom
’’
>>> z.naissance
1982
50
9 Les expressions régulières
9.1
9.1.1
Approche théorique
Défintion
Une expression régulière sur un alphabet V est définie de la manière suivante :
(i) le mot vide ε est une expression régulière ;
(ii) si a est un élément de V , alors a est une expression régulière ;
(iii) si R est une expression régulière, alors R∗ est aussi une expression régulière ;
(iv) si R et S sont des expressions régulières, alors (RS) et (R + S) sont aussi des
expressions régulières.
Remarque : On peut supprimer les parenthèses les plus extérieures dans une expression régulière. On peut aussi supprimer certaines autres parenthèses en considérant que
l’opération produit est prioritaire par rapport à l’opération union, et que les opérations
produit et union sont associatives.
Ainsi, l’expression ((a + (bc))∗ ((cd)e)) peut être écrite plus simplement:
(a + bc)∗ cde
9.1.2
Langages décrits par une expression régulière
Toute expression régulière sur V décrit un langage sur V .
(i) le mot vide ε décrit le langage qui comporte comme seul élément le mot vide : {ε} ;
(ii) a décrit le langage qui comporte comme seul élément le symbole a : {a} ;
(iii) si à R est associé le langage L, alors à R∗ est associé le langage noté L∗ tel que,
pour tout nombre entier n, si x1 , x2 , . . . , xn sont des mots de L, alors x1 x2 ...xn est un mot
de L∗ . En particulier, ε appartient à L∗ .
(iv) si à R et à S sont respectivement associés les langages L1 et L2 , alors :
- à (RS) est associé le produit des langages, c’est-à-dire le langage, noté L1 L2 , et tel
que pour tout mot x de L1 , pour tout mot y de L2 , xy appartient à L1 L2 .
- à (R + S) est associée l’union des langages, notée L1 ∪ L2 : x appartient à L1 ∪ L2 si
et seulement si x appartient à L1 ou x appartient à L2 .
Par définition, un langage qui peut être décrit par une expression régulière est un
langage rationnel.
9.2
La syntaxe des expressions régulières en Python
En Python une expression régulière est écrite sous la forme d’une suite de caractères. La
syntaxe est distincte de celle de l’approche théorique pour des raisons de commodité, mais
également pour rendre compte des aspects procéduraux de l’utilisation des expressions
régulières par le langage de programmation.
Les écritures ci-dessous permettent de décrire un caractère.
51
[ ]
[^ ]
.
\s
\S
\+
définit un ensemble de caractères
définit le complément d’un ensemble de caractères
représente tout caractère
tout espace
tout caractère autre qu’un espace
le caractère + ; on obtient de même les autres caractères spéciaux
Ainsi, [ae3f] décrit un caractère qui est soit a, soit e, soit 3, soit f. [a-z] décrit
toutes les lettres minuscules, [a-zA-Z] décrit toutes les lettres minuscules ou majuscules,
[0-9] décrit tous les chiffres, [^ab] décrit tout caractères différent de a et b.
Les écritures ci-dessous permettent de définir à partir d’une ou plusieurs expression(s)
régulière(s) une nouvelle expression régulière.
EF
E|F
E*
E+
E?
E{m,n}
E{m}
E suivi de F
E ou F
répétition, éventuellement 0 fois, de E
répétition, au moins une fois, de E
E une fois ou 0 fois
entre m et n répétitions de E
au minimum m répétitions de E
En fait les différents opérateurs indiqués s’appliquent à un seul caractère. Si on veut
qu’ils s’appliquent à une expression plus longue on utilise des parenthèses mais avec une
syntaxe particulière.
(?:ab)*a représente ainsi l’expression régulière (ab)∗ a.
9.3
Les opérations sur les expressions régulières
On importe le module re.
>>> import re
L’idée est de chercher un motif, autrement dit une expression régulière, dans une suite
de caractères.
Il y a plusieurs façons de le faire, mais l’écriture même de l’expression régulière va
également déterminer le processus de recherche.
9.3.1
Les opérations
L’opération findall(motif, suite) renvoie une liste de suites de caractères : la liste
obtenue en extrayant de la suite les sous-suites qui correspondent au modèle. Toutefois, il
ne peut y avoir de recouvrement entre les sous-suites extraites : on cherche ces sous-suites
en parcourant la suite de gauche à droite. Par exemple :
52
>>> re.findall(’\sde[a-z]*\s’, ’les deux cours de demain soir’)
[’ deux ’, ’ de ’]
’ demain ’ ne peut être trouvé car le premier espace est déjà pris par ’ de ’.
L’opération sub(motif, rempl, suite) renvoie la suite de caractères obtenue en
remplaçant toutes les occurrences du motif (trouvées comme avec findall) par le remplaçant.
L’opération split(motif, suite) renvoie la liste de suites de caractères obtenue en
découpant la suite selon les occurrences du motif trouvées comme avec findall.
>>> re.sub(’\sde[a-z]*\s’, ’XXX’, ’les deux cours de demain soir’)
’lesXXXcoursXXXdemain soir’
>>> re.split(’\sde[a-z]*\s’, ’les deux cours de demain soir’)
[’les’, ’cours’, ’demain soir’]
9.3.2
Les groupes
En utilisant des parenthèses dans les motifs on définit des groupes, qui seront retrouvés
par les différentes opérations. C’est ainsi que si on définit des groupes dans une expression
régulière, le résultat de findall sera une liste de n-uplets de suites de caractères. Par
exemple :
>>> re.findall(’a(b.)[e-m]*(c.)’, ’xabdefcugabffcwr’)
[(’bd’, ’cu’), (’bf’, ’cw’)]
9.3.3
Les aspects procéduraux de l’écriture des expressions régulières
Étant donné l’usage qui en est fait, les expressions régulières ne décrivent pas réellement
un ensemble de suites de caractères mais décrivent le moyen de trouver une sous-suite dans
une suite donnée, c’est-à-dire qu’elles guident l’exécution d’une procédure. Cela se traduit
par diverses conséquences.
1. Les opérations *, +, ?, {m,n} trouvent la suite de caractères la plus longue possible qui est décrite par le motif. Si on veut la suite la plus courte possible, on ajoute un
point d’interrogation.
>>> re.findall(’de.*\s’, ’les deux cours de demain soir’)
[’deux cours de demain ’]
>>> re.findall(’de.*?\s’, ’les deux cours de demain soir’)
[’deux ’, ’de ’, ’demain ’]
>>> re.findall(’ab?b’, ’xabby’)
[’abb’]
53
>>> re.findall(’ab??b’, ’xabby’)
[’ab’]
2. L’opération ou n’est pas symétrique. Si on cherche E|F et que E est trouvée on
n’ira pas chercher F . On a ainsi :
>>> re.findall(’(?:a.)|(?:aba)’, ’xabacy’)
[’ab’, ’ac’]
>>> re.findall(’(?:aba)|(?:a.)’, ’xabacy’)
[’aba’]
3. On peut chercher un motif dans une suite en exigeant que ce motif soit suivi ou
précédé d’un autre motif, ou au contraire qu’il ne soit pas suivi ou précédé d’un autre
motif, sans que cet autre motif soit intégré dans les sous-suites trouvées.
E(?=F)
E(?!F)
(?<=F)E
(?<!F)E
E
E
E
E
doit être suivi de F
ne doit pas être suivi de F
doit être précédé par F
ne doit pas être précédé par F
Par exemple :
>>> re.findall(’(?<=c)a.(?!d)’, ’xabcamcaedcacaped’)
[’am’, ’ac’, ’ap’]
>>> re.findall(’(?<!b)c.(?=[em])’, ’xabcamcaedcacaped’)
[’ca’]
4. On peut vouloir que le motif soit recherché au début de la suite de caractères, ou à
la fin 18 .
On utilise les symboles ^ et $.
9.4
9.4.1
Utilisation des objets
Les objets de type expression régulière
On peut compiler un motif afin d’obtenir un objet de type expression régulière. On
écrit :
>>> er = re.compile(motif)
Ceci permet notamment d’accélérer l’exécution des programmes.
Aux objets de type expression régulière sont attachés des attributs et des méthodes.
Entre autres, l’attribut pattern permet de retrouver le motif qui a permis la constitution
18. Auquel cas on ne pourra évidemment trouver qu’une occurrence du motif, sauf dans le mode Multiline.
54
de l’expression régulière.
Des options peuvent être définies à la compilation. Elles sont indiquées comme deuxième
argument de l’opération compile, éventuellement reliées par l’opérateur (( | )). Parmi ces
options :
I ignorer la casse
M recherche ligne par ligne et pas sur la totalité d’une suite
S . correspond à tout caractère, y compris le saut de ligne
U Unicode
On peut ainsi écrire :
>>> er = re.compile(motif, re.I|re.M))
9.4.2
Les méthodes sur les expressions régulières
En appliquant les opérations match ou search à un objet de type expression régulière
on obtient un objet de type MatchObject, ou None si la correspondance n’est pas trouvée.
L’opération search cherche dans la suite une (première) occurrence du motif.
L’opération match cherche à vérifier que la suite tout entière correspond au motif.
Remarquons qu’il est possible d’appliquer match et search directement sur les motifs,
sans passer par la compilation.
Inversement, findall, sub et split peuvent être utilisées comme des méthodes sur
les expressions régulières.
9.4.3
Les objets correspondants
Ces objets admettent aussi des attributs et des méthodes.
La méthode start() donne la position dans la suite du début de la sous-suite qui a
été trouvée.
La méthode end() donne la position dans la suite de la fin de la sous-suite qui a été
trouvée.
On obtient donc la sous-suite en écrivant :
>>> er = re.compile(motif))
>>> ob = re.serach(suite))
>>> sousSuite = suite[ob.start():ob.end()]
La méthode groups() renvoie un n-uplet constitué de tous les groupes utilisés.
La ùéthode group(i) va renvoyer le i-ème groupe.
55
10 Programmes récursifs
10.1
La récursivité
A l’aide des instructions d’itération on définit des programmes dont le nombre d’instructions qui sont exécutées peut être très largement supérieur au nombre d’instructions
écrites 19 .
Mais il est possible d’écrire de tels programmes sans utiliser d’itérations : on fait alors
appel à la récursivité. Nous n’évoquerons ici la récursivité qu’à travers des fonctions.
Une fonction récursive est une fonction qui fait un appel à elle-même dans sa définition.
Prenons un exemple dans le domaine courant. Soit la question : x est-il l’ancêtre de y?
On peut énoncer la construction de la réponse à cette question sous la forme suivante :
- si y est Adam ou Eve, alors la réponse est négative (n’a pas d’anc^
etre);
- si x est le père ou la mère de y, alors c’est son anc^
etre;
- si x est l’anc^
etre du père ou de la mère de y, alors c’est son anc^
etre.
On voit que pour définir la notion d’ancêtre, on fait appel à la notion d’ancêtre. Ecrit
sous la forme d’un programme, en supposant que les opérations pere et mere soient
connues, on obtient:
Programme 10.1
def ancetre(x, y) :
if y == "Adam" or y == "Eve" : return 0
else :
if x == pere(y) or x == mere(y) : return 1
else : return ancetre(x,pere(y)) or ancetre(x,mere(y))
On peut se demander comment il se fait que l’appel de la fonction ancetre par ellemême ne produit pas un processus sans fin.
La réponse est dans le fait que certaines exécutions du programme ne comportent pas
d’auto-appel : les auto-appels sont insérés dans des instructions conditionnelles et, selon
la réponse à des tests, ils sont atteints ou pas.
Dans une fonction récursive, il faut donc que les premières instructions permettent
d’arrêter la récursivité.
10.2
Exemples
Ce premier exemple calcule la somme des n premiers nombres entiers.
19. Il peut même arriver que ce nombre devienne infini, dans le cas des programmes qui (( bouclent )).
56
Programme 10.2
def somme(n) :
if n == 0 : return 0
else : return somme(n-1) + n
L’exécution d’un programme appelant la fonction récursive somme(5) conduira aux
appels successifs de somme(4), somme(3), somme(2), somme(1) et somme(0).
Ce dernier appel conduira à l’exécution du programme sans auto-appel, le résultat
étant la valeur 0. L’arrêt de la récursivité permettra ensuite de trouver les résultats, dans
l’ordre inverse de leurs appels, de somme(1), somme(2), somme(3), somme(4) et enfin de
somme(5).
Ci-dessous, on écrit de manière récursive une fonction qui calcule la somme terme à
terme de deux listes de nombres. Si l’une des deux listes est plus longue que l’autre, on
ne s’intéresse pas aux derniers termes de la liste la plus longue.
Programme 10.3
def sigma(l1,l2) :
if l1 == [ ] or l2 == [ ] : return [ ]
else : return [l1[0]+l2[0]] + sigma(l1[1:],l2[1:])
Remarquons que pour une liste l non vide, on peut accéder à son premier élément par
l[0], et au reste de la liste par l1[1:].
10.3
Récursivité non directe
Le plus souvent la récursivité est obtenue de manière non directe. Par exemple si une
fonction f appelle une fonction g, laquelle appelle la fonction f.
Ci-dessous un exemple : on vérifie si une liste a un nombre impair d’éléments. A cette
fin, on passe par la vérification du fait que son reste possède un nombre pair d’éléments.
def impair(l):
if l==[] : return 0
else : return pair(l[1:])
def pair(l):
if l==[] : return 1
else : return impair(l[1:])
57

Documents pareils