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