Programmation web en python cours et exemples
Table des matières
Cet ouvrage :
Eléments de programmation (en Python)
C’est le support principal du cours Eléments de programmation I proposé aux étudiants de licence première année (premier semestre) à l’Université Pierre et Marie Curie.
Dispensé à plus de 800 étudiants chaque année (en plus des étudiants CNED), ce cours d’introduction à l’informatique utilisait précédemment (de 1999 à 2013) le langage Scheme pour illustrer les concepts du cours. Pour des raisons diverses, il a été décidé de changer de langage support pour adopter Python (version 3) mais la philosophie de ce cours a été préservéé.
— le cours n’est pas une énumération des constructions syntaxique du langage de support
— l’accent est mis sur la spécification de problèmes (souvent à thématique scientifique) et leur résolution par la conception d’un algorithme et son implantation dans le langage de support
De ce fait, les constructions syntaxiques principales du langage support sont abordées mais dans le cadre d’une méthodologie. Cette approche nous semble essentielle dans un cursus universitaire scientifique, ou l’informatique sera pour la plupart des étudiants un de leurs principaux outils (à l’exception des informaticiens pour qui cela sera la finalité).
Ce cours est donc tout à fait compatible avec un apprentissage plus classique du langage Python 3 à travers ses constructions syntaxiques.
Un ouvrage compagnon :
Eléments de programmation (en Python) – Recueil d’exercices
est également disponible sous les mêmes conditions.
Python est un langage de programmation très répandu, utilisé dans le monde scientifique et industriel, et reconnu pour certaines de ses vertus pédagogiques.
D’après wikipedia (en date du 28 août 2014) :
Python est un langage de programmation objet, multi-paradigme et multi-plateformes. Il favorise la programmation impérative structurée et orientée objet. Il est doté d’un typage dynamique fort, d’une gestion automatique de la mémoire par ramasse-miettes et d’un système de gestion d’exceptions ; il est ainsi similaire à Perl, Ruby, Scheme, Smalltalk et Tcl.
Le langage Python est placé sous une licence libre proche de la licence BSD2 et fonctionne sur la plupart des plates-formes informatiques, des supercalculateurs aux ordinateurs centraux, de Windows à Unix en passant par GNU/Linux, Mac OS, ou encore Android, iOS, et aussi avec Java ou encore .NET. Il est conçu pour optimiser la productivité des programmeurs en offrant des outils de haut niveau et une syntaxe simple à utiliser.
Il est également apprécié par les pédagogues qui y trouvent un langage où la syntaxe, clairement séparée des mécanismes de bas niveau, permet une initiation aisée aux concepts de base de la programmation.
Important : le cours Eléments de programmation n’est pas un cours de Python mais un cours en Python. Le langage est utilisé pour illustrer les concepts et nous ne considérons qu’une partie de ses possibilités. A l’issue de ce cours, il est fortement suggérer d’aller plus loin dans la découverte de ce langage de programmation.
— Cours 1 : expressions arithmétiques et définitions de fonctions simples
— Cours 2 : suites d’instructions, variables et alternatives if
— Cours 3 : répétitions et boucle while
— Cours 4 : plus sur les boucles : correction, terminaison et efficacité
— Cours 5 : intervalles, itérations avec for et chaînes de caractères
— Cours 6 : listes
— Cours 7 : n-uplets, plus sur les listes et fonctionnelles
— Cours 8 : compréhensions de listes
— Cours 9 : ensembles et dictionnaires
— Cours 10 : compréhensions d’ensembles et de dictionnaires — Cours 11 : cours d’ouverture sur les objets
Les contributeurs principaux de ce document sont membres de l’équipe pédagogique du cours Eléments de programmation I (codifié 1i-001).
— Frédéric Peschanski, Maître de conférences en informatique UPMC / LIP6 (responsable d’édition, contributeur principal)
— Fabien Tarissan, Maître de conférences en informatique UPMC / LIP6 (contributeur majeur)
— Romain Demangeon, Maître de conférences en informatique UPMC / LIP6 (contributeur)
— Christophe Marsala, Professeur en informatique UPMC / LIP6 (contributeur, responsable des supports CNED)
— Antoine Genitrini, Maître de conférences en informatique UPMC / LIP6 (contributeur) — Maryse Pelletier, Maître de conférences en informatique UPMC / LIP6 (contributrice)
— Clémence Magnien, Directrice de Recherches en informatique UPMC / LIP6 (contributrice)
— Choun Tong Lieu, Maître de conférences en informatique UPMC / PPS (contributeur) — Pascal Manoury, Maître de conférences en informatique UPMC / PPS (contributeur)
— Mathilde Carpentier, Maître de conférences en informatique UPMC / LIP6 (contributrice)
— Isabelle Mounier, Maître de conférences en informatique UPMC / LIP6 (contributrice)
— Marie-Jeanne Lesot, Maître de conférences en informatique UPMC / LIP6 (contributrice, responsable des supports CNED)
Nous remercions également l’ensemble des membres de l’équipe pédagogique 1i001 ainsi que l’ensemble des étudiants pour les nombreux retours sur ce cours.
Cet ouvrage :
Eléments de programmation (en Python)
est copyright :
© 2014-2015 Equipe enseignante 1i001 / UPMC (cf. section contributeurs ci-dessous) et distribué sous licence CC BY-SA 3.0 FR reproduire en annexe.
En résumé, ceci vous permet :
— de partager et éventuellement adapter ce document (CC: Creative Commons)
— en attribuant correctement le document original aux contributeurs originaux et en expliquant clairement les modifications effectuées au document (BY: attribution) — en distribuant le document ou des versions modifiée sous les même condition de licence (SA: share-alike).
Toujours d’après Wikipedia (en date du 28 août 2014) :
Un programme informatique est une séquence d’instructions qui spécifie étape par étape les opérations à effectuer pour obtenir un résultat. Il est exprimé sous une forme qui permet de l’utiliser avec une machine comme un ordinateur pour exécuter les instructions. Un programme est la forme électronique et numérique d’un algorithme exprimé dans un langage de programmation - un vocabulaire et des règles de ponctuation destinées à exprimer des programmes.
def liste_premiers(n): """int -> list[int] Hypothèse : n >= 0 Retourne la liste des nombres premiers inférieurs à n.""" # i_est_premier : bool i_est_premier = False # indicateur de primalité # L : list[int] L = [] # liste des nombres premiers en résultat # i : int (entier courant)for i in range(2, n): i_est_premier = True # j : int (candidat diviseur)for j in range(2, i - 1): if i % j == 0: # i divisible par j, donc i n'est pas premier i_est_premier = False if i_est_premier: L.append(i) return L |
>>> liste_premiers(30)
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
Au-delà du sens exact de ce programme, nous pouvons en extraire les principaux constituants : — définitions de fonction,
— variables,
— expressions et applications,
— instructions : affectation, alternative, suite d’instructions, boucle.
Remarque: Contrairement à de nombreux autres langages de programmation, le langage Python impose les indentations c’est-à-dire les retours de ligne et le nombre d’espaces nécessaires ensuite. Nous reviendrons sur ce point à de nombreuses reprises.
La construction :
def liste_premiers(n):
etc est une définition de fonction de nom liste_premiers et avec un paramètre formel dont le nom est n
L’identifiant i_est_premier est un nom de variable. Nous verrons les variables au prochain cours.
Les constructions telles que 1, i-1, i % j == 0 sont des expressions, ici arithmétiques (1, i-1, et i % j) ou booléennes (i % j == 0).
L’écriture :
liste_premiers(30)
est une expression dite application de fonction utilisateur. La fonction appelée se nomme liste_premiers et son unique argument d’appel est 30.
La construction i_est_premier = True est une instruction dite d’affectation à la variable de nom i_est_ premier.
La construction :
if i_est_premier:
etc
est une instruction dite alternative. Les alternatives seront vues au cours 2.
La construction : for i in range(1, n):
etc
est une instruction dite de boucle. Les boucles seront abordées au cours 3.
Nous allons commencer par expliquer le premier type fondamental d’élément de programmation que l’on nomme expression. Une expression en informatique est une écriture formelle textuelle que l’on peut saisir au clavier. Le langage des expressions que l’on peut saisir est un langage informatique, beaucoup plus contraint que les langages naturels comme le français par exemple. En effet, c’est à l’ordinateur que l’on s’adresse et non à un autre humain.
Dans ce cours, les expressions sont écrites dans le langage Python, mais il existe bien sûr de nombreux autres langages informatiques (C, C++, java, ocaml, etc.).
Les expressions sont de deux types :
— expressions atomiques (ou atomes) : la forme la plus simple d’expression. On parle également d’expressions simples.
— expressions composées : expressions (plus) complexes composées de sousexpressions, elles-mêmes à nouveau simples ou composées.
La propriété fondamentale d’une expression est d’être évaluable, c’est-à-dire que chaque expression possède une valeur que l’on peut obtenir par calcul. En arithmétique, la valeur d’une expression est soit un entier de type int ou un flottant de type float.
Une expression atomique v possède un type T et - c’est ce qui caractérise la propriété d’atomicité - sa valeur est également notée v.
Par exemple :
>>> 42
42
Ici, on a saisi l’entier 42 au clavier et python nous a répondu également 42. Le type de cette expression atomique est : int, le type des nombres entiers. Nous pouvons vérifier ce fait par la fonction prédéfinie type qui retourne la description du type d’une expression.
>>> type(42) int
Le processus d’évaluation mis en jeu ci-dessus pour évaluer l’expression 42 est en fait plus complexe qu’il n’y paraît.
Lorsque l’on tape 42 au clavier et qu’on soumet cette expression à l’évaluateur de Python, ce dernier doit transformer ce texte en une valeur entière représentable sur ordinateur. Cette traduction est assez complexe, il faut notamment représenter 42 par une suite de 0 et de 1 les fameux bits d’information - au sein de l’ordinateur (on parle alors de codage binaire). Cette représentation dans la machine se nomme en python un objet. On dit que le type de l’expression 42 est int (entier) mais pour la représentation interne, on dit que l’objet qui représente 42 en mémoire est de la classe int.
Cette terminologie fait de Python un langage objet et nous reviendrons sur ce point, notamment lors du dernier cours d’ouverture sur ce thème.
Le processus dit d’auto-évaluation des expressions atomiques n’est pas terminé. Dans un second temps, Python nous «explique» qu’il a bien interprété l’expression saisie en produisant un affichage de la valeur. Dans cette deuxième étape l’objet en mémoire qui représente 42 est converti en un texte finalement affiché à l’écran.
Nous avons fait la distinction entre :
— une expression d’un type donné, saisie par le programmeur, par exemple l’expression 42 de type int,
— la valeur de l’expression : un objet représenté en mémoire d’une classe donnée, par exemple la représentation interne (codée en binaire) de l’entier 42, objet de la classe int,
— l’affichage de cet objet en sortie, par exemple la suite de symboles 42 pour l’affichage de l’entier 42.
Avec un peu de pratique, le programmeur ne voit qu’un seul 42 à toutes les étapes mais il faut être conscient de ces distinctions pour comprendre pourquoi et comment l’ordinateur est capable d’effectuer nos calculs.
Retenons donc le principe simplifié d’évaluation des expressions atomiques :
Une expression atomique s’évalue en elle-même, directement, sans calcul ni travail particulier.
Effectuons maintenant un tour d’horizon des principales expressions atomiques fournies par Python.
2.2.1.1 Les constantes logiques (ou booléens) La vérité logique est représentée par l’expression True qui signifie vrai et l’expression False qui signifie faux. Ces deux atomes forment le type booléen dénoté bool du nom du logicien George Boole qui au XIXème siècle a établi un lien fondamental entre la logique et le calcul.
>>> type(True) bool
>>> type(False) bool
2.2.1.2 Les entiers Les entiers sont écrits en notation mathématique usuelle.
>>> type(4324) int
Une remarque importante est que les entiers Python peuvent être de taille arbitraire.
>>> 23239287329837298382739284739847394837439487398479283729382392283
23239287329837298382739284739847394837439487398479283729382392283
Dans beaucoup de langages de programmation (exemple : le langage C) les entiers sont ceux de l’ordinateur et le résultat aurait donc été tronqué.
Sur une machine 32 bits, l’entier (signé) maximal que l’on peut stocker dans une seule case mémoire est 231.
>>> 2 ** 31 2147483648 |
# l'opérateur puissance se note ** en python. |
Ceci illustre un aspect important de Python : l’accent est mis sur la précision et la généralité des calculs plutôt que sur leur efficacité. Le langage C, par exemple, fait plutôt le choix opposé de se concentrer sur l’efficacité au détriment de la précision et de la généralité. On dit que Python est (plutôt) un langage de haut-niveau, et que le langage C est (plutôt) un langage de bas-niveau.
2.2.1.3 Les constantes à virgule flottante. Les expressions atomiques 1.12 ou -4.3e-3 sont de type flottant noté float.
Les flottants sont des approximations informatiques des nombres réels de R, qui eux n’existent qu’en mathématiques. Dans ce cours d’introduction, nous ne nous occuperons pas trop des problèmes liés aux approximations, mais il faut savoir que ces problèmes sont très complexes et sont sources de nombreux bugs informatiques, certains célèbres comme le bug de la division en flottant du Pentium, cf.
>>> type(-4.3e-3) float
Il est important de remarquer que les entiers et les flottants sont des types disjoints mais comme beaucoup d’autres langages de programmation, Python convertit implicitement les entiers en flottants si nécessaire.
Par exemple :
>>> type(3 + 4.2) float
Ici 3 est un entier de type int et 4.2 est de type float. Le résultat de l’addition privilégie les flottants puisqu’en effet on s’attend au résultat suivant :
>>> 3 + 4.2
7.2
Remarque : lors de certains calculs, on ne veut pas distinguer entre entiers et flottants (par exemple le calcul de la valeur absolue). Dans ce cas on utilisera le type générique Number pour désigner les nombres en général. Nous reviendrons sur ce point dès le prochain cours.
2.2.1.4 Les chaînes de caractères Les chaînes de caractères de type str ne sont pas à proprement parler atomiques, mais elles s’évaluent de façon similaire.
Une chaîne de caractères est un texte encadré soit par des apostrophes ou guillemets simples ' :
>>> 'une chaîne entre apostrophes'
'une chaîne entre apostrophes'
Soit par des guillemets à l’anglaise " ou guillemets doubles :
>>> "une chaîne entre guillemets"
'une chaîne entre guillemets'
On remarque que Python privilégie les apostrophes pour les affichages des chaînes de caractères. Ceci nous rappelle d’ailleurs bien ici le processus en trois étapes : lecture de l’expression (avec guillemets doubles), conversion en un objet en mémoire, puis écriture de la valeur correspondante (avec guillemets simples).
Il existe d’autres types d’expressions atomiques que nous aborderons lors des prochains cours.
2.2.2 Expressions composées
Les expressions composées sont formées de combinaisons de sous-expressions, atomiques ou elles-mêmes composées.
Pour ne pas trop charger ce premier cours nous nous limiterons dans nos exemples aux expressions arithmétiques, c’est-à-dire aux expressions usuelles des mathématiques, le langage des calculs simples sur les nombres. Nous aborderons d’autres types d’expressions lors des prochains cours, mais il faut retenir que la plupart des concepts étudiés ici dans le cadre arithmétique restent valables dans le cadre plus général.
Pour composer des expressions arithmétiques, le langage fournit diverses constructions, notamment :
— les expressions atomiques entiers et flottants vues précédemment
— des opérateurs arithmétiques
— des applications de fonctions prédéfinies en langage Python ou définies par le programmeur.
2.2.2.1 Opérateurs arithmétiques Le langage Python fournit la plupart des opérateurs courants de l’arithmétique :
— les opérateurs binaires d’addition, de soustraction, de multiplication et de division
— l’opérateur moins unaire
— le parenthésage
La notation utilisée suit l’usage courant des mathématiques.
Par exemple, on peut calculer de tête assez rapidement les expressions suivantes :
>>> 2 + 1
3
>>> 2 + 3 * 9
29
>>> (2 + 3) * 9
45
>>> (2 + 3) * -9
-45
Remarque importante sur la division : en informatique on distingue généralement deux types de division entre nombres :
- la division entière ou euclidienne qui est notée // en Python
- la division flottante qui est notée /.
Voici quelques exemples illustratifs.
>>> 7.0 / 2.0
3.5
Ici on a divisé deux flottants par la division flottante. Le résultat est aussi un flottant. Divisons maintenant dans les entiers :
>>> 7 // 2
3
Ici le résultat est bien un entier : 3 est le quotient de la division entière de 7 par 2. Nous pouvons d’ailleurs obtenir le reste de la division entière avec l’opérateur modulo (qui est noté % en Python).
>>> 7 % 2
1
Le reste de la division entière de 7 par 2 est bien 1, on a : 7 qui vaut 2 ? 3 + 1
Mais maintenant, que se passe-t-il si on utilise la division flottante pour diviser des entiers ? >>> 7 / 2
3.5
Puisque la division flottante nécessite des opérandes de type float, l’entier 7 a été converti implicitement en le flottant 7.0 et l’entier 2 a été converti en le flottant 2.0. C’est donc sans surprise (supplémentaire !) que le résultat produit est bien le flottant 3.5.
On retiendra de cette petite digression que les divisions informatiques ne sont pas simples.
Priorité des opérateurs
Les règles que nous appliquons implicitement dans nos calculs mentaux doivent être explicitées pour l’ordinateur. Pour cela, les opérateurs sont ordonnés par priorité.
Retenons les règles de base de priorité des opérateurs :
— la multiplication et la division sont prioritaires sur l’addition et la soustraction
— le moins unaire est prioritaire sur les autres opérateurs
— les sous-expressions parenthésées sont prioritaires
Dans l’expression 2 + 3 * 9 ci-dessus, on évalue la multiplication avant l’addition. Le processus de calcul est donc le suivant :
2 + 3 * 9
==> 2 + 27
==> 29
Pour forcer une priorité et changer l’ordre d’évaluation, on utilise, comme en mathématiques, des parenthèses. On a ainsi :
(2 + 3) * 9
==> 5 * 9
==> 45
Exercice - Donner la valeur des expressions suivantes :
— 4 + 7 * 3 - 9 * 2
— (4 + 7) * 3 - 9 * 2
— 4 + 7 * (3 - 9) * 2
— 4 + (7 * 3 - 9) * 2
— 4 + (7 * - 3 - 9) * 2
— 4 + 3 / 2
— 4 + 3 // 2
2.2.2.2 Applications de fonctions prédéfinies Le langage Python fournit un certain nombre de fonctions prédéfinies (en fait plusieurs centaines !). Les fonctions prédéfinies dites primitives sont accessibles directement.
Pour pouvoir utiliser une fonction, prédéfinie ou non, il nous faut sa spécification.
Prenons l’exemple de la fonction max qui retourne le maximum de deux nombres et qui est spécifiée de la façon suivante :
def max(a, b): """Number * Number -> Number retourne le maximum des nombres a et b.""" |
Par exemple :
>>> max(2,5)
5
>>> max(-5.12, -7.4)
-5.12
Dans la spécification de fonction, par exemple max, on trouve trois informations essentielles :
— l’en-tête de la fonction, def max(a, b), qui donne le nom de la fonction (max) et de ses paramètres formels (a et b).
— la signature de la fonction qui indique ici que le premier paramètre (a) est de type Number, le second paramètre (b) également de type Number (l’étoile * est le produit cartésien qui sépare les types des paramètres). La signature indique également après la flèche -> que la valeur de retour de la fonction max est de type Number.
— la description de la fonction qui explique le problème résolu par la fonction. Ici, la fonction «retourne le maximum des nombres a et b».
Nous reviendrons longuement dans ce cours sur ce concept fondamental de spécification.
Le principe d’évaluation des appels de fonctions prédéfinies est expliqué ci-dessous :
Pour une expression de la forme fun(arg1, , argn)
— on évalue d’abord les expressions en arguments d’appels: arg1, , argn — puis on remplace l’appel par la valeur calculée par la fonction.
Considérons l’exemple suivant :
max(2+3, 4*12)
==> max(5, 48) [évaluation des arguments d'appel] ==> 48 [remplacement par la valeur calculée]
Un second exemple :
3 + max(2, 5) * 9
L’opérateur de multiplication est prioritaire mais on doit d’abord évaluer son premier argument, c’est-à-dire l’appel à la fonction max. Cela donne :
==> 3 + 5 * 9
Et on procède comme précédemment :
==> 3 + 45 ==> 48
Ce que l’on vérifie aisément en python :
>>> 3 + max(2, 5) * 9
48
Les fonctions prédéfinies primitives ne sont pas très nombreuses. En revanche, Python fournit de nombreuses bibliothèques de fonction prédéfinies que l’on nomme des modules.
La carte de référence résume les fonctions de bibliothèque que vous pouvez utiliser dans le cadre de ce cours. Il s’agit du seul document autorisé lors des épreuves : devoir sur table, TME en solitaire et examen.
Important : nous ne distribuons qu’un seul exemplaire par étudiant donc prenez soin de votre carte de références et ne la perdez pas !
Pour ce qui concerne l’arithmétique, la plupart des fonctions intéressantes se trouvent dans le module math.
Pour pouvoir utiliser une fonction qui se trouve dans une bibliothèque, il faut tout d’abord importer le module correspondant à cette bibliothèque.
Par exemple, pour importer le module math on écrit :
import math # bibliothèque mathématique
Prenons l’exemple de la fonction de calcul du sinus d’un angle, dont la spécification est la suivante :
# fonction présente dans le module mathdef sin(x): """Number -> Number retourne le sinus de l'angle x exprimé en radians.""" |
Attention : la fonction sin du module math s’appelle en fait .
Voici quelques exemples d’utilisation.
>>> (0)
0.0
>>> ( / 2)
1.0
On remarque ici que la constante ? est aussi définie dans le module math et qu’elle s’écrit en Python.
>>>
3.141592653589793
2.2.2.3 Principe d’évaluation des expressions arithmétiques Nous pouvons maintenant résumer le principe d’évaluation des expressions arithmétiques.
Pour évaluer une expression e:
- si e est une expression atomique alors on termine l’évaluation avec la valeur e.
- si e est une expression composée alors :
- on détermine la sous-expression e0 de e à évaluer en premier (cf. règles de priorité des opérateurs).
— si e0 est unique (il n’y a qu’une seule sous-expression prioritaire) alors on lui applique le principe d’évaluation (on passe à l’étape 1 donc).
— s’il existe plusieurs expressions à évaluer en premier, alors on procède de gauche à droite et de haut en bas.
- on réitère le processus d’évaluation jusqu’à sa terminaison.
Considérons l’exemple suivant :
(3 + 5) * ( max(2, 9) - 5 ) - 9
Il s’agit bien sûr d’une expression composée. Les sous-expressions prioritaires sont entre parenthèses (le parenthésage est toujours prioritaire). Il y en a deux ici : (3 + 5) et ( max(2, 9) 5 ). On procède de gauche à droite donc on commence par la première :
==> 8 * ( max(2, 9) - 5 ) - 9 [parenthésage - gauche]
La deuxième sous-expression prioritaire est la soustraction dont on évalue les arguments de gauche à droite, en commençant donc par l’appel de la fonction max :
==> 8 * ( 9 - 5 ) - 9 [appel de la fonction max]
Maintenant on peut évaluer la soutraction prioritaire car entre parenthèses :
==> 8 * 4 - 9 [soustraction entre parenthèses]
On s’occupe ensuite de la multiplication prioritaire :
==> 32 - 9 [multiplication prioritaire]
Et finalement la dernière soustraction est possible :
==> 23 [soustraction]
>>> (3 + 5) * ( max(2, 9) - 5 ) - 9
23
Si ces principes d’évaluation peuvent apparaître un peu complexes, on retiendra qu’ils sont assez proches de notre propre manière de calculer.
Les fonctions occupent une place centrale en programmation, notamment dans les langages Python et C que vous verrez cette année.
Considérons le problème suivant :
Calculer le périmètre d’un rectangle défini par sa largeur et sa longueur.
La solution mathématique du problème consiste à calculer la valeur de l’expression suivante :
2 * (largeur + longueur)
Par exemple, si on souhaite calculer en Python le périmètre d’un rectangle de largeur valant 2 unités (des mètres par exemple) et de longueur valant 3 unités, on saisit l’expression suivante :
>>> 2 * (2 + 3)
10
Le périmétre obtenu est donc de 10 unités.
Maintenant si l’on souhaite calculer le périmètre d’un rectangle de largeur valant 4 unités et de longueur valant 9 unités, on saisit alors l’expression :
>>> 2 * (4 + 9)
26
Le premier usage que nous ferons des fonctions est de permettre de paramétrer, et donc de généraliser, les calculs effectués par une expression arithmétique.
Pour le calcul du périmètre, en mathématiques on écrirait quelque chose comme ce qui suit :
Le périmètre p d’un rectangle de largeur l et de longueur L est :
p = 2 ? (l + L)
Cette expression arithmétique peut être traduite presque directement en Python, de la manière suivante :
def perimetre(largeur, longueur): """int * int -> int hypothèse : (longueur >= 0) and (largeur >= 0) hypothèse : longueur >= largeur retourne le périmètre du rectangle défini par sa largeur et sa longueur."""return 2 * (largeur + longueur) |
Une fois écrite, on peut utiliser la fonction avec quelques exemples:
>>> perimetre(2, 3)
10
>>>
De ces exemples d’application de la fonction perimetre nous pouvons déduire un jeu de tests permettant de valider la fonction.
# Jeu de testsassert perimetre(2, 3) == 10 assert perimetre(4, 9) == 26 assert perimetre(0, 0) == 0 assert perimetre(0, 8) == 16
Remarque : le mot-clé assert permet de définir un test. Le premier test ci-dessus signifie «Le programmeur de la fonction certifie que le périmètre d’un rectangle de largeur 2 et de longueur 3 vaut 10».
Si une telle assertion ne correspond pas à la définition de fonction, une exception est levée et une erreur se produit. Par exemple :
>>> assert perimetre(2, 3) == 12 --------------------------------------------------------------------------AssertionError Traceback (most recent call last) ----> 1 assert perimetre(2, 3) == 12 AssertionError: |
Illustrons un autre intérêt de bien spécifier les fonctions :
>>> help(perimetre) Help on function perimetre in module __main__: perimetre(largeur, longueur) int * int -> int |
hypothèse : (longueur >= 0) and (largeur >= 0) hypothèse : longueur >= largeur retourne le périmètre du rectangle défini par sa largeur et sa longueur.
Détaillons le processus qui conduit du problème de calcul de périmètre à sa solution informatique sous la forme de la fonction perimetre.
Les trois étapes fondamentales sont les suivantes :
- la spécification du problème posé et devant être résolu par la fonction, comprenant :
- l’en-tête de la fonction
- la signature de la fonction
- les hypothèses éventuelles pour une bonne application de la fonction
- la description du problème résolu par la fonction
- l’implémentation dans le corps de la fonction de l’algorithme de calcul fournissant une solution au problème posé
- la validation de la fonction par le biais d’un jeu de tests Détaillons ces trois étapes pour la fonction perimetre.
2.3.1 la spécification de la fonction
La spécification d’une fonction permet de décrire le problème que la fonction est censée résoudre. Cette spécification s’énonce dans un cadre standardisé pour ce cours.
Le rôle fondamental de la spécification est de permettre à un programmeur de comprendre comment utiliser la fonction sans avoir besoin d’une lecture détaillée de son code d’implémentation en Python. On retiendra la maxime suivante :
On n’écrit pas une fonction uniquement pour soi, mais également et surtout pour toute personne susceptible de l’utiliser dans un futur plus ou moins proche.
La partie spécification de la fonction perimetre est répétée ci-dessous.
def perimetre(largeur, longueur): """int * int -> int hypothèse : (longueur >= 0) and (largeur >= 0) hypothèse : longueur >= largeur retourne le périmètre du rectangle défini par sa largeur et sa longueur.""" |
La ligne
def perimetre(largeur, longueur):
se nomme l’en-tête de la fonction. Son rôle est de donner le nom de la fonction (qui est souvent à la fois le nom porté par le problème et sa solution), ici perimetre, ainsi que les noms de ses paramètres formels (ou plus simplement paramètres tout court). Pour notre problème de périmètre, les paramètres se nomment naturellement largeur et longueur.
Le mot-clé def de Python introduit la définition d’une fonction.
La partie de la spécification en dessous de l’en-tête se trouve entre des triples guillemets """ """.
Important : comme indiqué précédemment Python impose les indentations dans les programmes. Ainsi, après l’en-tête de fonction, il faut indenter par 4 espaces (ou une tabulation) les lignes qui composent la spécification et le corps de la fonction.
Sur la première ligne, directement à proximité des guillemets se trouve la signature de la fonction. Cette signature indique :
— les types des paramètres formels, séparés par le symbole * (signifiant le produit cartésien des types)
— le type de la valeur de retour
Ici, il est indiqué que le premier paramètre formel largeur est de type int, donc un entier. Le type du deuxième paramètre longueur est également int (pour simplifier on ne calcule que des périmètres entiers). Le type de la valeur de retour de la fonction se situe à droite de la flèche -> est également de type int.
La signature indique donc ici qu’à partir de deux entiers passés en paramètre, la fonction retourne un entier. On verra que la signature joue un rôle essentiel, notamment lorsque l’on manipule des types de données complexes comme les listes, les ensembles ou les dictionnaires.
La signature est souvent complétée par une ou plusieurs hypothèses permettant de préciser les domaines de valeurs possibles pour les paramètres. La fonction perimetre indique deux hypothèses :
- La largeur et la longueur doivent être positives, ce qui correspond à l’expression booléenne
: (longueur >= 0) and (largeur >= 0)
- La largeur doit être inférieure ou égale à la longueur : largeur <= longueur.
On retiendra que les hypothèses sont des expressions logiques Python, c’est-à-dire de type bool et s’évaluant donc soit en True, soit en False. Nous reviendrons sur les expressions logiques avec l’alternative lors du prochain cours.
Finalement, après une ligne vide, on donne la description en français du problème résolu par la fonction. L’objectif de cette description est d’indiquer le QUOI de la fonction, mais sans expliquer le COMMENT, c’est-à-dire sans préciser les calculs à effectuer.
2.3.2 l’implémentation du corps de la fonction
L’implémentation de la fonction consiste à programmer en Python un algorithme de calcul permettant de résoudre le problème posé. Cette implémentation représente le corps de la fonction composé d’une instruction ou d’une suite d’instructions.
Dans le cadre des problèmes consistant à paramétrer une expression arithmétique, comme pour notre calcul de périmètre, le corps de la fonction se résume à une instruction de la forme suivante
:
return
Cette instruction s’exécute ainsi : 1. évaluation de l’
- retour de la fonction à son appelant (voir plus loin) avec la valeur calculée, dite valeur de retour.
Dans notre cas c’est bien sûr notre expression paramétrée par la largeur et la longueur du rectangle :
return 2 * (largeur + longueur)
Important : en règle générale, un même problème peut être résolu de différentes façons. Ainsi on parle de la spécification et d’une implémentation (possible) de la fonction. Par exemple, pour notre fonction de calcul du périmètre, nous pouvons proposer une implémentation différente, par exemple :
return (largeur + longueur) + (largeur + longueur)
Pour l’instant nos définitions de fonctions sont très simples et concises en comparaison de la spécification du problème. Mais plus nous avancerons dans le cours plus cette tendance s’inversera, avec des fonctions de plus en plus complexes.
2.3.3 la validation de la fonction par un jeu de tests
Il faut distinguer deux “parties” distinctes dans l’approche d’un problème informatique :
— le client qui pose le problème à résoudre (par exemple : un enseignant qui rédige un exercice).
— le fournisseur qui propose une solution informatique au problème posé par le client (comme l’étudiant qui répond à l’exercice).
Dans ce cours, pour chaque problème posé par le client (l’enseignant), une définition de fonction sera rédigée par le fournisseur (l’étudiant).
On retiendra donc la maxime suivante :
un problème = une fonction pour le résoudre.
Cependant, proposer une spécification et une définition de fonction pour résoudre le problème ne suffit pas pour ce que l’on appelle l’étape de validation, c’est-à-dire l’acceptation (ou non !) de la solution par le client (par exemple, un enseignant qui corrige un exercice).
Pour assurer cette validation, l’objectif est de proposer un jeu de tests sous la forme d’une suite d’expressions d’appel de la fonction définie. Une expression d’appel est de la forme suivante :
nom_fonction(arg1, arg2, )
Il s’agit d’appeler la fonction en donnant des valeurs précises à ses paramètres. Les expressions qui décrivent la valeur prise par les paramètres se nomment les arguments d’appel.
Par exemple, dans notre premier test pour la fonction perimetre :
perimetre(2, 3)
— l’argument d’appel pour le paramètre largeur est l’expression 2 — l’argument d’appel pour la longueur est l’expression 3.
Si la définition de fonction répond bien au problème posé, la valeur retournée par l’expression d’appel sera valide.
>>> perimetre(2, 3)
10
Les expressions 2 et 3 sont ici des expressions simples, mais on peut aussi utiliser des expressions de complexité arbitraire.
>>> perimetre(1 * 14 - 2 * 6, (3 * 121 // 11) - 30)
10
Question : que pensez-vous de l’expression d’appel suivante ?
perimetre(2, 3.1)
Ici, on casse une règle fondamentale dans le cadre de notre cours :
les expressions d’appel de fonctions doivent respecter la signature et les hypothèses spécifiées par la fonction. Dans le cas contraire, aucune garantie n’est fournie concernant les effets éventuels de cette expression d’appel erronée.
Donc il est possible (voire probable) que Python “accepte” l’appel et produise une valeur, mais cette valeur obtenue ne correspond à aucun problème posé, en particulier celui censé être résolu par la fonction.
Dans l’expression d’appel ci-dessus, l’expression 3.1 de type float ne respecte pas la signature de la fonction perimetre qui indique que le second paramètre, la longueur, doit être de type int. On dit que cette expression n’est pas valide, et dans ce cas la meilleure correction est de tout simplement l’effacer ou la modifier pour la rendre valide.
Dans l’expression d’appel ci-dessous :
perimetre(-2, 3)
l’erreur commise, qui rend l’expression invalide, est que le premier argument d’appel est négatif alors qu’une hypothèse de la fonction perimetre indique que les valeurs prises par le premier paramètre largeur doivent être positives.
Dans le même ordre d’idée, l’expression :
perimetre(3, 2)
est invalide puisqu’elle contredit l’hypothèse que la largeur doit être inférieure à la longueur.
On retiendra finalement que le jeu de tests permettant de valider la fonction doit :
— être composé uniquement d’expressions d’appel valides, qui respectent les signatures et hypothèses de la fonction appelée
— couvrir suffisamment de cas permettant à l’exécutant d’avoir confiance dans la validité de sa solution.
Bien sûr, c’est bien le client (par exemple l’enseignant) qui décidera finalement si la solution proposée est bien valide ou non (par exemple : en décidant d’une note pour l’exercice).
L’écriture du jeu de test proprement dit se compose d’une suite d’assertions. Nous répétons ci-dessous le jeu de tests proposé pour la fonction périmètre.
# Jeu de testsassert perimetre(2, 3) == 10 assert perimetre(4, 9) == 26 assert perimetre(0, 0) == 0 assert perimetre(0, 8) == 16
Il existe une différence fondamentale entre expressions et instructions.
Les principes d’évaluation des expressions (arithmétiques) vues lors du cours précédent expliquent comment passer d’une expression à sa valeur. En comparaison, une instruction ne possède pas de valeur et ne peut donc pas être évaluée. On parle donc plutôt de principe d’interprétation
des instructions.
La seule instruction que nous avons utilisée lors du cours précédent est le retour de fonction :
return
Le principe d’interprétation du retour de fonction est le suivant : - évaluation de l’ en une valeur de retour - sortie directe de la fonction (où se trouve le return) avec la valeur de retour calculée précédemment.
Dans ce cours, nous allons enrichir le répertoire d’instructions que nous pouvons placer dans le corps des fonctions.
Nous allons notamment introduire :
— les suites d’instructions qui permettent de combiner les instructions de façon séquentielle,
— les variables qui permettent de stocker en mémoire des résultats intermédiaires,
— les alternatives qui permettent d’effectuer des branchements conditionnels dans les calculs.