Système d'Information et programmation Programmation Python Manipulation de données http://www.agroparistech.fr/Donnees-et-Programmation.html Christine MARTIN UFR d'Informatique Département MMIP [email protected] AgroParisTech Massy 1A apprentis Année 2015-2016 2 Table des matières 1 2 3 Introduction ................................................................................................................................5 1.1 Contexte, organisation et objectifs du module .....................................................................5 1.2 Choix du langage de programmation : pourquoi Python ......................................................5 1.3 Installer Python ...................................................................................................................6 Premiers programmes .................................................................................................................7 2.1 Environnement de développement......................................................................................7 2.2 Un exemple .........................................................................................................................8 Premiers pas en Python............................................................................................................. 10 3.1 4 5 3.1.1 Définitions et syntaxe ................................................................................................ 10 3.1.2 Les types prédéfinis ................................................................................................... 10 3.1.3 Conversion de types................................................................................................... 11 3.1.4 Variables et affectation (ou assignation) .................................................................... 12 3.1.5 Afficher la valeur d'une ou plusieurs variables ............................................................ 12 3.1.6 Opérateurs et expressions ......................................................................................... 13 3.2 Interactions avec l'utilisateur : les fonctions input() et raw_input() .................................... 15 3.3 Sélection ou exécution conditionnelle ............................................................................... 16 3.4 Instructions et blocs .......................................................................................................... 17 3.5 Les fonctions ..................................................................................................................... 18 3.6 Importer un module de fonctions ...................................................................................... 22 3.7 Structure générale d'un script Python ................................................................................ 25 Boucles et séquences ................................................................................................................ 27 4.1 Répétitions en boucle - l'instruction while ......................................................................... 27 4.2 Les séquences.................................................................................................................... 29 4.3 Utilisation des chaînes de caractères ................................................................................. 30 4.4 Les listes ............................................................................................................................ 35 4.5 Conversions liste – chaîne de caractères ............................................................................ 36 4.6 Retour sur l'affectation ...................................................................................................... 39 Programmation de graphiques - séquences ............................................................................... 41 5.1 6 Données, types et variables ............................................................................................... 10 Présentation des bibliothèques ......................................................................................... 41 5.1.1 La bibliothèque random ............................................................................................. 41 5.1.2 La bibliothèque matplotlib ......................................................................................... 42 Manipulation des Fichiers ......................................................................................................... 57 3 6.1 Ouvrir un fichier ................................................................................................................ 57 6.2 Fermer un fichier ............................................................................................................... 57 6.3 Méthodes de lecture ......................................................................................................... 57 6.4 Méthodes d’écriture : ........................................................................................................ 58 6.5 Méthodes seek et tell ....................................................................................................... 58 6.6 Mise en pratique ............................................................................................................... 59 7 Programmation de graphique - fichiers ..................................................................................... 61 8 Les bases de données et le langage SQL .................................................................................... 63 8.1 Quelques définitions.......................................................................................................... 64 8.2 La notion de clé ................................................................................................................. 67 8.2.1 Clé primaire ............................................................................................................... 67 8.2.2 Clé étrangère ............................................................................................................. 67 8.3 Contraintes d'intégrité ....................................................................................................... 68 8.4 Schéma et contenu ............................................................................................................ 69 8.5 Le langage SQL (Structured Query Language) ..................................................................... 71 9 8.5.1 Consulter les données stockées dans une base de données existante ........................ 71 8.5.2 Ajouter des données dans une table .......................................................................... 82 8.5.3 Supprimer des données dans une table ...................................................................... 83 8.5.4 Modifier le contenu d'une table ................................................................................. 83 8.5.5 Créer une base de données ........................................................................................ 86 8.5.6 Créer / Supprimer/ Modifier la structure d'une table ................................................. 86 Interaction avec des bases de données en Python ..................................................................... 89 9.1 Se connecter à une base de données SQLite en Python...................................................... 89 9.2 Interroger une base de données SQLite en Python............................................................. 90 9.3 Interrogation contextuelle ................................................................................................. 91 9.4 Se déconnecter d'une base de données SQLite en Python ................................................. 91 9.5 Insertions et suppressions ................................................................................................. 93 9.6 Création du schéma d'une base de données depuis un programme Python ....................... 94 10 Projet .................................................................................................................................... 95 4 Séance 1 1 Introduction 1.1 Contexte, organisation et objectifs du module Issu des domaines de l'informatique et des télécommunications, le concept de Système d’Information a envahi l'ensemble des organisations privées ou publiques quel que soit leur domaine d’intérêt. Le terme système d'information (ou SI) est notamment défini comme un ensemble organisé de ressources (personnel, données, procédures, matériel, logiciel, ...) permettant d'acquérir, de stocker, de structurer et de communiquer des informations (de formats divers) nécessaires au bon fonctionnement de l’organisme concerné. Connaître, comprendre, savoir utiliser voire créer de tels systèmes est désormais indispensable à tout ingénieur. Lors de ce module, vous apprendrez les bases de la programmation dans un des langages informatiques les plus utilisés à l'heure actuelle (Python) et comment un programme peut exploiter des données de différentes provenances notamment de bases de données (outils au cœur des systèmes d'information) que nous définirons à cette occasion. Ce module vous apportera des connaissances tantôt génériques tantôt plus spécifiques et indispensables à tout futur ingénieur et notamment vous apprendra à : structurer, modéliser, interroger ses données, décomposer un problème, définir l'algorithme résolvant un problème précis, implémenter un algorithme dans un langage donné, présenter des résultats. Ce module se compose de 27h d'enseignement réparties en 9 séances de 3h. Les 7 premières séances seront consacrées à du Cours/ TD tandis que les 2 dernières permettront la réalisation d'un projet en binôme sur lequel vous serez évalués. 1.2 Choix du langage de programmation : pourquoi Python Les avantages de Python sont nombreux, c’est un langage : facile à apprendre, à lire, à comprendre et à écrire ; avec une syntaxe sobre ; portable (fonctionne sous de nombreux systèmes d'exploitation) ; dynamique ; extensible ; gratuit ; qui permet (sans l'imposer) o une approche modulaire (découpage d'un programme en sous-unités) ; 5 o une approche orientée objet de la programmation (possibilité de définir des entités spécifiques aux besoins d'une application donnée). Développé depuis 1989 par Guido van Rossum et de nombreux contributeurs bénévoles, une des forces de Python est d'être étendu et maintenu par une communauté très dynamique donnant ainsi accès à de nombreuses bibliothèques adaptées à des besoins spécifiques : • Calcul numérique ; • Analyse de données ; • Traitement d’images ; • Bioinformatique ; • … Vous pourrez trouver un index des bibliothèques disponibles à l'adresse suivante : https://pypi.python.org/pypi Des éléments d'installation pour Windows sont par ailleurs accessibles ici : http://www.lfd.uci.edu/~gohlke/pythonlibs/ Le dynamisme de cette communauté se manifeste également par la tenue de plusieurs conférences annuelles et notamment la PyCon (internationale) : http://www.pycon.fr/2015/ Ses différentes qualités lui valent d'être parmi les langages les plus utilisés actuellement que ce soit dans l'Industrie ou le secteur public et la recherche. Des sociétés que vous connaissez bien telles que Google, Amazon, Facebook ou encore Dropbox réalisent au moins une partie de leurs développements informatiques en langage Python. Python présente enfin la particularité de pouvoir être utilisé de plusieurs manières différentes. En mode interactif, c'est-à-dire d'une manière telle que l'on peut dialoguer avec lui directement depuis le clavier, cela peut permettre de découvrir et tester un grand nombre de fonctionnalités du langage. Ou plus classiquement, il est possible de créer des scripts (fichiers contenant les instructions à réaliser), de les sauvegarder sur disque et de les faire exécuter. 1.3 Installer Python Il existe différentes versions de Python organisées en deux "lignées", nommées 2.x et 3.y. Plus x est grand (respectivement y) et plus la version en question de la lignée 2 (respectivement 3) est récente. Les deux lignées cohabitent à l'heure actuelle et certainement encore pour quelques années le temps que les programmes conçus en lignée 2 soient portés pour la lignée 3. Bien que la lignée 2 soit vouée à disparaître petit à petit, de nombreux programmes sont encore développés en Python 2.x et notamment dans l'industrie. En effet, le coût de portage d'un programme est souvent non négligeable. Pour ce module nous avons donc choisi d'utiliser la version 2.7 de Python (dernière version de lignée 2). Python est de manière générale facile à installer mais pour simplifier encore les choses, il existe différentes distributions qui proposent en plus du cœur de Python de nombreuses bibliothèques très utiles. 6 Nous utiliserons ici la distribution Anaconda (https://store.continuum.io/) qui est libre et va nous fournir les différentes librairies que nous allons utiliser ainsi qu'un environnement de développement convivial et performant nommé Spyder (https://github.com/spyder-ide). Etant libre, vous pouvez facilement installer cette distribution sur votre ordinateur personnel. Il existe des versions pour les différents systèmes d'exploitation. Vous trouverez toutes les informations nécessaires sur leur site web : https://store.continuum.io/ 2 Premiers programmes 2.1 Environnement de développement Il existe différents environnements de développement liés au langage Python. Pour ce module, nous avons choisi d'utiliser le logiciel Spyder (https://github.com/spyder-ide) qui est à la fois facile à prendre en main et présente des caractéristiques satisfaisantes. Figure 1 : Fenêtre principale de Spyder au lancement de l'application Il permet notamment dans un formalisme assez léger d'éditer et de faire exécuter nos programmes au sein d'une unique application. Configuration de Spyder Pour simplifier l'execution de vos scripts, allez dans Outils > Préférences > Executer Et comme indiqué dans la Figure 2 activez le bouton Exécuter dans une nouvelle console Python dédiée puis cliquez sur Apply et enfin sur OK. Désormais pour exécuter un script Python il vous suffira de placer votre curseur dans la zone d'édition (cf. Figure 1) puis d'actionner la touche F5. Cela ouvrira alors, comme nous verrons dans la section suivante, un nouvel onglet dédié au script considéré dans la zone d'exécution (en violet dans la Figure 1) 7 Figure 2 : Menu Outils > Préférences 2.2 Un exemple Un programme (script) est retranscrit dans un fichier texte qui pourra être enregistré et exécuté à la demande. Pour cela nous allons utiliser la zone d'éditeur de Spyder matérialisée en orange dans la Figure 1. À l'aide du menu Fichier, ouvrez le script arithmetique.py. Dans la zone d'édition de Spyder nous devez voir apparaître un nouvel onglet portant le nom du script concerné et les instructions qu'il contient comme dans la Figure 3. Figure 3 : Premier script Demandez ensuite son exécution en pressant la touche F5. Si la configuration précédente a bien été effectuée, pour notre programme arithmetique.py nous obtenons dans la zone d'exécution le résultat présenté en Figure 4. 8 Figure 4 : Résultat de l'execution du script arithmetique.py Exercice 1 Analysons un peu le résultat produit par notre script. À quelles instructions correspondent les valeurs présentes dans la zone de résultat ? Que pouvez-vous en déduire sur les différentes instructions présentes dans le script considéré et leur ordre d'exécution ? Que signifie 8. ? Que pouvez-vous en déduire sur les opérateurs mathématiques utilisés ? Quel est le rôle de l'instruction print ? Remarque : Un programme source doit être lisible pour un être humain et donc judicieusement "commenté" afin d'expliquer en langage naturel ses parties non triviales. Un commentaire en Python est précédé de : # Un DocString (documentation d'un élément de programme) se place entre """ """ . Nous reviendrons sur cette notion un peu plus tard. 9 Exemple : 3 Premiers pas en Python 3.1 Données, types et variables 3.1.1 Définitions et syntaxe Tout programme pour produire un résultat utilise des valeurs qui correspondent à des données de différentes natures ou types (nombres entiers ou réels, du texte, ...) sur lesquels il va réaliser des opérations créant ainsi de nouvelles valeurs jusqu'à l'obtention du résultat désiré. Lors de ce processus, les valeurs sont stockées en mémoire et rattachées à ce que l'on appelle des variables par un processus dit d'affectation. Sous Python, les noms de variables doivent obéir à quelques règles simples : - Un nom de variable est une séquence de lettres (a - z, A - Z) et de chiffres (0 - 9), qui doit toujours commencer par une lettre. - Seules les lettres ordinaires sont autorisées. Les lettres accentuées, les cédilles, les espaces, les caractères spéciaux tels que $, #, @, etc. sont interdits, à l'exception du caractère _ (souligné). - La casse est significative (les caractères majuscules et minuscules sont distingués). Les noms doivent être différents des mots réservés du langage (liste non exhaustive) : and def finally in print yield as del for is raise None assert elif from lambda return True break else global not try False class except if or while continue exec import pass with 3.1.2 Les types prédéfinis Pour distinguer les divers contenus possibles, le langage de programmation fait usage de différents types de variables prédéfinis : - le type 'entier' : int ; 10 - 3.1.3 le type 'réel' : float ; le type 'booléen' (vrai ou faux) : bool ; le type 'chaîne de caractères' (texte) : string ; o En Python, une donnée de type string est une suite quelconque de caractères délimitée soit par des apostrophes (simple quotes), soit par des guillemets (double quotes). o Une chaîne délimitée par des apostrophes peut contenir des guillemets et vice versa. o La séquence \' permet d'insérer une apostrophe dans une chaîne délimitée par des apostrophes. o La séquence \n dans une chaîne provoque un saut à la ligne. le type 'liste' (succession ordonnée de valeurs) : list ; etc. Conversion de types En Python, il est utile parfois de préciser, de façon explicite, le type de l’expression utilisée : int(expression) indique que expression doit être considérée comme un entier ; float(expression) indique que expression doit être considérée comme un réel ; str(expression) indique que expression doit être considérée comme une chaîne de caractères. Exemple : int(3.2) est égal à 3. Exercice 2 : Donnez la valeur et le type de chacune des conversions suivantes : Valeur Type float(10) float(3)/float(2) float(3)/int(2) str(10) int(float(3)/float(2)) float(int(3.)/int(2.)) int(10) int(3.)/float(2) 11 3.1.4 Variables et affectation (ou assignation) En Python, les variables sont déclarées à la volée sans précision de type. Le type d’une variable est déduit lors de son affectation. Comme dans de nombreux autres langages, l'opération d'affectation est représentée par le signe '=' : Exemple : a = 2 #La variable a reçoit la valeur 2 et est donc de type 'entier' Tout ce qui est noté après le symbole # n'est pas analysé par Python. C'est ce que l'on appelle un "commentaire" texte qui permet d'expliciter l'élément auquel il se rapporte. Exercice 3 : Votre premier script Rédigez le script ci-dessous dans un fichier hello.py : texte_a_afficher = "Hello World !" print texte_a_afficher # affichage du texte Testez ce script. La valeur d’une variable peut être modifiée. Sa valeur antérieure est alors perdue. a = a + 1 a = a - 1 # 3 (incrémentation) # 2 (décrémentation) Outre l'affectation simple, on peut aussi utiliser les formes suivantes : a = 4 a += 2 a -= 3 c = d = 8 e, f = 2.7, 5.1 e, f = f, e 3.1.5 # # # # # # # forme de base équivalent à : a = a + 2 si a est déjà référencé, provoquera une erreur sinon équivalent à : a = a-3 si a est déjà référencé cibles multiples (affectation de droite à gauche) affectation par position échange les valeurs de e et f Afficher la valeur d'une ou plusieurs variables Comme vu précédemment, pour afficher une valeur (ou plusieurs) valeurs à l'écran, nous disposons de l'instruction print. Cette instruction permet également d'afficher la valeur stockée dans une variable si son type le permet. Lorsque l'on souhaite afficher plusieurs éléments par une unique instruction, ces éléments peuvent être séparés par des virgules. Si une virgule est placée en fin d'instruction cela empêche le retour à la ligne. 12 Exercice 4 : On considère le script print_vars.py. Quelles sont les variables déclarées dans ce script et de quel type sont-elles ? Faites exécuter ce script et analysez le résultat produit. Exercice 5 : Créez un nouveau script que vous nommerez priority.py et dans lequel vous assignerez les valeurs respectives 3, 5, 7 à trois variables a, b, c. Votre script devra permettre ensuite d'afficher le résultat de l'opération a - b/c. Le résultat est-il mathématiquement correct ? Qu'en déduisez-vous sur le fonctionnement des opérateurs mathématiques ? 3.1.6 Opérateurs et expressions Les opérateurs Python ne sont pas seulement les quatre opérateurs mathématiques de base (*, +, et /). Il faut leur ajouter l'opérateur ** pour l'exponentiation (mise à la puissance x), ainsi qu'un certain nombre d'opérateurs logiques, des opérateurs agissant sur les chaînes de caractères, des opérateurs effectuant des tests d'identité ou d'appartenance, etc. Signalons au passage la disponibilité de l'opérateur modulo, représenté par le symbole %. Cet opérateur fournit le reste de la division entière d'un nombre par un autre. Exercice 6 : Testez et analysez le contenu du script cercle.py. Quelle est, à votre avis, l'utilité de la fonction type() ? 13 Opérateurs de comparaison Python dispose des opérateurs de comparaison suivants : x==y x est égal à y x != y x > y x est plus grand que y x < y x >= y x est plus grand que, ou égal à y x <= y x est différent de y x est plus petit que y x est plus petit que, ou égal à y La réduction d'une comparaison est de type booléen c'est-à-dire que cela vaut soit vrai (True) soit faux (False). Attention : Ne pas confondre : Affectation : = Comparaison : == L’affectation a un effet sur les variables concernées mais n’a pas de valeur après réduction. La comparaison n’a pas d’effet sur les variables comparées mais a une valeur après réduction qui est de type booléen. b=1 a=b # a reçoit b print a == b # a et b ne sont pas modifiées # affichera True Opérateurs logiques Il existe également des opérateurs logiques c'est-à-dire qui agissent sur des booléens. En Python, il s'agit de : x and y le "et" logique Vrai si x et y sont vrais en même temps et faux sinon x or y le "ou" logique Vrai si x ou y sont vrais indépendamment l'un de l'autre et faux sinon not x la négation Vrai si x est faux et vice versa Priorité des opérations Lorsqu'il y a plus d'un opérateur dans une expression, l'ordre dans lequel les opérations doivent être effectuées dépend de règles de priorité. Sous Python, les règles de priorité des opérateurs arithmétiques sont les mêmes que celles qui vous ont été enseignées en mathématiques. Les opérateurs arithmétiques sont prioritaires sur les opérateurs de comparaison qui sont euxmêmes prioritaires sur les opérateurs logiques. 14 Exercice 7 : Quelle est la valeur et le type des expressions suivantes ? Valeur Type 5 + 3/2 5. + 3/2 int(5.+3/2) float(5+3 % 2) 5 + 3 % 2 5 + 3./2 int(5+3./2) 5/2. + float(2**3 -1) 4 < 2 or 5 < 7 '123' == 123 int('123') == 123 int(int('123') == 123) 'au revoir' != False 3.2 Interactions avec l'utilisateur : les fonctions input() et raw_input() La plupart des scripts élaborés nécessitent à un moment ou l'autre une intervention de l'utilisateur (entrée d'un paramètre, clic de souris sur un bouton, etc.). Dans un script simple en mode texte (comme ceux que nous avons créés jusqu'à présent), la méthode la plus simple consiste à employer la fonction intégrée input(). Cette fonction provoque une interruption dans le programme courant. L'utilisateur est invité à entrer des caractères au clavier et à terminer avec <Enter>. Lorsque cette touche est enfoncée, l'exécution du programme se poursuit, et la fonction fournit en retour une valeur correspondant à ce que l'utilisateur a entré. Cette valeur peut alors être assignée à une variable choisie. Remarques importantes : La fonction input() renvoie une valeur dont le type correspondant à ce que l'utilisateur a entré. Pour cette raison, il sera souvent préférable d'utiliser dans vos scripts la fonction similaire raw_input(), laquelle renvoie toujours une chaîne de caractères. Vous pouvez ensuite convertir cette chaîne en nombre à l'aide de int() ou de float(). Exemple : 15 a = raw_input('Entrez print type(a) # b = float(a) # print type(b) # une donnée : ') affiche <type 'str'> conversion en valeur numérique affiche <type 'float'> 3.3 Sélection ou exécution conditionnelle Il arrive fréquemment en programmation que l'on souhaite réaliser certains traitements dans certains cas et éventuellement d'autres dans d'autres circonstances. Pour cela, nous disposons en Python de l'instruction if. Considérons le script : a = 150 if (a > 100): print "a dépasse la centaine" print "nous sommes sortis de la condition" La première instruction affecte la valeur 150 à la variable a. Jusqu'ici rien de nouveau. À la deuxième ligne, constatez en revanche la présence d'un nouveau mot-clé if et le retrait vers la droite des écritures de la troisième ligne. Cela signifie que vous êtes entré dans ce que l'on appelle un bloc d’instructions. L'instruction commençant par le mot clé if est appelé entête de bloc. Tant que vous voulez que les instructions suivantes soient effectuées sous la condition « if (a > 100): » (c'est-à-dire si la variable a contient une valeur strictement supérieure à 100) il faudra les faire précéder d’une tabulation (ou de 4 espaces). Dans le cas contraire, pour indiquer que vous sortez de ce bloc d’instruction, il faut vous repositionner au même niveau que l'entête du bloc que vous souhaitez quitter de manière analogue à l'instruction en quatrième ligne de notre script précédent. Exercice 8 : Ouvrez et exécutez le script condition.py. Le résultat obtenu est-il conforme à votre attente ? Modifiez ce script pour attribuer à a la valeur 20. Exécutez à nouveau votre script. Que constatez-vous ? Modifiez à nouveau votre script comme suit : a = 20 if (a > 100): print "a dépasse la centaine" else: print "a ne dépasse pas cent" Qu'en déduisez-vous sur le sens du mot clé else ? 16 Remarque : En Python, vous devez utiliser les sauts à la ligne et l'indentation, mais en contrepartie vous n'avez pas à vous préoccuper d'autres symboles délimiteurs de blocs. En définitive, Python vous "force" à écrire du code lisible (où les blocs d'instructions et leur organisation apparaissent clairement), et à prendre de bonnes habitudes que vous conserverez lorsque vous utiliserez d'autres langages. Le mot clé elif Lorsque l'on veut prendre en compte davantage d'alternatives on utilise la syntaxe suivante : if -- [elif] -- [else] Exemple : Cas particulier : Test d'une valeur booléenne : if x : # mieux que (if x is True:) # ou que (if x == True:) Exercice 9 : Écrire un programme divise.py qui demande à l'utilisateur deux nombres a et b et affiche "oui" si a divise b ou b divise a et "non" sinon. Pensez à utiliser l'opérateur modulo. 3.4 Instructions et blocs Comme nous le verrons par la suite, d'autres éléments de langage permettent de créer des blocs d'instructions. Quels que soient ces éléments que nous détaillerons par la suite (boucles, fonctions, ...) la syntaxe à adopter reste la même. Instruction composée = En-tête , double point , bloc d'instructions indenté Ainsi, l’indentation détermine les débuts et fin de blocs, l'entête de bloc qui se termine par « : » détermine dans quels cas de figure le bloc sera exécuté ou non. On peut imbriquer des blocs pour réaliser des structures de décision complexes. Le schéma ci-contre en résume le principe. 17 Les blocs d'instructions sont toujours associés à une ligne d'en-tête contenant une instruction bien spécifique (if, elif, else, while, def, ...) se terminant par un double point. Les blocs sont délimités par l'indentation : toutes les lignes d'un même bloc doivent être indentées exactement de la même manière (c'est-à-dire décalées vers la droite d'un même nombre d'espaces). Le nombre d'espaces à utiliser pour l'indentation est quelconque, mais la plupart des programmeurs utilisent des multiples de 4. Notez que le code du bloc le plus externe (bloc 1) ne peut pas lui-même être écarté de la marge de gauche (Il n'est imbriqué dans rien). Les espaces et les commentaires sont normalement ignorés à part ceux qui servent à l'indentation, en début de ligne, les espaces placés à l'intérieur des instructions et des expressions sont presque toujours ignorés (sauf s'ils font partie d'une chaîne de caractères). Il en va de même pour les commentaires : ceux-ci commencent toujours par un caractère dièse (#) et s'étendent jusqu'à la fin de la ligne courante. Exercice 10 : Écrire un script compare_2.py qui compare deux nombres. Votre programme affichera 1 si le premier est strictement plus grand que le deuxième, -1 dans le cas contraire et 0 s'ils sont égaux. Exercice 11 : Écrire un script classe_3.py qui classe trois nombres dans l'ordre croissant. Les données alphanumériques 3.5 Les fonctions Motivations Pour effectuer une même série de tâches à des endroits différents du programme, il faudrait a priori copier ce code plusieurs fois. Pour illustrer ces propos, voici un petit programme destiné à chercher le maximum parmi trois triplets de nombres entrés successivement par l'utilisateur, toutes séries de nombres confondues : 18 La recherche du max d'un triplet est réutilisée plusieurs fois : 3 fois pour les triplets utilisateur, et 1 fois pour le résultat global. Définition Un moyen de synthétiser le code précédent est de créer une fonction c'est-à-dire un ensemble d'instructions regroupées sous un nom et permettant d'effectuer à la demande (appel) la série d'actions correspondante, à l'aide éventuellement de paramètres d'entrée (valeurs représentées par des variables et nécessaires à l'exécution de sa tâche), et "retourne à celui qui l’a appelée" le résultat à l'aide de l'instruction return. Les fonctions sont les éléments structurants de base de tout langage procédural. La définition d'une fonction se fait à l'aide de l'instruction def, suivie du nom de la fonction, et de la liste des paramètres entre parenthèses. def nom_fonction(parametres): """Documentation de la fonction.""" #<bloc_instructions> (docstring) help(nom_fonction) # affiche le docstring de la fonction 19 Le bloc d'instructions est obligatoire. S'il ne fait rien, on emploie l'instruction pass. La documentation est fortement conseillée. Les DocString évoqués précédemment se rattachent à l'élément qui les précède et, dans le cas d'une fonction, peuvent être réaffichés à l'aide de la fonction help comme dans l'exemple ci-dessus. Les paramètres En python, on dit que les paramètres sont "passés" par affectation : • Chaque argument de la définition de la fonction correspond, dans l'ordre, à un paramètre de l'appel. • La correspondance se fait en place par affectation. Exemple : def ma_fonction(x, y, z) : pass # Appel de la fonction : ## le passage d'arguments se fait dans l'ordre, par affectation : ma_fonction(7, 'k', 2.718) # x = 7, y = 'k', z = 2.718 Dans le cas cité en exemple, notre fonction va calculer le max de trois nombres, aura comme entrées les trois nombres, que l'on peut nommer comme on veut, et aura pour sortie un nombre, le maximum du triplet fourni. Nous obtenons donc : def cherche_max_triplet(a,b,c): """ Fonction qui étant donnés trois nombres a, b, c retourne le maximum a : float b : float c : float -> float """ maxi = a if b > maxi: maxi = b if c > maxi : maxi = c else : if c > maxi : maxi = c return maxi Encore une fois, il faudra bien respecter la syntaxe avec ":" et l'indentation. Du coup, notre programme devient : def cherche_max_triplet(a,b,c): """ Fonction qui étant donnés trois nombres a, b, c retourne le maximum a : float b : float c : float -> float """ maxi = a if b > maxi: 20 maxi = b if c > maxi : maxi = c else : if c > maxi : maxi = c return maxi ########################################################################### #Programme principal ########################################################################### a, b, c = input('Entrez trois nombres séparés par une virgule :\n') max1 = cherche_max_triplet(a,b,c) print max1 a, b, c = input('Entrez à nouveau trois nombres séparés par une virgule :\n') max2 = cherche_max_triplet(a,b,c) print max2 a, b, c = input('Entrez enfin trois nombres séparés par une virgule :\n') max3 = cherche_max_triplet(a,b,c) print max3 maximum = cherche_max_triplet(max1,max2,max3) print maximum La fonction est définie avant son appel. Portée des variables Les variables définies dans la fonction cherche_max_triplet ci-dessus sont locales, c'est-à-dire qu'elles n'existent pas en dehors de cette fonction et ne risquent pas de modifier les autres variables contenues dans le programme. Voici un autre exemple pour illustrer cela : r = 5 def test(): r = 18 return r print "Dans la fonction, r = ", test() print "A l'extérieur, r = ", r Voici le résultat du programme : Dans la fonction, r = A l'extérieur, r = 5 18 À l'inverse, si l'on définit la variable r comme globale dans la fonction ( à l'aide du mot clé global ) : r = 5 def test(): global r r = 18 return r print "Dans la fonction, r = ", test() print "A l'extérieur, r = ", r 21 Alors on obtient : Dans la fonction, r = 18 A l'extérieur, r = 18 Valeurs par défaut Il est possible également de préciser des valeurs par défaut aux paramètres d'une fonction. Cela permet notamment d'éviter à l'utilisateur de préciser systématiquement les valeurs de paramètres qui varient rarement. Exemple : def initPort(speed=9600, parity="paire", data=8, stops=1): print "Initialisation a", speed, "bits/s" print "parite :", parity print data, "bits de donnees" print stops, "bits d'arrêt" # Appels possibles : initPort() # prise en compte de toutes les valeurs par défaut initPort(parity="nulle") # les paramètres nommés sont modifiés initPort(2400, "paire", 7, 2) # tous les paramètres sont fournis # il n'est donc pas nécessaire de les nommer Exercice 12 : On donne le fichier de script erreur.py. Vous pouvez le récupérer sur le site du cours. Chacune des fonctions qui s'y trouvent contient une unique erreur. Modifiez chacune des fonctions, l'une après l'autre afin que le script fonctionne de manière satisfaisante c'est-à-dire qu'il affiche : Somme1 : 25 Somme2 : 29 Somme3 : 31 Veillez en faisant cela à respecter les DocString des fonctions qui précisent les entrées (paramètres nommés) et sorties attendues (précédées du symbole ->). 3.6 Importer un module de fonctions Définition Un module est un fichier de script indépendant contenant notamment la définition d'un ensemble de fonctions (bibliothèque de fonctionnalités) que l'on veut pouvoir réutiliser dans diverses circonstances, divers programmes. Il permet ainsi d'éviter les redondances entre différents programmes. L'usage de modules permet également de rattacher une documentation spécifique à un ensemble de fonctionnalités et les tests 22 nécessaires à la validation de son bon fonctionnement. Ceci permettant donc à la fois un gain de temps et de sécurité. Il existe un grand nombre de modules pré-programmés qui sont fournis d'office avec Python. Le module math, par exemple, contient les définitions de nombreuses fonctions mathématiques telles que sinus, cosinus, tangente, racine carrée, etc. Pour pouvoir utiliser ces fonctions, il vous suffit d'incorporer la ligne suivante au début de votre script : from math import * Cette ligne indique à Python qu'il lui faut inclure dans le programme courant toutes (symbole *) les fonctions du module math. Si l'on n'utilise que quelques fonctions d'un module, il est préférable de lister celles-ci après le mot clé import comme dans l'exemple ci-dessous avant d'alléger le traitement à réaliser. En effet, un import peut être vu comme la réécriture au sein du script considéré des éléments importés. Exemple : from math import pi, sqrt, sin nombre = 121 angle = pi/6 # soit 30° print 'racine carrée de', nombre, '=', sqrt(nombre) print 'sinus de', angle, 'radians', '=', sin(angle) L'exécution de ce script provoque l'affichage suivant : racine carrée de 121 = 11.0 sinus de 0.523598775598 radians = 0.5 Pour utiliser un module dans un autre programme il est également possible d'utiliser l'instruction suivante : import <nom du module> [as nom_choisi] Dans ce cas la totalité des fonctionnalités du module seront importées et pourront être utilisées. Lors de l'appel de l'une d'entre elles il sera en revanche nécessaire de faire précéder le nom de la fonction par le nom du module, les deux étant séparés par un '.' qui marque l'"appartenance" de la fonction au module en question. Exemple : import math print 'La racine carrée de 100 est ', math.sqrt(100) L'exécution de ce script provoque l'affichage suivant : La racine carrée de 100 est 10.0 Ce mode d'import peut être coûteux notamment si le module est conséquent et que l'on n'utilise qu'une petite partie de ses fonctionnalités. Dans un tel cas, comme indiqué ci-dessus, il faudra lui préférer la syntaxe : from <nom du module> import obj1, obj2... qui n'importe que les fonctions obj1, obj2... du module concerné. 23 Il en est de même avec vos propres modules à la nuance près que si vous souhaitez importer un de vos modules se trouvant dans un répertoire différent de celui où est stocké le script dans lequel vous voulez l'insérer il faudra préciser son chemin. Nous aurons l'occasion de tester cela par la suite. Quelques modules utiles math : comme décrit plus haut, ce module permet d'utiliser les fonctions et constantes classiques en math : les fonctions trigonométriques ( cos(), sin(), tan(), etc...), fonction racine ( sqrt() ), les nombres pi et e, les logarithmes et exponentielles ( log(), log10(), exp() ), partie entière et partie fractionnaire ( modf() ), valeur absolue( abs() et fabs() ), etc.... random : offre tout un panel de fonctions pour générer des nombres de manière aléatoire. Par exemple, la fonction randint(a,b) qui rend un nombre entier compris entre a et b : randint(1,10) #fournit une valeur entre 1 et 10 par exemple 5 ou random() qui renvoie un nombre réel compris entre 0 et 1 : random() #vaut par exemple 0.39041964805607265 time et datetime : modules permettant la gestion du temps et des dates, avec des conversions, calculs, etc... re : pour le traitement des expressions régulières (un motif dans une chaîne de caractères) ( par exemple pour vérifier dans un formulaire si une adresse email a été rentrée correctement ). Le site pypi.python.org/pypi recense plus de 4500 packages ! Exercice 13 : 1. Écrire un programme nommé age.py qui, en utilisant le module random, permet de tirer aléatoirement un nombre entre 1 et 100, de le stocker dans une variable nommée age et de l'afficher à l'écran. 2. Compléter votre programme en créant une fonction nommée tester_age qui étant donné une valeur d'âge affichera "Vous êtes un enfant" si l'âge est inférieur à 10. 3. Compléter votre programme afin d'utiliser votre fonction tester_age pour un âge aléatoire. 4. Compléter enfin votre fonction pour qu'elle affiche : "Vous êtes un enfant" si l'âge est inférieur à 10, "Vous êtes un adolescent" si l'âge est compris entre 10 et 17, "Vous êtes un adulte" si l'âge est compris entre 18 et 49, "Vous êtes un vétéran" si l'âge est supérieur ou égal à 50. 24 3.7 Structure générale d'un script Python Pour terminer cette partie et compte tenu des différentes notions présentées, voici un petit graphique présentant la structure générale d'un script Python. Toutes les instructions qui s'y trouvent n'ont pas encore été présentées, mais ce qui nous intéresse ici c'est uniquement la structure du fichier que l'on retrouvera systématiquement. En résumé, un script fera apparaître dans cet ordre : l'encodage utilisé la liste des modules importés la liste des fonctions définies un ensemble d'instructions qui constitue le "programme principal" c'est-à-dire en quelque sorte le chef d'orchestre, le pilote qui va nous guider à la solution recherchée. Cet ensemble d'instructions est la traduction dans le langage choisi de l'algorithme que l'on a conçu pour répondre à un problème donné. 25 Entête d'un module Afin d'assurer une bonne réutilisabilité d'un module, sa documentation est importante et est de manière conventionnelle analogue à ce qui suit : #!/bin/env python # -*- coding: utf-8 -*"""Jeu de carte.""" # Assure le fonctionnement du script sur tous les systèmes. # définit l'encodage # nécessaire dès que l'on utilise des chaînes de caractères # le docstring du script # fichier : mon_fichier.py # auteur(s) : Dupond et Durand # nom du script # noms des auteurs 26 Séance 2 4 Boucles et séquences 4.1 Répétitions en boucle - l'instruction while L'une des choses que les machines font le mieux est la répétition sans erreur de tâches identiques. Il existe bien des méthodes pour programmer ces tâches répétitives. Nous allons commencer par l'une des plus fondamentales : la boucle construite à partir de l'instruction while. Exercice 14 : Dans un script nommé premiere_boucle.py veuillez saisir les instructions ci-dessous : a = 0 while (a < 7) : a = a + 1 print a # (n'oubliez pas le double point !) # (n'oubliez pas l'indentation !) Exécutez votre script. Que se passe-t-il ? La syntaxe est donc : while (condition): instruction 1 instruction 2 ... instruction n # retour à la ligne pour la suite du programme Tant que la condition est vérifiée (de valeur égale à True), les instructions de 1 à n seront effectuées. Il faut comme toujours en Python bien faire attention à respecter la même indentation pour chaque instruction dans la boucle. Le retour à la ligne signifie la fin des instructions à répéter dans la boucle while. Ainsi l'écriture suivante : while (condition): instruction 1 instruction 2 ... instruction n-1 instruction n signifie que seules les instructions 1 à n-1 seront effectuées si la condition de la boucle est vrai. Lorsque la condition sera fausse alors le programme se poursuivra en reprenant à l'instruction n. Avant d'écrire ce genre de boucles, assurez-vous qu'elle n'est pas infinie ! Autrement dit, assurezvous que la condition d'entrée dans la boucle sera forcément fausse à un moment donné. Ce dont 27 elle dépend devra donc évoluer au sein de la boucle afin que si on y est entré on puisse en ressortir tôt ou tard. Par exemple, le code suivant lancera un programme qui ne s'arrêtera jamais : n = 5 while (n > 0): print n Le problème est que la valeur de n ne change pas, et donc la condition n > 0 est toujours vérifiée, la boucle ne se finit jamais. Pour corriger, il faut qu'à un moment donné n devienne négatif ou nul, voici un exemple de solution : n = 5 while (n > 0): print n n = n - 1 La valeur de n baisse de un à chaque fois que la boucle s'exécute, et atteindra à un moment ou à un autre 0. Exercice 15 : Combien de fois va-t-on avoir un résultat affiché dans le code ci-dessus ? Exercice 16 : Construction d'une suite mathématique Analysez le programme ci-dessous, décrivez le mieux possible le rôle de chacune des instructions et donnez son résultat. a = 1 b = 1 c = 1 while c < 11 : print b, temp = b b = a + b a = temp c = c+1 Vérifiez votre réponse en transcrivant ces instructions dans un script que vous nommerez fibo.py. 28 Exercice 17 : Écrire un programme qui affiche les 20 premiers termes de la table de multiplication par 7. Vous nommerez ce programme multi_7.py. Exercice 18 : Devine un nombre ! Écrire un programme devine.py utilisant le module random pour implémenter le petit jeu devine un nombre. L'ordinateur « choisit » un nombre et propose à l'utilisateur de le deviner. L'ordinateur guide alors l'utilisateur en lui indiquant si le nombre qu'il propose est plus grand ou plus petit que le nombre attendu. Exercice 19 : Écrire un programme simule_de.py utilisant le module random qui simule le lancé d'un dé à 6 faces, autant de fois que l'utilisateur le désire (celui-ci peut très bien choisir de lancer un million de fois le dé). L'utilisateur choisit le nombre de lancés ainsi qu'une valeur d'intérêt valide, et doit voir apparaitre le nombre de lancés positifs c'est-à-dire égaux à la valeur d'intérêt choisie par l'utilisateur. 4.2 Les séquences Définition Une séquence est un conteneur ordonné d‘éléments indicés par des entiers. Python dispose de trois types prédéfinis de séquences : les chaînes ; les listes ; les tuples. Fonctions et méthodes On peut agir sur les séquences en utilisant des fonctions (notation procédurale) qui vont être communes aux différents types de séquences et des méthodes (notation objet) qui vont être spécifiques à un type de séquence en particulier. Pour appliquer une fonction on utilise directement l'opérateur (). Par exemple, si l'on veut avoir accès à la longueur d'une séquence, il s'agit d'une fonction nommée len que l'on utilise classiquement comme suit : sequence = "abc" longueur = len(sequence) print longueur #affichera 3 dans ce cas On applique en revanche une méthode à une séquence (de manière plus générale à un objet) en utilisant la notation pointée. Par exemple, la méthode upper s'applique uniquement aux chaînes de caractères (nous y reviendrons pas la suite) et réalise une mise en majuscule. 29 Elle s'utilise comme suit : sequence = "abracadabra" seq2 = sequence.upper() print seq2 #affichera 'ABRACADABRA' Parcourir une séquence : Outre la boucle while vue précédemment, il existe en Python un autre mécanisme de boucle spécialement conçu pour parcourir des séquences lorsque l'on n'a pas besoin de savoir où on en est dans notre parcours mais uniquement d'accéder aux différentes valeurs de la séquence les unes après les autres. L'instruction à utiliser dans ce cas est for. Exemples : for lettre in "ciao": print lettre, for x in [2, 'a', 3.14]: print x, for i in range(5): print i, #la virgule permet d'obtenir les différents éléments #sur une même ligne # affiche c i a o #affiche 2 a 3.14 #affiche 0 1 2 3 4 Syntaxe générique : for element_courant in ma_sequence : <bloc d’instructions> element_courant est une variable que l'on choisit pour stocker à chaque "tour" de boucle l'élément que l'on considère dans la séquence ma_séquence. Ainsi, à la ième itération de la boucle, est stocké dans la variable element_courant le i ème élément de la séquence parcourue. Après ces quelques généralités, nous allons voir plus en détails les deux principaux types de séquences en Python : les chaînes de caractères et les listes. 4.3 Utilisation des chaînes de caractères Une chaîne de caractères est une séquence de lettres et s'écrit en plaçant cette chaîne soit entre deux guillemets (ex. "exemple") soit entre deux simples quotes (ex. 'exemple') soit, plus rarement, entre triples guillemets lorsque l’on veut préserver la mise en page (sauts de lignes). Les guillemets permettent d'inclure des apostrophes : c1 = "L'eau vive" Les apostrophes permettent d'inclure des guillemets : 30 c2 = ' est "froide" !' Les triples guillemets ou triples apostrophes conservent la mise en page (lignes multiples) : c3 = """Usage : -h : help -q : quit""" Exercice 20 : Ouvrez le script premieres_chaines.py. Analysez son contenu et le résultat produit à son exécution. Qu'en déduisez-vous sur les effets des opérateurs utilisés ? Les chaînes de caractères : opérations en résumé Dans vos tests précédents vous avez dû constater que les opérateurs + et * ont un sens particulier pour les chaînes de caractères. Concaténation : s1 = "abc" s2 = "defg" s3 = s1 + s2 #s3 vaut donc 'abcdefg' Répétition : s4 = "Fi! " s5 = s4 * 3 #s5 vaut donc 'Fi! Fi! Fi!' Accès aux éléments d’une chaîne de caractères L’opérateur [ ] permet d’extraire un (ou plusieurs) caractère(s) d’une chaîne. Les caractères d’une chaîne de caractères sont numérotés de manière croissante de gauche à droite à partir de 0 et de manière décroissante de droite à gauche à partir de -1. 0 1 2 3 4 5 6 b o n j o u r -7 -6 -5 -4 -3 -2 -1 Soit chaine une variable de type chaîne de caractères. – chaine[i] renvoie le caractère en position (ou indice) i dans chaine ; – chaine[i:j] renvoie la sous-chaîne commençant à l’indice i et se terminant à l’indice j-1 ; – chaine[i:] renvoie le suffixe débutant en position (ou indice) i dans chaine ; – chaine[:j] renvoie le préfixe se terminant en position (ou indice) j-1 dans chaine. 31 Quelques exemples particuliers pour chaine="bonjour" : – chaine[-1] vaut r ; – chaine[0:3] (ou chaine[ :3]) vaut bon ; – chaine[4:] vaut our ; – chaine[0:-2] (ou chaine[ :-2]) vaut bonjo ; – chaine[3:6] vaut jou ; – chaine[-5:-2] vaut njo. Attention ! Les chaînes de caractères ne sont pas modifiables en Python, c'est-à-dire que l'on ne peut pas affecter directement uniquement un caractère de la chaîne. Autrement dit, la syntaxe chaine[3] = 2 n’est pas correcte. Fonctions utiles La fonction len(chaine) renvoie la longueur de chaine. Par exemple, len("bonjour") renvoie 7. Il existe également des méthodes permettant d’effectuer facilement des opérations sur les chaînes de caractères, parmi celles-ci (liste non exhaustive) : • find() : donne la position d'une sous-chaîne dans la chaîne (la première position vaut 0) : 'abracadabra'.find('bra') • # 1 count() : donne le nombre de sous-chaînes dans la chaîne : 'abracadabra'.count('bra') • # 2 lower() : convertit en minuscules : 'PETIT'.lower() • #'petit' upper() : convertit en majuscules : 'grand'.upper() • #'GRAND' capitalize() : convertit la première lettre en majuscule : 'michelle'.capitalize() • #'Michelle' title() : la première lettre de tous les mots en majuscule : 'un beau titre !'.title() • #'Un Beau Titre !' swapcase() : intervertit les casses : 'bOB'.swapcase() • " strip() : supprime les espaces en début et fin de chaîne : Trop d'espaces • #'Bob' ".strip() #"Trop d'espaces" replace() : remplace une sous-chaîne par une autre : 'abracadabra'.replace('a', 'o') #'obrocodobro' 32 Exercice 21 : Quels sont les valeurs et types des expressions Python suivantes : Valeur Type len("polololom") "bonhomme"[0 :3] + ’assoir’[2 :] 123.replace(1,6) "o" in ’coin’.replace(’o’, ’a’) 679[2] + 3 len("123") + 123 5 * "exemple".find(’em’) "123".replace("1", "6") x='marche'[:3]+'bandit'[3:5]+"_"+str(5**2)+' '+"octobre" Parcours d’une chaîne de caractères Une chaîne de caractères en Python étant une séquence, nous avons donc à disposition en plus de la boucle while la boucle for présentée précédemment. Exemples : for lettre in "bonjour": print lettre, # b o n j o u r Exercice 22 : Dans un script chaines.py créez, les unes après les autres, les fonctions détaillées ci-dessous et le programme principal qui permet de les tester. Pour chaque fonction vous veillerez à la documenter soigneusement et notamment à bien préciser ses entrées et ses sorties comme suit : def ma_fonction (param1, param2, ...) : """ Rôle de la fonction ma_fonction param1 : type param2 : type ... sortie : type """ ... return sortie 33 1. Écrire une fonction detail_chaine récupérant un texte ou une phrase de l'utilisateur, et qui affiche sur plusieurs lignes : le texte ou la phrase donnés, le nombre de caractères contenus, le premier et le dernier caractères, séparés par trois petits points ... 2. Écrire une fonction affiche_colonne qui affiche les lettres d'une chaîne de caractères donnée par l'utilisateur ( une par ligne ). 3. Écrire une fonction insere_star qui pour une chaîne de caractères donnée en paramètre retourne une nouvelle chaîne construite sur la base de la première en insérant une astérisque entre chacune de ses lettres (ex: gaston --> g*a*s*t*o*n ). 4. Écrire une fonction teste_lettre qui teste si une phrase contient une lettre, les deux éléments étant fournis comme paramètres et retourne le nombre de fois où cette lettre apparait dans le phrase. 5. Écrire une fonction palindrome qui prend en entrée une chaîne de caractères et retourne True si c'est un palindrome (c'est à dire une chaîne de caractères s'écrivant de la même façon de gauche à droite et de droite à gauche comme : non, radar...) et False dans le cas contraire. 6. Écrire une fonction ligne_star qui prend en entrée un entier n et retourne une chaîne de caractères composée de n fois le symbole *. Par exemple pour n = 5, la fonction devra retourner la chaîne : ***** 7. Écrire une fonction carre_star qui prend en entrée un entier n et retourne une chaîne de caractères semblable à un carré d'étoiles de côté n. Pensez pour cela à utiliser la fonction ligne_star. Par exemple pour n = 3, la fonction devra retourner la chaîne : *** *** *** 8. Écrire enfin une fonction triangle_star qui prend en entrée un entier n et retourne une chaîne de caractères semblable à un triangle rectangle d'étoiles. Par exemple pour n = 6, la fonction devra retourner : ****** ***** **** *** ** * 34 Séance 3 4.4 Les listes Une liste en Python est une collection hétérogène, ordonnée et modifiable d’éléments séparés par des virgules, et entourée de crochets. Ce sont des objets qui peuvent en contenir d'autres. C'est donc une séquence, comme une chaîne de caractères, mais qui, au lieu de contenir des caractères, contient n'importe quel objet. Au sein d’une même liste on peut trouver des objets de types différents (entiers, flottants, chaînes de caractères, listes, ...). Les listes en Python s'initialisent très simplement grâce à l’opérateur [ ]. Quelques exemples de listes : ma_liste = [] print ma_liste # on crée une liste vide # affichera [] ma_liste = [1, 2, 3, 4, 5] print ma_liste # une liste avec cinq objets # [1, 2, 3, 4, 5] ma_liste = [1, 3.5, "une chaine", []] #4 objets de types différents Accès aux éléments d’une liste Les modes d'« accès » présentés pour les chaînes de caractères sont également valables pour les listes. couleurs = ['trefle', 'carreau', 'coeur', 'pique'] print couleurs # affichera ['trefle', 'carreau', 'coeur', 'pique'] print couleurs[1] # affichera carreau list1 list2 list3 print = ['a', 'b'] = [4, 2.718] = [list1, list2] list3 machin = [0.0] * 3 # liste de listes # affichera [['a', 'b'], [4, 2.718]] # [0.0, 0.0, 0.0] Il faut y rajouter quelques éléments compte tenu du fait que les listes sont modifiables à l’inverse des chaînes de caractères. Modification : couleurs[1] = 14 print couleurs # affichera ['trefle', 14, 'coeur', 'pique'] mots = ['jambon', 'sel', 'confiture'] mots [1] = 'piment' print mots # affichera ['jambon', 'piment', 'confiture'] Insertions et suppressions : Dans le membre de gauche d'une affectation, il faut obligatoirement indiquer une tranche pour exécuter une insertion ou une suppression. 35 Le membre de droite doit lui-même être une liste. mots = ['jambon', 'sel', 'confiture'] mots[2:2] = ['miel'] # insertion en 3e position mots[4:4] = ['beurre'] # insertion en 5e position print mots # affichera ['jambon','sel','miel','confiture', 'beurre'] mots[2:4] = [] print mots # effacement par affectation d'une liste vide # affichera ['jambon', 'sel', 'beurre'] Fonctions utiles sort() : permet d’ordonner une liste nombres = [17, 38, 10, 25, 72] nombres.sort() # [10, 17, 25, 38, 72] append() : permet d’ajouter en queue de liste nombres.append(12) # [10, 17, 25, 38, 72, 12] reverse() : permet de renverser une liste nombres.reverse() # [12, 72, 38, 25, 17, 10] index(val) : donne la position de l’élément val nombres.index(17) # 4 remove(val) : permet de supprimer l’élément val de la liste nombres.remove(38) # [12, 72, 25, 17, 10] range() : permet de créer des listes d’entiers range(4) # [0, 1, 2, 3] range(4, 8) # [4, 5, 6, 7] range(2, 9, 2) # [2, 4, 6, 8] chose = range(6) print 3 in chose # [0, 1, 2, 3, 4, 5] # teste l'appartenance donc affichera True 4.5 Conversions liste – chaîne de caractères Dans certains cas de figure, il peut être intéressant de passer d'une chaîne de caractères à une liste et vice versa. Pour cela nous disposons des deux méthodes suivantes : • split() : scinde une chaîne en une liste de mots suivant un caractère de séparation : 'ab*cd'.split('*') • # (on indique le séparateur) # ['ab', 'cd'] join() : concatène plusieurs chaînes en une seule chaîne suivant un caractère de jonction : '-'.join(['ci', 'joint']) # (on indique le caractère de "liaison") #'ci-joint' Celles-ci sont propres aux chaînes de caractères. 36 Exercice 23 : Dans un script listes.py créez, les unes après les autres, les fonctions détaillées ci-dessous et le programme principal qui permet de les tester. Pour chaque fonction vous veillerez à la documenter soigneusement et notamment à bien préciser ses entrées et ses sorties comme précédemment pour les fonctions se rapportant aux chaînes de caractères. 1. Écrire une fonction renverse_sans_bords qui renverse le contenu d'une liste sauf le premier et le dernier élément et testez-la. Par exemple, la liste [1, 2, 3, 4, 5] deviendra [1, 4, 3, 2, 5]. 2. Écrire une fonction permute_demi qui permute la première moitié d'une liste avec l'autre moitié et testez-la. Par exemple, [1, 2, 3, 4, 5] devient [ 4, 5, 3, 1, 2]. 3. Écrire une fonction pairs_impairs qui construit une liste des 100 premiers entiers, puis l'éclate en deux listes, l'une avec les nombres pairs et l'autre avec les nombres impairs et retourne la liste de ces listes. 4. Écrire une fonction indice(e,l) qui renvoie l’indice de la première occurrence de l’élément e dans la liste l (si e est présent dans l) et qui renvoie la longueur de l sinon. 5. Écrire une fonction sousListe(pL, gL) qui renvoie un booléen qui dit si les éléments de pL apparaissent dans gL, et ce dans le même ordre. Par exemple, sousListe([2,4,10], [1,2,4,5,6,8,10,12]) renvoie True. 6. Tri 1. Écrire une fonction insere qui prend en argument une liste l supposée triée par ordre croissant, et un nouvel élément e, et qui renvoie la liste obtenue en insérant l’élément e à la bonne place pour que le résultat soit encore une liste triée. 2. Écrire une fonction tri qui prend une liste en argument et renvoie la liste triée. On utilisera le tri par insertion : au début, on crée une nouvelle liste vide, et ensuite, pour chaque élément e de la liste d’origine, on insère e dans la nouvelle liste comme indiqué dans la question précédente. 7. Histogramme Écrire une fonction histo_de utilisant le module random qui simule le lancé d'un dé à 6 faces, autant de fois que l'utilisateur le désire (celui-ci peut très bien choisir de lancer un million de fois le dé) et retourne le nombre de résultats pour chacun des 6 chiffres. 8. Compression de données Une séquence d’ADN peut être très longue et contenir des répétitions. Le but de cet exercice est d’écrire une fonction qui prend un long brin d’ADN et qui renvoie la même information sous une forme condensée. L’idée est de ne pas répéter un nucléotide répété mais de mémoriser dans la liste son nombre d’occurrences. On considèrera que le brin d’ADN est passé sous forme d'une chaîne de caractères. Ainsi comprime('AAAGCTTCCCG' retourne [’A’,3,’G’,’C’,’T’,2,’C’,3,’G’] 1. Ecrivez la fonction comprime 2. Ecrivez la fonction deploie qui fait l’opération inverse. 37 Exercice 24 : Le jeu du pendu Règles du jeu : L'ordinateur choisit un mot au hasard dans une liste, un mot de huit lettres maximum. Le joueur tente de trouver les lettres composant le mot. À chaque coup, il entre une lettre. Si la lettre est dans le mot, l'ordinateur affiche le mot avec les lettres déjà trouvées. Celles qui ne le sont pas encore sont remplacées par des étoiles (*). Le joueur a 8 chances. Au-delà, il a perdu. Implémentez ce jeu en Python. 38 4.6 Retour sur l'affectation L'opération d'affectation en Python réalise plusieurs actions. Exemples : i = 1 msg = "Quoi de neuf ?" e = 2.718 Quel que soit le type de l'élément concerné, lorsqu'une affectation est réalisée comme dans les exemples ci-dessus, les opérations suivantes sont réalisées : créer et mémoriser un nom de variable (membre de gauche) dans l'espace de noms courant (c'est en fait une adresse) ; lui attribuer dynamiquement un type bien déterminé ; créer et mémoriser une valeur (membre de droite) ; établir un lien entre le nom de la variable et l'adresse de la valeur correspondante. Le problème des références Considérons l'exemple ci-dessous. Exemple : fable = ['Je', 'plie', 'mais', 'ne', 'romps', 'point'] phrase = fable fable[4] = 'casse' print phrase #affichera ['Je', 'plie', 'mais', 'ne', 'casse', 'point'] Nous créons ici une liste et nous établissons un lien entre cette liste et une variable nommée fable. Puis nous affectons une nouvelle variable phrase à la valeur référencée par fable. Autrement dit nous faisons le lien entre phrase et notre liste de départ. Nous avons donc deux variables qui nous permettent d'y accéder. En utilisant notre premier moyen d'accès, nous modifions ensuite le 5ième valeur de notre liste, toujours la même et unique liste. Enfin lorsque nous regardons, par notre deuxième accès possible, le contenu de notre liste nous observons la modification réalisée. Le schéma ci-dessous résume ce mécanisme. 39 Le mécanisme d'affection n'est donc pas un mécanisme de copie de valeur mais de référencement d'adresse un peu comme si vous aviez fait un double de la clef de votre porte d'entrée. En définitive, vous disposeriez de deux clefs mais toujours d'une unique porte. En conséquence, si l'on désire réaliser une vraie copie d'un objet, on doit utiliser le module copy : import copy as cpy a = [1, 2, 3] b = a # une référence b.append(4) print a # affichera [1, 2, 3, 4] c = cpy.copy(a) c.append(5) print c # une copie de "surface" print a # affichera [1, 2, 3, 4] # affichera [1, 2, 3, 4, 5] Pour une copie en profondeur, c'est-à-dire lorsqu'un objet modifiable contient des objets modifiables il faudra avoir recours à la fonction copy.deepcopy(). 40 Séance 4 5 Programmation de graphiques - séquences Bien que de nombreux logiciels de visualisation de données soient disponibles, souvent les besoins des utilisateurs vis-à-vis de la visualisation de leurs données sont spécifiques et savoir programmer des graphiques personnalisés s’avère indispensable. Pour cela le langage Python est complété par une librairie très fournie : la librairie matplotlib. Pour ce TD nous utiliserons également la bibliothèque random. 5.1 Présentation des bibliothèques Rappels : Une bibliothèque est un fichier python (script) qui contient des instructions définissant un certain nombre de fonctions. On l’importe à l’aide des commandes : import (nom de la bibliothèque) ou : import (nom de la bibliothèque) as (abréviation) Exemple : import random import random as rd Dans le premier cas on appellera la fonction gauss avec la syntaxe random.gauss, dans le second avec rd.gauss. 5.1.1 La bibliothèque random La bibliothèque random donne des outils pour générer des nombres pseudo-aléatoires. • • random.random() : retourne un flottant pseudo-aléatoire de l’intervalle [0.0, 1.0[. random.gauss(mu,sigma) : retourne un flottant pseudo-aléatoire selon la loi gaussienne de moyenne mu et d'écart-type sigma. • random.randrange(start, stop[, step]) : retourne un élément au hasard de la liste range(start,stop, step). • random.randint(a,b) : retourne un entier de [a,b]. Une liste exhaustive des fonctions se trouve sur http://docs.python.org/3/library/random.html avec des exemples. 41 Exercice 25 : Écrire dans un script genere.py une fonction liste_alea qui étant donnés trois entiers nb, mini et maxi retourne une liste de nb nombres flottants aléatoires compris entre mini et maxi au sens large. 5.1.2 La bibliothèque matplotlib La bibliothèque matplotlib donne des outils pour faire des graphiques en 2D et en 3D. On peut faire aussi bien des graphes de fonctions, des histogrammes que des opérations assez poussées (de nombreux exemples sur http://matplotlib.org/gallery.html). C’est une bibliothèque robuste et efficace utilisée par de nombreuses autres bibliothèques. La bibliothèque est documentée à l'adresse suivante : http://matplotlib.org/api/pyplot_api.html Matplotlib permet de réaliser des nuages de points notamment en deux dimensions et de les paramétrer suivant différents critères. Il est notamment possible d'utiliser des symboles divers et des couleurs afin par exemple de distinguer des groupes au sein des éléments représentés, de dimensionner spécifiquement chaque point pour prendre en compte un troisième descripteur, de réaliser des légendes pour rendre le graphique explicite, etc. Figure 5 : Exemple de nuage de points obtenu avec Matplotlib 42 Cette librairie permet également de réaliser des courbes avec des styles de traits différents, des barres d’erreurs, du remplissage, plusieurs tracés, des légendes, etc. Figure 6 : Exemple de courbe avec des barres d'erreurs et une légende Figure 7 : Exemple de courbe avec du remplissage Certaines fonctions fournies permettent la création de diagrammes en bâtons et d'histogrammes verticaux ou horizontaux, éventuellement pour plusieurs groupes, avec ou sans barres d’erreurs, avec des légendes, etc. Figure 8 : Exemples de diagrammes en bâtons et d'histogrammes Souvent, lorsque l'on souhaite produire ses propres graphiques, on va vouloir combiner différents types : Des courbes avec des nuages Des nuages avec des histogrammes etc. 43 Figure 9 : Exemples de graphiques combinés Il nous faudra alors gérer l’ordre d’affichage afin d'assurer un rendu satisfaisant. Figure 10 : Courbe et points mal superposés Figure 11 : Courbes et points superposés de manière satisfaisante Il peut être intéressant dans certains cas de proposer plusieurs graphiques sur une même figure par exemple pour pouvoir visualiser simultanément plusieurs projections en deux dimensions d'un nuage de points en dimension plus grande. 44 Les Nuages de points Pour réaliser des nuages de points avec Matplotlib il faut avoir recours à la fonction scatter qui nécessite au minimum deux arguments : Une liste des valeurs en X (axe horizontal) Une liste des valeurs en Y (axe vertical) Chaque couple (x,y) représentant la position d'un point à matérialiser sur le graphique, les listes X et Y doivent être de même taille. Exemple : L'instruction scatter ([0,1,2,3,4,5],[0,1,2,3,4,5]) à incorporer dans un script faisant référence à la bibliothèque Matplotlib correspond au graphique ci-dessous : Un exemple de script … minimaliste : # Il nous faut importer la librairie from pylab import * # Récupérer des données sous forme de listes x = rand(100) # génère une liste aléatoire de nombres de [0,1] y = rand(100) # de longueur 100 # Faire appel à une fonction de dessin scatter(x,y) # fonction de dessin d’un nuage de points #enregistrer la figure produite savefig('premiere_figure.jpg') #Demander l’affichage show() # ouvre une fenêtre contenant la figure # bloque l’exécution 45 Figure 12 : Graphique obtenu par exécution du script ci-dessus Coloration Par défaut la fonction scatter représente les points par des disques bleus. Le paramètre nommé c permet de paramétrer la couleur. Lorsque l'on donne une unique valeur pour ce paramètre tous les points seront de même couleur. Par exemple : scatter(x,y, c='r') permet d'afficher en rouge les points dont les coordonnées sont contenues dans x et y. Si une liste de valeurs (de même longueur que x et y) est associée au paramètre c alors une couleur est attribuée dans l’ordre de la liste à chaque point dessiné Exemple : from pylab import * N = 100 x = rand(N) y = rand(N) color = ['r']*(N/2) + ['g']*(N/2) scatter(x,y, c=color) show() Figure 13 : Graphique produit grâce au script ci-contre Il existe quelques couleurs par défaut représentées par un caractère comme dans l'exemple cidessus. 46 Tableau 1 : Couleurs par défaut ‘b’ : blue ‘g’ : green ‘r’ : red ‘c’ : cyan ‘m’ : magenta ‘y’ : yellow ‘k’ : black ‘w’ : white Un niveau de gris peut également être précisé par un flottant de [0,1] entre apostrophes. Par exemple : c = '0.75'. Une couleur peut également être codée par un triplet de valeurs de [0,1] entre parenthèses représentant des quantités de rouge, vert et bleu (représentation R,G,B). Par exemple c = (0.0,1.0,0.0) Il existe d'autres formats possibles pour préciser une couleur (voir la page web de la bibliothèque au besoin). Les symboles Le paramètre nommé marker permet de paramétrer le symbole utilisé pour matérialiser les points. scatter(x,y, marker = '+') Figure 14 : Nuage de points représentés par le symbole '+' Un seul symbole peut être considéré par appel de la fonction scatter. Si dans un graphique nous souhaitons faire apparaître des points avec des symboles distincts il faudra donc autant d'appels à la fonction scatter que de symboles différents désirés. scatter(x[:(N/2)],y[:(N/2)], marker = '+') scatter(x[(N/2):],y[(N/2):], marker = '^') Figure 15 : Nuages de points avec deux symboles distincts 47 Comme pour les couleurs, certains symboles sont prédéfinis : '+' plus '1' tri_down '_' Hline '2' tri_up '|' vline '3' tri_left 'x' x '4' tri_right 'v' triangle_down 'D' diamond '<' triangle_left 'h' hexagon '>' triangle_right 'p' pentagon '^' triangle_up 's' square '8' octagon '*' star 'o' circle 'd' thin_diamond Mais il est également possible de définir ses propres polygones (cf. site web de Matplotlib). Taille des symboles Le paramètre nommé s permet de paramétrer la taille des symboles. Comme pour la couleur, si une seule valeur est fournie les points seront tous de même taille. scatter(x,y, s = 200) Si en revanche, une liste (de bonne longueur) de valeurs est fournie alors elle détermine position par position la taille de chacun des points à représenter. N = 100 x = rand(N) y = rand(N) sizes = [20]*(N/2) + [200]*(N/2) scatter(x,y, s=sizes) 48 Exemple de script combinant les différents effets listés ci-dessus : #!/usr/bin/env python from pylab import * N = 100 x = rand(N) y = rand(N) color = ['b']*(N/2) + ['r']*(N/2) sizes = [40]*(N/4) + [400]*(N/4)+[40]*(N/4) + [400]*(N/4) scatter(x[:(N/2)],y[:(N/2)], c = color[:(N/2)], marker = '+', s = sizes[:(N/2)] ) scatter(x[(N/2):],y[(N/2):], c = color[(N/2):], marker = '_', s = sizes[(N/2):] ) savefig('premiere_figure.jpg') show() Figure 16 : Figure obtenue par exécution du script ci-dessus Exercice 26 : Dans un script premiers_nuages.py et en utilisant la fonction développée à l'Exercice 25, écrire une fonction nuage_alea permettant d'afficher un nuage de points aléatoires. Votre fonction aura comme paramètres le nombre de points à considérer, l'abscisse minimale, l'abscisse maximale, l'ordonnée minimale et l'ordonnée maximale autorisées. Exercice 27 : Dans le script premiers_nuages.py, écrire une fonction affiche_seuil qui construit un nuage aléatoire en considérant les bornes x_min, x_max, y_min, y_max en abscisses et en ordonnées et qui étant donné un seuil s_xy affiche tous les points d'abscisses ou d'ordonnée inférieure à s_xy en bleu et les autres points en rouge. Exercice 28 : On considère 3 points aléatoires particuliers auxquels on associe des couleurs distinctes. On considère par ailleurs nb points aléatoires (on considère toujours des valeurs extrêmes x_min, x_max, y_min, y_max). Dans le script premiers_nuages.py, écrire une fonction affiche_groupes permettant de générer ces ensembles de points et de produire un graphique où les 3 points d'intérêts sont matérialisés par des marqueurs spécifiques et les autres points portent la couleur du point d'intérêt qui lui est le plus proche au sens de la distance euclidienne. 49 Les figures multiples Sur une même figure on peut faire apparaître plusieurs graphiques grâce à la fonction subplot . Exemple d'usage : from numpy import sqrt from numpy.random import rand from pylab import * x = rand(10) y = rand(10) z = sqrt(x**2 + y**2) subplot(321) scatter(x,y,s=80, c=z, marker=">") subplot(322) scatter(x,y,s=80, c=z, marker='1') subplot(323) scatter(x,y,s=80, c=z, marker='s') #… show() Le script ci-dessus contient les instructions nécessaires à la réalisation des trois premiers graphiques de la figure ci-dessus. Il est contenu dans le fichier subplot.py. Exercice 29 : Compléter ce script afin d'obtenir la figure dans sa totalité. À quoi correspondent les valeurs du paramètre de la fonction subplot ? 50 Les courbes Lorsque l'on souhaite tracer des courbes avec Matplotlib, nous avons recours à la fonction plot qui nécessite au minimum un argument correspondant à la liste des valeurs en Y. Sera alors associée comme valeur pour l'axe X à chaque valeur de la liste son indice dans la liste. #!/usr/bin/env python from pylab import * N = 100 y = rand(N) plot(y) savefig('premiere_courbe.jpg') show() Lorsque l'on a des valeurs spécifiques à préciser par l'axe X, nous donnerons comme pour la fonction scatter deux listes de même longueur à la fonction plot : • • Une liste des valeurs en X Une liste des valeurs en Y associées #!/usr/bin/env python from pylab import * from numpy import arange from math import sin x = arange(0,10,0.01) y = [] for val in x : y.append(sin(val)) plot(x,y) savefig('deuxieme_courbe.jpg') show() Couleur du trait La couleur du trait d'une courbe est déterminée par le paramètre c dont l'usage est analogue aux nuages de points en fournissant une valeur ou une liste de valeurs. Style du trait Le style du trait (plein, pointillé, etc.) est géré par le paramètre linestyle qui admet les valeurs suivantes : '-' Ligne continue '--' Ligne tiré 51 '-.' Ligne mixte ':' Ligne pointillée Epaisseur du trait L'épaisseur du trait est commandée par le paramètre nommé linewidth. plot(x,y1,linewidth=2, linestyle = '--', c='k') plot(x,y2,linewidth=2, linestyle = '-.', c='r') plot(x,y3,linewidth=2, linestyle = ':', c='b') Marquer les points Il est également possible de matérialiser par un symbole la position des points fournis pour générer la courbe grâce au paramètre marker (analogue au nuage de points). La taille du symbole pourra alors être gérée par le paramètre makersize, tandis que sa couleur sera déterminée par la valeur du paramètre markerfacecolor. #!/usr/bin/env python from pylab import * from numpy import arange from math import sin x = arange(0,10,0.2) y = [] for val in x : y.append(sin(val)) plot(x, y, c = 'k', marker = 'o', markersize = 15, markerfacecolor = 'g') show() Figure 17 : Courbe dont les points fournis sont matérialisés par des disques vert 52 Superposition de courbes Lorsque l'on veut tracer plusieurs courbes sur un même graphique il faudra réaliser plusieurs appels à la fonction plot. L'ordre d’affichage des courbes étant commandé par le paramètre zorder. Les courbes sont empilées dans l’ordre des zorder croissants comme si il s'agissait d'une valeur sur un axe pointant vers nous. from pylab import * from numpy import arange from math import sin x = arange(0,10,0.01) y1 = [] for val in x : y1.append(sin(val)) y2 = [] for val in x : y2.append(sin(val+1.0)) # on top plot(x,y1,linewidth=10, color='black', zorder = 10) # bottom plot(x,y2,linewidth=10, color='red', zorder = 1) show() Courbes et erreurs Lorsque l'on a des données expérimentales à représenter, il est fréquent qu'une mesure soit entachée d'une erreur potentielle qu'il faut pouvoir retranscrire sur le graphique correspondant afin que celui-ci soit représentatif. Cela est faisable par appel à la fonction errorbar (x, y, …) qui pourra prendre en compte des erreurs suivant l'axe horizontal par le paramètre xerr et/ou sur l'axe vertical par le paramètre yerr. Exemple : #!/usr/bin/env python from pylab import * from numpy import arange from math import sin x = arange(0,10,0.5) y = [sin(val) for val in x] plot(x,y) errorbar(x, y, linestyle = '', xerr=rand(len(x)), ecolor='r') errorbar(x, y, linestyle = '', yerr=rand(len(x)), ecolor='g') savefig('courbe_erreurs.jpg') show() Nous avons deux appels à la fonction errorbar dans l'exemple ci-dessus afin de représenter dans des couleurs différentes les erreurs en x et en y. D'autres fonctionnalités (nombreuses) en lien avec le tracé de courbe sont disponibles. Reportezvous à la documentation et aux exemples accessibles sur leur site web pour plus de détails. 53 Exercice 30 : Une marche aléatoire (en dimension 2) est une suite de points, où chacun est obtenu par un saut indépendant à partir du point précédent (et rien de plus : le système « perd la mémoire » à chaque saut). Dans l’exemple étudié, on autorise les sauts de longueur 1 sur l’axe des x (donc +1 ou −1 sur x) ou (exclusif) sur l’axe des y (donc +1 ou −1 sur y), de façon équiprobable. Dans un script marche_alea.py, implémenter les fonctions suivantes : 1. Écrire une fonction positions_alea qui retourne deux listes x_liste et y_liste contenant les coordonnées (x[i],y[i]) des points d’une marche aléatoire de N pas. 2. Écrire une fonction plot_marche qui prend en entrée deux listes de valeurs et affiche la courbe correspondante. 3. Écrire une fonction compte_position qui, étant données x_liste et y_liste, détermine pour chaque position visitée le nombre de fois où elle l'a été. Vous construirez pour cela une liste qui dans chaque case contient une liste de trois éléments : position en x, position en y et nombre d'occurrence. Votre fonction retournera cette liste de listes. 4. Écrire une fonction affiche_marche_scatter qui permet d'afficher chaque position visitée lors de la marche avec un code couleur correspondant au nombre de fois où chacune d'entre elles a été occupée. Vous utiliserez pour cela la fonction de la question précédente. Les diagrammes en bâtons Dès que l'on souhaite représenter des quantités par catégories (nombre d'hommes, nombre de femmes dans une entreprise par exemple) les diagrammes en bâtons offrent une bonne solution. Dans la bibliothèque Matplotlib la fonction bar va nous permettre de réaliser de tels graphiques. Voici un petit exemple d'usage : #!/usr/bin/env python from pylab import * N = 5 menMeans = (20, 35, 30, 35, 27) menStd = (2, 3, 4, 1, 2) ind = range(N) # the x locations for the groups width = 0.35 # the width of the bars bar(ind, menMeans, width, color ='r', yerr=menStd) ind_ticks = [] for val in ind : ind_ticks.append(val+(width/2)) xticks(ind_ticks, ('G1', 'G2', 'G3', 'G4', 'G5') ) show() 54 Dans le script ci-dessus nous précisons que nous allons représenter un diagramme à 5 bâtons (fonction bar) pour des valeurs en x contenues dans ind, des valeurs associées en y contenues dans menMeans, des erreurs contenues dans menStd. La fonction xticks permet de mettre en place la légende pour les bâtons. Figure 18 : Diagramme obtenu par exécution du script ci-dessus Pour des diagrammes horizontaux, il faudra utiliser la fonction barh dont l'usage est analogue en considérant une liste des positions en y des barres, une liste des longueurs des barres en x, une couleur, etc. Exercice 31 : Dans un script batons.py, écrire une fonction histo_de qui prend en paramètre un entier correspondant au nombre de lancés que l'on souhaite simuler et qui affiche le diagramme en bâton correspondant au nombre de fois où chaque valeur possible est apparue. Le première barre du graphique représentera donc le nombre de fois où un 1 a été observé lors de la simulation du lancé du dé. Autre exemple : Les histogrammes Les histogrammes constituent un autre grand type de graphiques souvent utiles lorsque l'on veut représenter des distributions. En Matplotlib, la fonction disponible s'appelle hist . Un exemple simple : Dans l'exemple ci-dessous, nous construisons une liste x qui contient des valeurs aléatoires générées suivant une loi gaussienne (fonction gauss) de moyenne mu et d'écart type sigma. Nous représentons ensuite l'histogramme de ces valeurs par la fonction hist en précisant que l'on souhaite 20 barres (paramètres bins), un écart entre les barres de 0.8 (paramètre rwidth) et que l'on souhaite un histogramme normé (paramètre normed). La bibliothèque offre la possibilité des représenter plusieurs histogrammes sur un même graphique, de les juxtaposer ou de les superposer. Pour plus de détails, consulter les différents exemples disponibles depuis leur site web pour découvrir les paramètres à utiliser. 55 #!/usr/bin/env python from pylab import hist, savefig, show from random import gauss mu, sigma = 200, 25 x = [] for i in range (10000) : x.append(gauss(mu,sigma)) hist(x, bins=20, normed=1, rwidth=0.8) savefig('histo.jpg') show() Les légendes Pour rendre un graphique explicite une légende est souvent nécessaire. Voici quelques fonctions utiles : • • • • • Titre de figure : title Labels sur les axes : xlabel, ylabel Encart de légende de couleurs : legend Valeurs spécifiques sur les axes : xticks , yticks … Pour plus de détails sur ces fonctions reportez vous à la documentation en ligne ou utilisez la fonction help dans Spyder. En résumé ... Rappels des fonctions principales présentées dans les pages précédentes. • matplotlib.pyplot.plot(x,y) : Prend deux listes x et y et trace la courbe (affine par morceaux) passant par les couples (x[i], y[i]). • matplotlib.pyplot.scatter(x,y,c='b',marker='o',s='20') : place une série de points (sans les relier) en (x[i], y[i]). La variable c règle la ou les couleurs utilisées ; marker le symbole utilisé pour matérialiser les points ; s la taille du symbole. • matplotlib.pyplot.hist(L,nombre) : L est une liste qui contient les données. C’est le seul argument obligatoire. Le nombre contient le nombre de barres de l’histogramme. On peut rajouter l’argument normed='True' pour avoir des proportions plutôt que des effectifs. • matplotlib.pyplot.show() : Affiche toutes les figures construites jusqu’ici. L’affichage des figures bloque a priori l’exécution du programme. Autrement dit, si d'autres instructions apparaissent dans votre programme après l'instruction show(), celles-ci ne seront exécutées qu'après fermeture des fenêtres correspondantes. • matplotlib.pyplot.savefig(nom de fichier) : Sauve la figure courante. Le nom du fichier est donné sous forme de chaine de caractères, et l’extension indique le format de sauvegarde. o Exemple : matplotlib.pyplot.savefig('Ma_jolie_figure.png'). 56 Séance 5 6 Manipulation des Fichiers Jusqu'ici les données (valeurs) exploitées par nos programmes étaient soit écrites directement dans le code source, fournies par l'utilisateur au moyen des fonctions input ou raw_input ou encore générées aléatoirement. Bien que cela permette de développer certaines fonctionnalités et de réaliser quelques tests, dans la pratique les données qu'un programme peut avoir à traiter ou utiliser sont stockées sur disque. Une façon de faire cela est de les enregistrer dans un fichier (éventuellement formaté d'une manière particulière) au même titre que nous enregistrons nos programmes. Dans cette partie, nous allons voir comment depuis un programme Python nous pouvons accéder au contenu d'un fichier ou en créer. 6.1 Ouvrir un fichier Le type fichier est un type prédéfini de Python comme les entiers, les flottants, les chaînes de caractères, les listes, …. En Python, une seule fonction permet plusieurs modes d’ouverture d’un fichier suivant la valeur de ses arguments et les actions que l’on veut effectuer sur celui-ci : lecture écriture ajout Exemples pour les principaux modes d'ouverture d’un fichier : f1 = open("monFichier_1", "r") f2 = open("monFichier_2", "w") f3 = open("monFichier_3", "a") # ouverture en lecture # ouverture en écriture # ouverture en ajout 6.2 Fermer un fichier En Python, il y a une seule méthode de fermeture : la fonction close f1.close() #fermeture du fichier f1 Remarque : Tant que le fichier n'est pas fermé, son contenu n'est pas garanti sur le disque. Si l’on veut forcer l’écriture sur le disque avant la fermeture du fichier, il faut utiliser la fonction flush f1.flush() # force l’écriture sur le disque 6.3 Méthodes de lecture En Python, il existe différentes méthodes pour lire un fichier : 57 f s s s s = = = = = open("truc.txt", "r") f.read() # lit f.read(n) # lit f.readline() # lit f.readlines() # lit tout le fichier --> string au plus n octets --> string la ligne suivante --> string tout le fichier --> liste de strings Exemple de parcours d’un fichier : f = open("truc.txt", "r") s = f.readlines() # lit tout le fichier --> liste de strings f.close() for ligne in s : print ligne # bon procédé de parcours d'un fichier Une autre méthode de lecture complète d’un fichier ligne à ligne : f = open("truc.txt", "r") for ligne in f : print ligne f.close() Remarque : La deuxième version est plus légère en lignes de codes que la première mais présente l’inconvénient de devoir laisser le fichier ouvert tout le temps de traitement de son contenu. L'ouverture d'un fichier n'en bloque pas l'accès autrement dit, un autre programme peut y accéder et le modifier alors que le vôtre est en train de lire son contenu ce qui peut être source d'incohérences. 6.4 Méthodes d’écriture : Pour écrire dans un fichier, le moyen le plus lisible est d’utiliser la méthode write. Voici un exemple d’utilisation : f = open("truc.txt", "w") s = 'toto' f.write(s) # écrit la chaîne s dans f l = ['a', 'b', 'c'] f.writelines(l) # écrit les chaînes de la liste l dans f f.close() 6.5 Méthodes seek et tell Les méthodes seek et tell permettent respectivement de se déplacer au nième caractère d'un fichier et d'afficher où en est la lecture du fichier, c'est-à-dire quel caractère est en train d'être lu. filin = open('zoo.txt', 'r') filin.readline() # 'girafe\n' filin.tell() #7 filin.seek(0) filin.tell() filin.readline() filin.close() #0 #'girafe\n' 58 On remarque qu'à l'ouverture d'un fichier, le tout premier caractère est considéré comme le caractère 0 (tout comme le premier élément d'une liste). La méthode seek() permet facilement de remonter au début du fichier lorsque l'on est arrivé à la fin ou lorsqu'on en a lu une partie. 6.6 Mise en pratique Les différentes fonctions que vous allez développer dans cette section sont à enregistrer dans un script fichiers.py qui contiendra le programme principal nécessaire pour les tester. Exercice 32 : Étant données les instructions suivantes : file = open('fichier.txt','r') lignes = file.readlines() for i in lignes: print i file.close() Expliquez le résultat associé : Ceci est la première ligne, Ceci est la deuxième ligne ! Ceci est la troisième ligne Ceci est la quatrième et dernière ligne. Modifiez les instructions pour supprimer les sauts de ligne inutiles. Exercice 33 : Écrire une fonction alterne qui à partir de deux fichiers A.txt et B.txt, construit un fichier C.txt qui contient alternativement une ligne de A, une ligne de B, une ligne de A, et ainsi de suite jusqu'à atteindre la fin de l'un des deux fichiers originaux. Complétez ensuite C.txt avec les éléments restant de l'autre. Exercice 34 : Écrire une fonction fichier_multi qui génère automatiquement un fichier texte (multi_a_b.txt) contenant les tables de multiplications de a à b (chacune d'entre elles incluant seulement les dix premiers termes), a et b étant fournis en paramètres. Vous mettrez en forme votre fichier de sortie afin que les tables apparaissent nettement. Le nom de votre fichier de sortie devra dépendre des 59 valeurs attribuées à a et à b au moment de l'appel de la fonction. Par exemple, votre fonction devra permettre de générer un fichier multi_2_10.txt contenant les tables de 2 à 10. Remarque : la chaine '\n' correspond à un saut de ligne, la chaîne '\t' à une tabulation. Exercice 35 : Statistiques sur un fichier textuel Écrire une fonction words calculant la liste des mots d’un fichier. Écrire une fonction count_words comptant les occurrences de tous les mots dans le texte (une table associant à chaque mot le nombre de ses occurrences). Écrire une fonction sum_up qui affiche le nombre de mots différents apparaissant dans le texte, puis la liste de ces mots ainsi que leur nombre d’occurrences. Exercice 36 : Dans cet exercice, nous allons utiliser une sortie partielle de DSSP (Define Secondary Structure of Proteins), qui est un logiciel d'extraction des structures secondaires de protéines. Ce fichier contient 5 colonnes correspondant respectivement au numéro de résidu, à l'acide aminé, sa structure secondaire et ses angles phi/psi. Nous allons travailler sur le fichier dssp.txt (jetez-y un petit coup d'œil en passant...). Écrivez une fonction extract_cols qui dans un fichier extract_i.txt pour chacune des lignes les éléments contenu dans la colonne i, i étant fourni en paramètre. Écrivez une fonction recense_aa qui dans un fichier aa.txt écrit une ligne par acide aminé présent dans le fichier de départ et ses structures secondaires recensées. Inversement écrivez une fonction recense_struct qui dans un fichier structs.txt écrit une ligne par structure secondaire et la liste des acides aminés correspondants. 60 7 Programmation de graphique - fichiers Pour faire suite à la séance 4, nous allons nous intéresser désormais à la réalisation de graphiques à partir de données contenues dans des fichiers. Exercice 37 : Représentation des données du fichier iris.tab Dans un script iris.py, implémenter les fonctions demandées ci-dessous : 1. Écrire une fonction extract_descripteurs qui, pour un fichier donné d'extension particulière (.tab), retourne une liste des descripteurs (noms des colonnes) présents dans le fichier. 2. Écrire une fonction extract_values qui, étant donnés un fichier et un descripteur, retourne la liste des valeurs mesurées pour celui-ci s'il s'agit bien d'un descripteur considéré dans le fichier fourni. 3. Écrire une fonction scatter_proj qui, étant donnés deux descripteurs et un nom de fichier, crée le graphique (nuage de points) en 2 dimensions correspondant si cela est possible. Vous testerez vos différentes fonctions sur le fichier iris.tab. Exercice 38 : Représentation des données pour la méta-analyse Une méta-analyse est une démarche statistique combinant les résultats d'une série d'études indépendantes sur un problème donné. Dans ce cadre, une représentation graphique particulièrement appréciée consiste à relier les points qui proviennent d’une même expérience afin de faire ressortir la variabilité entre celles-ci. À partir du fichier data_meta.txt, écrivez un programme meta.py permettant une représentation, telle que décrite ci-dessus, des données contenues dans ce fichier. Le fichier data_meta.txt se compose de trois colonnes séparées par des tabulations. La première représente le numéro d’expérience, la deuxième la valeur à représenter en x et la troisième la valeur à représenter en y. Pour chaque publication, tracer les différentes valeurs connues et reliez-les. Vous obtenez un graphique semblable à celui-ci-dessous. 61 Dans un deuxième temps, reliez les points par valeurs croissantes sur le premier axe. Exercice 39 : Représentations « radiales » Un mode de représentation encore peu disponible dans les outils mais de plus en plus utilisés lorsque l’on veut faire ressortir des variabilités entre individus (en général en petit nombre) sur les différents axes qui les caractérisent (aussi en nombre restreint) consiste à représenter les axes par des rayons équidistants partant de l’origine et les individus par un polygone dans cet espace dont les sommets correspondent à la valeur prise par l’individu sur chacun des axes. On obtient une figure semblable à celle ci-dessous : Où deux individus (un en vert et l’autre en rouge) ont été représentés vis-à-vis des cinq descripteurs arg0, arg1, arg2, arg3 et arg4. Les données correspondantes au graphique sont les suivantes : arg0 1 2 arg1 5 20 arg2 4 5 arg3 16 7 arg4 2 9 Grâce à la bibliothèque Matplotlib, écrire un programme radial.py permettant de générer une telle représentation pour un nombre de descripteurs et d’individus quelconque. 62 Séance 6 8 Les bases de données et le langage SQL1 Comme nous l'avons vu précédemment, les fichiers nous permettent de stocker des données que l'on peut utiliser par la suite dans nos programmes et, à l'inverse, nos programmes peuvent construire des fichiers pour stocker les résultats des traitements effectués. Néanmoins, suivant la quantité de données et les relations existantes entre elles, les fichiers sont assez rapidement insuffisants, et avoir recours à d'autres solutions peut être très judicieux. Considérons l'extrait suivant d'un fichier de données de commandes de clients : NCLI Nom Adresse Localité NCOM Date NPRO Qté Libellé K111 VANBIST Lille 30178 25 FERARD Poitiers 30179 CS262 60 C400 FERARD Poitiers 30179 PA60 20 S127 VANDERKA Namur 30182 PA60 30 C400 FERARD Poitiers 30184 CS464 120 C400 FERARD Poitiers 30184 PA45 20 F011 PONCELET Toulouse 30185 CS464 260 F011 PONCELET Toulouse 30185 PA60 15 F011 PONCELET Toulouse 30185 PS222 600 C400 FERARD Poitiers 30186 PA45 3 B512 GILLET Toulouse 30188 CS464 180 B512 GILLET 14, r. de l'Ete Toulouse 30188 PA45 22 B512 GILLET 14, r. de l'Ete Toulouse 30188 PA60 70 B512 GILLET 14, r. de l'Ete Toulouse 30188 200812-21 200812-22 200812-22 200812-23 200812-23 200812-23 200901-02 200901-02 200901-02 200901-02 200901-03 200901-03 200901-03 200901-03 CS464 C400 180, r. Florimont 65, r. du Tertre 65, r. du Tertre 3, av. des Roses 65, r. du Tertre 65, r. du Tertre 17, Clos des Erables 17, Clos des Erables 17, Clos des Erables 65, r. du Tertre 14, r. de l'Ete PH222 92 CHEV. SAPIN 400x6x4 CHEV. SAPIN 200x6x2 POINTE ACIER 60 (1K) POINTE ACIER 60 (1K) CHEV. SAPIN 400x6x4 POINTE ACIER 45 (1K) CHEV. SAPIN 400x6x4 POINTE ACIER 60 (1K) PL. SAPIN 200x20x2 POINTE ACIER 45 (1K) CHEV. SAPIN 400x6x4 POINTE ACIER 45 (1K) POINTE ACIER 60 (1K) PL. HETRE 200x20x2 Prix Uni. 220.00 75.00 95.00 95.00 220.00 105.00 220.00 95.00 185.00 105.00 220.00 105.00 95.00 230.00 En observant le contenu de ce fichier, nous pouvons constater rapidement un certain nombre de redondances. Par exemple, si l'on considère le client C400, nous pouvons constater que son nom et son adresse apparaissent à plusieurs reprises. En effet, il a passé plusieurs commandes avec parfois 1 Cette partie du support de cours s’appuie sur l’ouvrage "Bases de données. Concepts, utilisation et développement" de Jean-Luc Hainaut, paru en 2009 aux éditions Dunod. 63 plusieurs produits. De manière analogue, un même produit commandé à plusieurs reprises aura son libellé noté pour chaque achat. Tout ceci est nécessaire lorsque l'on veut stocker ces données dans un fichier. Néanmoins cela est rapidement coûteux. Ici, il ne s'agit que d'un bref exemple considérant uniquement quelques clients et quelques produits. Imaginez le volume de données analogues qu'Amazon ou la Fnac possèdent. La particularité des données présentées dans cet exemple par rapport à ce que l'on a pu voir dans les séances précédentes est que nos données sont ici "liées" : un client est à "rattacher" à un certain nombre de commandes qui elles-mêmes concernent un certain nombre de produits. Certaines de ces informations sont propres à une entité particulière par exemple l'adresse pour une personne ou le libellé d'un produit en revanche d'autres marquent les liens qui les unissent par exemple le numéro de commande permet de retrouver tous les produits achetés lors d'un même achat. Lorsque l'on est confronté à ce cas de figure il devient préférable de stocker ces données dans ce que l'on appelle une base de données. Par ailleurs, en dehors du problème de redondance, il n'est pas aisé avec un tel fichier de déterminer rapidement et automatiquement par exemple tous les produits commandés par un client donné. Les bases de données sont des outils de plus en plus fréquemment utilisés. Elles permettent de stocker des données nombreuses dans un seul ensemble bien structuré, d’éviter le plus souvent les doublons, d’assurer la cohérence des informations qu’elles contiennent et fournissent des moyens d'interrogation performants répondant ainsi à toutes nos réserves vis-à-vis de l'usage de fichiers pour des données "liées". Il existe de nombreux systèmes de bases de données souvent construits sur une architecture complexe dite client-serveur (MySQL, PostgresSQL, Access, etc.). Pour ce module nous avons choisi d'utiliser SQLite, une bibliothèque écrite en langage C, qui s’intègre directement aux programmes que l'on souhaite développer. SQLite n'offre bien entendu pas autant de fonctionnalités que les systèmes plus complexes néanmoins c'est un bon point de départ pour découvrir les bases de données puisqu'il va nous permettre d'aborder toutes les notions fondamentales sans se charger de contraintes techniques inutiles pour le moment. 8.1 Quelques définitions Une base de données est constituée d'un ensemble de tables. Une table contient une collection/suite de lignes, aussi appelées enregistrements ; une par entité considérée. Une ligne d'une table est une suite de valeurs, chacune d'un type (nature) déterminé. Une ligne regroupe les données relatives à une entité considérée dans des colonnes aussi appelées champs. Toutes les lignes d'une table ont la même structure (même nombre de colonnes). Une colonne est définie par son nom et le type des valeurs qu'elle peut contenir. Elle peut être obligatoire ou non c'est-à-dire que l'on peut imposer d'avoir une valeur pour chaque entité ou au contraire accepter la valeur particulière NULL (ou None suivant les systèmes) qui représente l'absence de valeur pour cette colonne et l'entité considérée. En SQLite, les types de valeurs qui pourront être gérés sont les suivants : None pour l'absence de valeur. INTEGER pour les entiers. REAL pour les nombres réels. TEXT pour les chaînes de caractères. BLOB pour les données brutes. 64 Dans notre exemple précédent, on peut lister la présence de trois groupes d'entités : des clients, des commandes et des produits. Observons par ailleurs les informations dont on disposait : NCLI Nom Adresse Localité NCOM Date NPRO Qté Libellé Prix Uni. Les colonnes NCLI, Nom, Adresse et Localité se rapportent aux clients, les informations NCOM et Date aux commandes et enfin NPRO, Libellé et Prix Uni aux produits. Il semblerait donc assez naturel de créer trois tables pour stocker ces informations avec, au moins, les colonnes citées précédemment. Tableau 2 : Table des clients NCLI K111 C400 S127 F011 B512 Nom VANBIST FERARD VANDERKA PONCELET GILLET Adresse 180, r. Florimont 65, r. du Tertre 3, av. des Roses 17, Clos des Erables 14, r. de l'Ete Localité Lille Poitiers Namur Toulouse Toulouse Tableau 3 : Table des commandes NCOM 30178 30179 30182 30184 30185 30186 30188 Date 2008-12-21 2008-12-22 2008-12-23 2008-12-23 2009-01-02 2009-01-02 2009-01-03 Tableau 4 : Table des produits NPRO CS464 CS262 PA60 PA45 PS222 PH222 Libellé CHEV. SAPIN 400x6x4 CHEV. SAPIN 200x6x2 POINTE ACIER 60 (1K) POINTE ACIER 45 (1K) PL. SAPIN 200x20x2 PL. HETRE 200x20x2 Prix Uni. 220.00 75.00 95.00 105.00 185.00 230.00 Seules les lignes distinctes ont été conservées, les autres n'apportant pas d'informations supplémentaires dans ce contexte. Pour être équivalent à notre fichier de départ (c'est-à-dire permettre de reconstruire chacune de ses lignes), il nous faut désormais considérer les liens qui existent entre les entités. Un premier lien exprime le fait qu'une commande est passée par un client. C'est un lien de nature assez simple (dit 1-N, car elle peut faire correspondre à une entité (e.g. un client) plusieurs entités 65 (e.g. des commandes)). Pour réaliser ce lien, il est suffisant de rajouter une colonne dans la table des commandes afin d'y recenser les numéros de clients correspondants comme ceci : Tableau 5 : Table des commandes complétée pour faire le lien avec les clients NCOM 30178 30179 30182 30184 30185 30186 30188 NCLI K111 C400 S127 C400 F011 C400 B512 Date 2008-12-21 2008-12-22 2008-12-23 2008-12-23 2009-01-02 2009-01-02 2009-01-03 Ainsi, depuis une commande, on pourra se référer à la table des clients par le numéro NCLI (qui doit être unique). Un autre lien à prendre en compte dans nos données de départ vient du fait qu'une commande correspond à l'achat d'un certain nombre de produits dans une certaine quantité stockée initialement dans le champ Qté que nous n'avions pu rattacher jusque là à aucune entité. Nous sommes confrontés ici à un lien dit N-N car une commande peut correspondre à plusieurs produits et un produit peut apparaitre dans plusieurs commandes. Dans ce cas, il nous faut créer une nouvelle table, que l'on pourrait nommer detail, qui va faire le "pont" entre les tables des commandes et des produits et dans laquelle une ligne servira à exprimer qu'un produit en particulier a été commandé dans une commande particulière. Soit la table suivante : Tableau 6 : Table faisant le lien entre les commandes et les produits NCOM 30178 30179 30179 30182 30184 30184 30185 30185 30185 30186 30188 30188 30188 30188 NPRO CS464 CS262 PA60 PA60 CS464 PA45 CS464 PA60 PS222 PA45 CS464 PA45 PA60 PH222 Qté 25 60 20 30 120 20 260 15 600 3 180 22 70 92 Ainsi nous avons au niveau des données stockées un équivalent de notre fichier de départ et nous allons pouvoir profiter des fonctionnalités offertes par les systèmes de base de données. 66 Exercice 40 : Vérifiez qu'il y a bien équivalence entre le contenu du fichier et les tableaux de 2 à 6 ci-dessus pour la commande 30185. 8.2 La notion de clé 8.2.1 Clé primaire Une ligne dans une table regroupe des données sur une entité. Une des propriétés importantes dans une table est de pouvoir identifier de manière certaine (unique) une ligne à l'aide d'un identifiant ou clé (key en anglais) qui peut être composée de plusieurs colonnes. Parmi les clés possibles pour une table donnée l'une est déclarée comme clé primaire (primary key en anglais). À partir de là, le système en garantira l'unicité c'est-à-dire qu'il refusera l'insertion d'une nouvelle ligne dans une table si la valeur de sa clé primaire est déjà présente. C'est ce que l'on appelle une contrainte d'unicité. Bien que non imposée, la déclaration d'une clé primaire pour toute table est fortement recommandée. Sans elle, on ne peut plus garantir l'identification d'une ligne de manière certaine. Dans notre exemple précédent, il serait naturel de définir par exemple NCLI comme clé primaire de la table des clients et le couple (NCOM,NPRO) comme clé primaire de la table des détails afin de garantir que pour une commande donnée un produit particulier ne puisse être considéré plus qu'une seule fois. Exercice 41 : Que peut-on choisir comme clé primaire pour les autres tables ? 8.2.2 Clé étrangère Dans une première table donnée (dite "table fille"), une information qui permet de se rapporter à une deuxième table (dite "table parent") c'est-à-dire qui contient des valeurs de la clé primaire de cette seconde table est appelée clé étrangère (foreign key en anglais) pour la première table. Toujours sur notre exemple, le numéro de commande NCOM dans la table des détails est une clé étrangère qui permet de se référer aux commandes. 67 Exercice 42 : Faîtes la liste des clé étrangères présentent dans chaque table de la base de données ci-dessus concernant des clients et leurs commandes. 8.3 Contraintes d'intégrité Les colonnes obligatoires, les clés primaires et les clés étrangères imposent des contraintes sur le contenu de la base de données qui doivent être satisfaites en permanence. Ces différentes contraintes dites d'intégrité seront donc vérifiées lors de chaque tentative de modification de la base de données. Contraintes sur les colonnes obligatoires Si une colonne est déclarée obligatoire (toute entité doit posséder une valeur pour cette colonne), alors, lors de l'ajout ou de la modification d'une ligne, une valeur différente de NULL (ou None) devra être fournie. Dans le cas contraire l'opération sera rejetée. Contraintes sur les clés primaires (contraintes d'unicité) Une clé primaire correspond, comme on l'a vu précédemment, à une contrainte d'unicité. C'est pourquoi un ajout ou une modification d'une ligne ne seront autorisés que s'ils ne remettent pas en cause cette propriété c'est-à-dire seulement si la nouvelle valeur de clé proposée est distincte de toutes celles déjà présentes. Sur notre exemple précédent, il nous serait donc impossible d'ajouter un nouveau client avec un numéro de client égal à K111 puisque ce numéro est déjà présent pour le client VANBIST. Contraintes sur les clés étrangères (contraintes référentielles) Les clés étrangères imposent également des contraintes. Celles-ci, comme nous l'avons vu, ont pour rôle de faire le lien entre plusieurs tables en exploitant les propriétés des clés primaires. Considérons les tables clients et commandes de notre exemple. À partir du moment où une commande est enregistrée pour le client K111 alors ce client ne pourra pas être supprimé, ou son numéro de client modifié, sans rendre la base de donnée incohérente. Plus généralement, la suppression directe dans la table parent (ici la table client) d'une ligne référencée dans la table fille (commande) sera refusée. Pour conserver une base de données cohérente si les données doivent être supprimées, la stratégie consiste à commencer par supprimer les lignes nécessaires dans la table fille avant de réaliser les suppressions dans la table parent. Ainsi si je veux pouvoir supprimer le client K111, il me faudra d'abord supprimer la commande 30178 et avant cela pour les mêmes raisons les lignes de la table des détails qui s'y rapportent. Ici, une seule ligne qui concerne le produit CS464. 68 Exercice 43 : Que faudrait-il faire pour pouvoir supprimer les clients F011 et C400 ? 8.4 Schéma et contenu Une base de données est composée de deux parties distinctes : son schéma et son contenu. Le schéma d'une base de données spécifie la liste des tables et pour chacune son nom, la liste de ses colonnes (champs), sa clé primaire et s'il y a lieu sa ou ses clés étrangères. Pour chaque colonne, il est également précisé son nom, son type et si elle est ou non obligatoire. Le contenu d'une base de données est dépendant de l'instant considéré et est l'ensemble des lignes présentes dans chacune de ses tables. Le contenu d'une base de données réelle compte généralement plusieurs milliers ou millions de lignes et peut évoluer à chaque instant. En revanche, son schéma doit contenir un nombre limité d'éléments et être assez stable dans le temps. Il existe plusieurs conventions graphiques de représentation d’un schéma de BD, parmi lesquelles les plus utilisées sont les suivantes : 1. Une table est représentée le plus souvent par une boîte dont le premier compartiment indique le nom de la table et ensuite les noms de ses colonnes en liste verticale. 2. La clé primaire est soit soulignée d’un trait continu, soit elle est indiquée en gras, soit elle est spécifiée par la clause “id :”. 3. Une clé étrangère est soit soulignée d’un trait pointillé, soit spécifiée par la clause “ref :”. 4. Une contrainte référentielle est représentée par une flèche qui part du nom de la colonne qui est une clé étrangère et qui pointe vers la clé primaire référencée dans la table cible. Figure 19 : Schéma de la base de données obtenu pour notre exemple traitant de clients et de commandes 69 On considère désormais la base de données nommée client_commande dont le schéma pus complet que celui étudié jusqu'à présent est fourni ci-dessous ainsi que le contenu de ses tables à l'instant qui nous intéresse. Figure 20 : Schéma de la base de données client_commande Tableau 7 : Contenu de la table client NCLI B062 B112 B332 B512 C003 C123 C400 D063 F010 F011 F400 K111 K729 L422 S127 S712 NOM GOFFIN HANSENNE MONTI GILLET AVRON MERCIER FERARD MERCIER TOUSSAINT PONCELET JACOB VANBIST NEUMAN FRANCK VANDERKA GUILLAUME ADRESSE 72, r. de la Gare 23, a. Dumont 112, r. Neuve 14, r. de l'Ete 8, ch. de la Cure 25, r. Lemaitre 65, r. du Tertre 201, bvd du Nord 5, r. Godefroid 17, Clos des Erables 78, ch. du Moulin 180, r. Florimont 40, r. Bransart 60, r. de Wepion 3, av. des Roses 14a, ch. des Roses Tableau 9 : Contenu de la table commande NCOM 30178 30179 30182 30184 30185 30186 30188 NCLI K111 C400 S127 C400 F011 C400 B512 DATECOM 2008-12-21 2008-12-22 2008-12-23 2008-12-23 2009-01-02 2009-01-02 2009-01-03 LOCALITE Namur Poitiers Geneve Toulouse Toulouse Namur Poitiers Toulouse Poitiers Toulouse Bruxelles Lille Toulouse Namur Namur Paris CAT B2 C1 B2 B1 B1 C1 B2 NULL C1 B2 C2 B1 NULL C1 C1 B1 COMPTE -3200.00 1250.00 0.00 -8700.00 -1700.00 -2300.00 350.00 -2250.00 0.00 0.00 0.00 720.00 0.00 0.00 -4580.00 0.00 Tableau 8 : Contenu de la table detail NCOM 30178 30179 30179 30182 30184 30184 30185 30185 30185 30186 30188 30188 30188 30188 NPRO CS464 CS262 PA60 PA60 CS464 PA45 CS464 PA60 PS222 PA45 CS464 PA45 PA60 PH222 QCOM 25 60 20 30 120 20 260 15 600 3 180 22 70 92 Tableau 10 : Contenu de la table produit NPRO CS262 CS264 CS464 PA45 PA60 PH222 PS222 LIBELLE CHEV. SAPIN 200x6x2 CHEV. SAPIN 200x6x4 CHEV. SAPIN 400x6x4 POINTE ACIER 45 (1K) POINTE ACIER 60 (1K) PL. HETRE 200x20x2 PL. SAPIN 200x20x2 PRIX 75.00 120.00 220.00 105.00 95.00 230.00 185.00 QSTOCK 45 2690 450 580 134 782 1220 70 Exercice 44 : Étant données ces informations répondez aux questions suivantes : 1. Combien de lignes existent dans chaque table ? 2. Quel est le prix du produit 'POINTE ACIER 60 (1K)' ? Combien en reste-t-il en stock ? 3. Quels sont les noms des clients de catégorie 'B1' ? 4. Pour le client 'FERARD', combien a-t-il passé de commandes ? 5. Pour le client 'VANBIST', combien a-t-il passé de commandes ? 6. Quel(s) produits(s) a commandé le client 'VANBIST' et en quelle quantité ? 8.5 Le langage SQL (Structured Query Language) Présenté pour la première fois en 1973, ce langage a rapidement été adopté comme standard. Une instruction SQL constitue une requête (query en anglais), c'est-à-dire une opération que le système de base de données doit réaliser. 8.5.1 Consulter les données stockées dans une base de données existante Suivant les besoins la consultation (extraction) de données stockées dans une base de données fera intervenir différents mots clés du langage SQL. Extractions simple A minima cela nécessitera le mot clé SELECT pour préciser de quelle(s) colonne(s) nous souhaitons obtenir des valeurs et le mot clé FROM pour indiquer de quelle table. La syntaxe générique est la suivante : SELECT colonne_souhaitée_1, ..., colonne_souhaitée_n FROM table_souhaitée Exemple : Si l'on souhaite, pour toutes les lignes de la table client de notre base de données clientcommande, obtenir les valeurs des colonnes NCLI, NOM et LOCALITE, nous utiliserons la requête suivante : SELECT NCLI, NOM, LOCALITE FROM client 71 Lorsque l'on souhaite obtenir les valeurs pour toutes les colonnes disponibles, il ne sera pas nécessaire de les lister toutes. Nous utiliserons le symbole *. Pour la table client nous aurions donc à écrire: SELECT * FROM client Extractions avec restrictions Souvent ce ne sont pas toutes les lignes qui nous intéressent mais uniquement celles qui correspondent à un critère donné, à une condition qui est vérifiée. Pour cela nous aurons recours au mot clé WHERE suivi de la condition à vérifier. La syntaxe appropriée sera alors : SELECT colonne_souhaitée_1, ..., colonne_souhaitée_n FROM table_souhaitée WHERE condition_est_vraie Une condition étant de type booléen (valant soit vrai soit faux) elle pourra être obtenue par l'usage de comparateur. En SQL, nous avons à notre disposition les opérateurs suivants : = > < <> >= <= égal à plus grand que plus petit que différent de plus grand ou égal à plus petit ou égal à L’interprétation de ces relations est bien connue pour les valeurs numériques. Pour les chaînes de caractères, l’expression chaine_1 < chaine_2 pour toutes chaînes chaine_1 et chaine_2, s’interprète comme chaine_1 est plus petite que chaine_2 selon l’ordre lexicographique (celui du dictionnaire). Attention : la casse est prise en compte autrement dit le caractère "a" n’est pas égal au caractère "A". Les comparaisons peuvent être "combinées" à l'aide d'opérateurs logiques pour obtenir des conditions complexes. Nous disposons en SQL des opérateurs suivants : AND conjonction (et) OR disjonction (ou) NOT négation (opposé) Lorsqu’une expression complexe comporte plusieurs opérateurs, les priorités des opérateurs déterminent l’ordre d’exécution des opérations. Les règles qui s'appliquent ici sont identiques à celles du langage Python (cf. section 3.1.6). Exemple : SELECT NCLI, NOM FROM client WHERE LOCALITE = 'Toulouse' 72 La requête ci-dessus nous fournira les valeurs des colonnes NCLI et NOM de la table client pour toutes les lignes dont la valeur pour la colonne LOCALITE est égale à Toulouse. Exercice 45 : NCLI B062 B112 B332 B512 C003 C123 C400 D063 F010 F011 F400 K111 K729 L422 S127 S712 NOM GOFFIN HANSENNE MONTI GILLET AVRON MERCIER FERARD MERCIER TOUSSAINT PONCELET JACOB VANBIST NEUMAN FRANCK VANDERKA GUILLAUME ADRESSE 72, r. de la Gare 23, a. Dumont 112, r. Neuve 14, r. de l'Ete 8, ch. de la Cure 25, r. Lemaitre 65, r. du Tertre 201, bvd du Nord 5, r. Godefroid 17, Clos des Erables 78, ch. du Moulin 180, r. Florimont 40, r. Bransart 60, r. de Wepion 3, av. des Roses 14a, ch. des Roses LOCALITE Namur Poitiers Geneve Toulouse Toulouse Namur Poitiers Toulouse Poitiers Toulouse Bruxelles Lille Toulouse Namur Namur Paris CAT B2 C1 B2 B1 B1 C1 B2 NULL C1 B2 C2 B1 NULL C1 C1 B1 COMPTE -3200.00 1250.00 0.00 -8700.00 -1700.00 -2300.00 350.00 -2250.00 0.00 0.00 0.00 720.00 0.00 0.00 -4580.00 0.00 En supposant qu'à l'instant qui nous intéresse la table client est celle donnée ci-dessus, quel est le résultat de la requête précédente ? Opérateurs spécifiques Une condition peut également porter sur la présence de la valeur NULL (ou None) : CAT is null CAT is not null ou sur l’appartenance ou non à un ensemble : CAT in ( 'C1', 'C2', 'C3') LOCALITE not in ( 'Toulouse', 'Namur', 'Breda') ou encore sur la présence de certains caractères dans une valeur : CAT like '_1' ADDRESSE like '%Neuve%' Dans les deux dernières conditions, le signe '_' désigne un caractère quelconque et '%' désigne toute suite de caractères, éventuellement vide. 73 Extractions sans doublons Pour éliminer les lignes en double dans le résultat d’une requête, on utilise le mot clé distinct. Exemple : SELECT distinct LOCALITE FROM client Extractions ordonnées Il est possible d’ordonner les résultats d’une requête grâce au mot clé ORDER BY: SELECT liste_colonnes FROM nom_table WHERE condition ORDER BY liste_colonnes ASC|DESC Par défaut, le classement se fait par ordre ascendant (ASC) des valeurs. On peut également spécifier explicitement un ordre ascendant (ASC) ou descendant (DESC). Exemple: Pour la base de données client_commande les lignes résultant de la requête ci-dessous vont apparaître classées par ordre alphabétique croissant sur le nom des localités. SELECT * FROM client WHERE CAT is not null ORDER BY LOCALITE Lorsqu'une liste de critères de tri est spécifiée, l'ordre des éléments marque les priorités du tri : on considère d'abord l'élément le plus à gauche dans la liste, puis à valeurs égales on considère l'élément à sa droite et ainsi de suite. Exemple : SELECT * FROM client ORDER BY LOCALITE, CAT Par cette requête, nous obtiendrons toutes les informations disponibles sur les clients classés par localité, puis dans chaque localité, classés par catégorie. Exercice 46 : Pour chaque requête donnée ci-dessous expliquez ce qu’elle fait et trouvez la réponse attendue compte tenu des tableaux 8 à 11. SELECT * FROM produit WHERE LIBELLE LIKE '%ACIER%' ORDER BY PRIX 74 SELECT NCOM, DATECOM FROM commande WHERE NCLI = 'C400' AND DATECOM < '2009-01-01' SELECT NOM, ADRESSE, COMPTE FROM client WHERE COMPTE > 0 AND (CAT = 'C1' OR LOCALITE = 'Paris') SELECT NOM, ADRESSE, COMPTE FROM client WHERE COMPTE > 0 OR CAT = 'C1' AND LOCALITE = 'Paris' Dans l’expression COMPTE > 0 OR CAT = 'C1' AND LOCALITE = 'Paris' dans quel ordre sont évalués les opérateurs logiques OR et AND ? Réponse 1 : ((COMPTE > 0 OR CAT = 'C1') AND LOCALITE = 'Paris') ou Réponse 2 : (COMPTE > 0 OR (CAT = 'C1' AND LOCALITE = 'Paris')) Justifiez votre réponse. Exercice 47 : En utilisant l'application AgroPythia, que vous trouverez sur la page du cours, vérifiez vos réponses pour l'Exercice 46. Pour éviter de les retaper, celles-ci ont été retranscrites dans le fichier requetes_client_commande.sql. Vous pourrez ainsi procéder par copier-coller. Exercice 48 : Toujours à l'aide de l'application AgroPythia, écrivez les requêtes répondant aux demandes suivantes : 1. Extraire en ordre alphabétique la liste des localités où habitent les clients. 2. Affichez les commandes passées pendant les dix derniers jours de l’année 2008. 3. Affichez le stock de chaque produit dont le libellé contient 'sapin'. 4. Affichez les produits dont le prix est inférieur à 200 et le stock est supérieur à 150. 5. Affichez le numéro, le nom et la localité des clients de catégorie B1 n’habitant pas à Paris. 75 Vous conserverez vos réponses en copiant-collant vos requêtes dans le fichier requetes_client_commande.sql. Remarque : Vous pouvez également les récupérer depuis le fichier de log généré lors de votre usage de l'application AgroPythia . Extractions sur plusieurs tables Pour profiter des liens qui existent entre les tables d'une base de données, nous allons avoir recours à ce que l'on appelle des jointures qui de manière dynamique vont nous permettre de mettre en correspondance les éléments des différentes tables liées entre elles suivant une règle que l'on devra préciser. Autrement dit nous aurons à préciser la nature des liens qui les unissent. Syntaxe générale : SELECT liste_colonnes FROM nom_table_1, nom_table_2, nom_table_3, ..., nom_table_n WHERE col_FK_1 = col_PK_2 AND col_FK_31 = col_PK_2 AND col_FK_32 = col_PK_n AND ... AND condition Ci-dessus, nous considérons n tables liées entre elles. Nous aurons donc à préciser n-1 liens par des conditions du type col_FK_i = col_PK_j où col_FK_i désigne la colonne dans la table nommée nom_table_i qui est une clé étrangère pour celle-ci lui permettant de se relier à la table nommée nom_table_j dont la clé primaire est nommée col_PK_j. Notre requête générique ci-dessus fait donc état des liens suivants : Figure 21 : Schéma partiel de la base de données considérée dans l'exemple ci-dessus Comme indiqué dans la requête générique ci-dessus, aux différentes conditions de jointure nécessaires, il est possible, comme nous le faisions jusque là, d'ajouter d'autres conditions qui seront combinées toujours par des opérateurs logiques. 76 Exemple : SELECT NCOM, DATECOM, NOM, LOCALITE FROM commande, client WHERE commande.NCLI = client.NCLI La requête ci-dessus, en considérant nos données initiales sur les clients et leurs commandes, nous fournira en plus du numéro et de la date contenus dans la table des commandes, le nom et la localité du client concerné, des informations qui proviennent quant à elles de la table des clients. Tableau 11 : Résultat fourni pour la requête ci-dessus et les données considérées en début de chapitre NCOM 30178 30179 30182 30184 30185 30186 30188 Date 2008-12-21 2008-12-22 2008-12-23 2008-12-23 2009-01-02 2009-01-02 2009-01-03 Nom VANBIST FERARD VANDERKA FERARD PONCELET FERARD GILLET Localité Lille Poitiers Namur Poitiers Toulouse Poitiers Toulouse Remarques : Si les deux tables ont des colonnes qui ont le même nom, il faut lever l’ambiguïté associée et préciser à quelle table appartient la colonne que l'on souhaite obtenir, en utilisant la syntaxe suivante : nom_table.nom_colonne L’ordre des noms des tables dans la clause FROM ainsi que l’ordre des conditions dans la clause WHERE n’a pas d’importance. Quelques détails sur le mécanisme de jointure Détaillons un peu ce mécanisme lors d'une jointure faisant intervenir deux tables. Le résultat est dans ce cas obtenu comme suit : 1. On construit dynamiquement une table temporaire en couplant chaque ligne de la première table avec chaque ligne de la seconde. Ainsi, pour la requête ci-dessus nous considérons la table des clients (4 colonnes et 5 lignes) et la table des commandes (3 colonnes et 7 lignes). Nous construisons donc à cette étape une table qui contiendra 7 colonnes (juxtaposition de colonnes des deux tables considérées) et 35 lignes (les 5 lignes des clients ayant été associées systématiquement aux 7 lignes des commandes). 2. On sélectionne, parmi les lignes ainsi obtenues, celles qui vérifient la condition de jointure. Dans notre exemple nous ne conserverons que les lignes qui mettent bien en correspondance un client avec ses propres commandes. 3. Si d'autres conditions sont considérées, les lignes seront encore filtrées suivant ces conditions. 4. Enfin, seules les valeurs des colonnes ou éventuellement des calculs demandés seront fournies en résultat. 77 Exercice 49 : Premières jointures Pour chaque requête donnée ci-dessous expliquez ce qu’elle fait et trouvez la réponse attendue par rapport au contenu des tableaux 8 à 11. Attention : Les requêtes peuvent être incomplètes ou fausses ! SELECT NOM, NCOM FROM client, commande WHERE client.NCLI = commande.NCLI AND LOCALITE = 'Toulouse' SELECT LIBELLE, PRIX, QCOM FROM detail, produit WHERE detail.NPRO = produit.NPRO AND NCOM = '30188' SELECT NOM, commande.NCOM, detail.NCOM, LIBELLE, PRIX, QCOM FROM client, commande, detail, produit WHERE client.NCLI = commande.NCLI AND detail.NPRO = produit.NPRO AND commande.NCOM = '30188' Exercice 50 : En utilisant à nouveau l'application AgroPythia vérifiez vos réponses à l'Exercice 49. Pour éviter de les retaper, celles-ci ont été retranscrites dans le fichier requetes_client_commande.sql. Vous pourrez ainsi procéder par copier-coller. Exercice 51 : Requêtes avec jointures Après avoir identifié les tables à consulter et les conditions de jointure nécessaires, utilisez l'application AgroPythia pour écrire les requêtes SQL qui répondent aux questions ci-dessous. Copier celles-ci dans le fichier requetes_client_commande.sql afin de les conserver. 1. Affichez tous les produits commandés (nom, prix, quantité) par le client 'B512'. 2. Affichez tous les produits (nom, prix, quantité) commandés par le client 'FERARD', par ordre croissant de numéro de commande puis par ordre alphabétique sur le nom de produit. 3. Quels clients ont procédé à des commandes en 2009 ? 4. Quels sont les noms des produits qui ont été commandés les dix derniers jours de l’année 2008 ? 5. Quels sont les numéros des produits qui ont été commandés à la fois le 23/12/2008 et le 2/01/2009 ? 78 6. Quels sont les numéros des produits qui ont été commandés le 2/01/2009, mais pas le 23/12/2008 ? Et quels sont les noms de ces produits ? Extractions et opérations sur les valeurs La clause SELECT permet aussi de spécifier des données calculées ou encore des constantes. Exemple : SELECT NPRO, ’ = ’, 1.20*PRIX*QSTOCK AS ”mont ant TVA” FROM produit Le résultat de la requête ci-dessus contient autant de lignes que de produits présents dans la table produit et pour chacun d'eux leur numéro associé à la valeur du stock TTC. SQLite offre différentes fonctions permettant de répondre à bon nombre de besoins. Reportez-vous à la page https://www.sqlite.org/lang_corefunc.html pour une liste de celles-ci. Fonctions agrégatives Il existe des fonctions prédéfinies qui donnent une valeur "agrégée" calculée pour les lignes sélectionnées par une requête select. Elles sont documentées à l'adresse suivante : https://www.sqlite.org/lang_aggfunc.html. Voici quelques exemples : count(*) compte le nombre de lignes trouvées, count(nom_colonne) compte le nombre de valeurs trouvées de la colonne (équivalent au nombre de lignes), avg(nom_colonne) calcule la moyenne des valeurs de la colonne, sum(nom_colonne) calcule la somme des valeurs de la colonne, min(nom_colonne) donne le minimum des valeurs de la colonne, max(nom_colonne) donne le maximum des valeurs de la colonne. Il est à noter que ces fonctions, à l’exception de la première (count), ne considèrent que les valeurs non NULL de la colonne. En outre, chaque valeur est prise en compte, même si elle apparaît plusieurs fois. Exemple : SELECT count(*) FROM client La requête ci-dessus permet d'obtenir le nombre de lignes de la table nommée client. SELECT count(distinct NCLI) FROM commande La requête ci-dessus donnera le nombre de clients différents ayant passé au moins une commande. Attention : SELECT MAX(DATECOM) FROM commande 79 La requête ci-dessus affiche la date de la dernière commande enregistrée dans la table commande (la plus grande), en revanche la requête ci-dessous est fausse. SELECT MAX(DATECOM), NCOM FROM commande En effet, elle ne permet pas de récupérer le numéro de cette dernière commande ! En d'autres termes le système n'est pas capable de retenir à quelle ligne le max se trouvait et lui associer ainsi les autres valeurs contenues dans sa ligne. Par cela, il faut raisonner en deux temps : trouver le maximum puis trouver la ligne où se trouve le maximum identifié. Cela correspond à faire ce que l'on appelle une sous-requête : SELECT NCOM, DATECOM FROM commande WHERE DATECOM in ( SELECT MAX(DATECOM) FROM commande ) Groupements Le mot clé Group By permet de créer des groupements de lignes en fonction d'une liste de critères. Il fournit une ligne par groupe avec les informations du dernier représentant de chaque groupe. Par exemple, la requête suivante nous donne les informations du dernier client de chaque catégorie. SELECT * FROM client GROUP BY cat Compte tenu de l'état considéré pour notre base de données client-commande, les sorties correspondantes seraient les suivantes : NCLI K729 S712 F011 S127 F400 NOM NEUMAN GUILLAUME PONCELET VANDERKA JACOB ADRESSE 40, r. Bransart 14a, ch. des Roses 17, Clos des Erables 3, av. des Roses 78, ch. du Moulin LOCALITE Toulouse Paris Toulouse Namur Bruxelles CAT None B1 B2 C1 C2 COMPTE 0.0 0.0 0.0 -4580.0 0.0 Ce type de résultats est rarement utile. En revanche, associée à une fonction agrégative, la clause Group By permet au sein d'une requête d'effectuer des traitements de nature statistique. Cela permet, par exemple, de savoir pour chaque catégorie de clients combien se situent dans telle ou telle catégorie. SELECT count(*), cat FROM client GROUP BY cat 80 Ce qui nous donnerait : count(*) 2 4 4 5 1 cat None B1 B2 C1 C2 Le groupement pouvant se faire sur plusieurs critères, cela nous permettrait également de déterminer combien de clients d'une certaine catégorie habitent dans une certaine ville et ainsi de suite. SELECT count(*), cat, localite FROM client GROUP BY cat, localite Tout ceci peut bien entendu être combiné avec l'utilisation de jointures ou autres fonctions agrégatives pour répondre à des questions plus poussées. Exercice 52 : Fonction agrégatives Pour chaque requête donnée ci-dessous expliquez ce qu’elle fait et trouvez la réponse attendue toujours par rapport aux tableaux 8 à 11. SELECT nom, max(COMPTE) FROM client SELECT count(distinct commande.NCOM) FROM commande, detail, produit WHERE commande.NCOM = detail.NCOM AND detail.NPRO = produit.NPRO AND LIBELLE like '%ACIER%' SELECT sum(QCOM*PRIX) as MONTANT FROM detail, produit WHERE detail.NPRO = produit.NPRO AND LIBELLE like '%ACIER%' 81 SELECT LIBELLE, PRIX as PrixUnitaire, QCOM, QCOM*PRIX as SousTotal FROM detail, produit WHERE detail.NPRO = produit.NPRO AND NCOM = '30179' Exercice 53 : En utilisant à nouveau l'application AgroPythia vérifiez vos réponses à l'Exercice 52. Pour éviter de les retaper, celles-ci ont été retranscrites dans le fichier requetes_client_commande.sql. Vous pourrez ainsi procéder par copier-coller. Exercice 54 Requêtes avec agrégations Utilisez l'application AgroPythia pour écrire les requêtes SQL qui répondent aux questions suivantes et copiez-les dans le fichier requetes_client_commande.sql. 1. 2. 3. 4. 5. 8.5.2 Calculez le nombre de produits (références) dont le libellé contient 'sapin'. Calculez le montant des produits commandés dont le libellé contient 'sapin'. Calculez le nombre d’unités de produits commandés dont le libellé contient 'sapin'. Quel est le produit dont la quantité en stock est la plus faible ? Calculer le nombre de fois où chaque produit a été commandé. Ajouter des données dans une table Pour ajouter une ligne dans une table on utilise les mots clé INSERT INTO pour préciser la table dans laquelle on souhaite effectuer une insertion et VALUES pour préciser les valeurs à ajouter pour les différentes colonnes concernées. Syntaxe générale : INSERT INTO nom_table (col_1, ..., col_n) VALUES (val_1, ...,val_n) col_1, ..., col_n désignent ici les colonnes pour lesquelles nous allons fournir des valeurs (toutes sauf éventuellement celles qui sont optionnelles ou auto-incrémentées) dans l'ordre dans lesquelles nous souhaitons les fournir ce qui permet de s'abstraire de l'ordre déclaré au niveau du système de base de données. val_1, ..., val_n sont les valeurs correspondantes sachant que val_1 doit correspondre à la valeur que l'on souhaite donner à la colonne col_1 et ainsi de suite. Exemple : INSERT INTO detail (NCOM, NPRO, QCOM) VALUES ('30185', 'PA45', 12) 82 La requête ci-dessus permet d'ajouter les valeurs '30185' pour la colonne nommée NCOM, 'PA45' pour la colonne nommée NPRO et 12 pour la commande nommée QCOM dans une table nommée détail. Remarques : Toute colonne non spécifiée dans la liste des colonnes prend la valeur NULL, la valeur par défaut si celle-ci a été déclarée comme propriété de la colonne, ou une valeur possible générée par le système dans le cas d'une colonne auto-incrémentée. Chaque valeur fournie (issue éventuellement d'un calcul) doit avoir même type que celui de la colonne concernée. De manière générale, les données ajoutées doivent respecter les contraintes d’intégrité (unicité, intégrité référentielle, colonnes obligatoires) attachées à la table dans laquelle la nouvelle ligne est insérée (cf. section 8.3). 8.5.3 Supprimer des données dans une table Pour supprimer des lignes dans une table on utilise le mot clé DELETE FROM pour préciser la table qui sera concernée et le mot clé WHERE pour préciser les lignes concernées c'est-à-dire celles qui correspondront à la condition spécifiée. Syntaxe générale : DELETE FROM nom_table WHERE condition Ici l'usage du WHERE est analogue à celui vu précédemment pour l'extraction d'informations (cf. section 8.5.1). De même qu'après un ajout, après une suppression, la base de données doit être dans un état qui respecte toutes les contraintes d’intégrité (unicité, intégrité référentielle, colonnes obligatoires) auxquelles elle est soumise sans quoi l'opération sera refusée et une erreur sera retournée. 8.5.4 Modifier le contenu d'une table Pour modifier des valeurs dans les lignes d’une table nous aurons recours au mot clé UPDATE pour préciser la table concernée, SET pour préciser la liste des affections à réaliser ainsi qu'au mot clé WHERE si les modifications ne concernent que les lignes qui correspondent à une certaine condition. Syntaxe générale : UPDATE nom_table SET nom_colonne = nouvelle_valeur, ... nom_colonne = nouvelle_valeur, WHERE condition La modification sera effectuée pour toutes les lignes qui vérifient la condition de sélection. Les nouvelles valeurs peuvent résulter de calculs. Exemple : UPDATE produit SET PRIX = PRIX * 1.05 WHERE LIBELLE like ’%SAPIN%’ Dans la requête ci-dessus, pour une table nommée produit la colonne nommée PRIX verra ses valeurs augmentées de 5 % si dans la colonne nommée LIBELLE la valeur contient SAPIN. 83 Exercice 55 : Pour chaque requête donnée ci-dessous expliquez ce qu’elle fait et trouvez la réponse attendue. ATTENTION! Le contenu de la base de données sera modifié ! UPDATE produit SET QSTOCK = QSTOCK - 10 WHERE NPRO = 'PH222' UPDATE client SET CAT = 'A1' WHERE CAT IS NULL INSERT INTO produit ( NPRO, LIBELLE, PRIX, QSTOCK) VALUES ( 'PH122', 'PL HETRE 100 x20x2', '120', '500') DELETE FROM detail WHERE NCOM = '30188' DELETE FROM commande WHERE NCOM = '30188' Exercice 56 : En utilisant à nouveau l'application AgroPythia vérifiez vos réponses à l'Exercice 55. Pour éviter de les retaper, celles-ci ont été retranscrites dans le fichier requetes_client_commande.sql. Vous pourrez ainsi procéder par copier-coller. 84 Exercice 57 : Requêtes de mises à jour Utilisez l'application AgroPythia pour écrire les requêtes SQL qui répondent aux questions suivantes et copiez-les dans le fichier requetes_client_commande.sql. 1. Ajoutez un nouveau client, nommé 'FRANCK', qui habite à Lille, au 14 avenue du Reclus (son numéro de client est 'W100' et son compte est 0.0). 2. Enregistrez une première commande du client 'FRANCK' : 10 unités du produit 'PL. HETRE 200x20x2' (le numéro de sa commande est 30189). 3. Mettez à jour le stock du produit 'PL. HETRE 200x20x2'. 4. Mettez à jour toutes les tables concernées pour enregistrer l’opération suivante : Le client 'FRANCK' achète aussi 25 unités du produit 'POINTE ACIER 60 (1K)' sur la même commande 30189. 5. Corrigez la quantité commandée du produit 'PA60' dans la commande '30185' : elle est de 10 unités. Exercice 58 : Mises à jour et contraintes d'intégrité Pour chaque question donnée ci-dessous trouvez la réponse en la justifiant : 1. Est-il possible d’ajouter un client ayant le même nom qu’un client déjà présent dans la base de données ? 2. Est-il possible d’ajouter dans la base de données une commande ayant pour numéro de commande '30179' (une commande existante), pour numéro de client 'C400' (un client existant) et dont la date de commande serait le 04/01/2012 (une nouvelle date) ? 3. Est-il possible d’ajouter à la commande numéro '30188' le produit 'PS222' ? Et le produit 'PH222' ? 4. Est-il possible d’ajouter dans la base de données un produit dont le numéro est 'PH222' et le libellé 'Planche' ? 5. Quelle est la liste des mises à jour à faire pour supprimer la commande '30179' ? 6. Est-il possible de supprimer n’importe quel enregistrement de la table detail ? 85 7. Est-il possible de supprimer n’importe quel enregistrement de la table commande ? 8. Est-il possible de supprimer n’importe quel enregistrement de la table produit ? 9. Est-il possible de supprimer n’importe quel enregistrement de la table client ? 8.5.5 Créer une base de données Nous avons vu jusqu'à présent comment il est possible de sélectionner de l'information stockée dans une base de données ou de la modifier grâce au langage SQL. Ce langage permet également d'agir sur le schéma de la base de données en commençant par la création de la base elle-même. Compte tenu de l'architecture simplifiée de SQLite, la création d'une base de donnée se résume à fournir un nom de fichier par exemple client_commande.sqlite. L'extension peut être quelconque. 8.5.6 Créer / Supprimer/ Modifier la structure d'une table Création Pour créer une table nous aurons recours à la syntaxe suivante : CREATE TABLE nom_de_la_table ( nom_de_la_premiere_colonne type, nom_de_la_suivante type, ... nom de la derniere type ) La mise en page des instructions ici n'a pas d'influence sur leur signification et est réalisée uniquement pour la lisibilité. Il faut indiquer le nom de la nouvelle table, nom_de_la_table, ainsi que la description de ses colonnes : pour chaque colonne il faut spécifier son nom et son type. Sur les colonnes on peut ajouter le cas échéant les contraintes à considérer : – pour définir une colonne obligatoire, il faut ajouter NOT NULL après sa définition ; – pour définir une clé primaire, il faut ajouter PRIMARY KEY après sa définition ; – pour définir une clé étrangère, il faut ajouter FOREIGN KEY (colonne) REFERENCES table_cible (colonne_cible) après la liste des colonnes. Par exemple la création de la table commande de notre exemple précédent se ferait par la requête suivante : 86 CREATE TABLE commande ( NCOM INTEGER PRIMARY KEY, NCLI TEXT NOT NULL, Date TEXT NOT NULL, FOREIGN KEY (NCLI) REFERENCES client(NCLI) ) Cette opération produit une table vide (c’est-à-dire sans ligne). Pour compléter cela, le mot clé AUTOINCREMENT est noté après la définition d'une colonne de type entier pour laquelle on souhaite que le système attribue automatiquement des valeurs non encore utilisées, ce qui s'avère particulièrement utile pour les clés primaires lorsque les numéros attribués n'ont pas de sens particulier par rapport à l'entité décrite. Le mot clé DEFAULT, quant à lui, sera utilisé pour préciser une valeur pour défaut pour une colonne. Exemple : CREATE TABLE nom_table ( ID INTEGER PRIMARY KEY AUTOINCREMENT, NOM TEXT NOT NULL DEFAULT NO_Name ) Suppression Pour supprimer une table nous utiliserons la syntaxe suivante : DROP TABLE nom_de_la_table Remarque : Si cette table contenait des données, elles seront perdues. Modification La modification du schéma d’une base de données implique le plus souvent des modifications au niveau des données elles-mêmes. Par exemple, l’ajout d’une colonne à une table contenant des lignes est suivi de la modification de cette colonne pour chacune des lignes en indiquant NULL ou la valeur par défaut si elle est spécifiée. Pour pouvoir être appliquées, ces opérations de modification doivent respecter les contraintes d’intégrité. Il en découle que : – Pour ajouter une colonne, si la colonne est facultative, l’opération s’effectue sans contrainte. Si elle est obligatoire, alors la table doit être vide ou la colonne doit être accompagnée d’une valeur par défaut. – Pour supprimer une colonne elle ne doit pas intervenir dans la composition d’une clé (primaire ou étrangère). Si nécessaire, ces clés doivent d’abord être modifiées ou supprimées. – Pour ajouter une clé primaire, si la table n’est pas vide, les lignes doivent respecter la contrainte d’unicité. – Pour supprimer une clé primaire, elle ne doit pas être référencée par une clé étrangère. Pour ajouter une clé étrangère, si la table n’est pas vide, les lignes doivent respecter la contrainte référentielle. 87 À cause de toutes ces règles, la modification du schéma d’une base de données n’est pas une opération aisée et elle doit être réalisée avec précaution et bien justifiée. Sqlite n'autorise pour sa part que de renommer une table ou d'y ajouter des colonnes. Pour plus de détails, vous pouvez vous reporter à la documentation de SQLite : https://www.sqlite.org/. 88 Séance 7 9 Interaction avec des bases de données en Python Python fournit les moyens d'utiliser les ressources de nombreux systèmes (MySQL, PostgreSQL, SQLite, …). Pour ce cours nous interagirons avec une base de données SQLite. Les différentes bibliothèques d’interaction avec des systèmes de base de données étant fondées sur la même API (Application Programming Interface : ensemble de fonctions grâce auxquelles un logiciel offre des services à d’autres logiciels), la transposition de ce qui sera vu à d’autres systèmes est en général très simple. Pour accéder aux fonctionnalités désirées, il suffit d'importer le module sqlite3 intégré à Python : import sqlite3 (Le chiffre à la fin du nom est le numéro de la version actuelle du module d'interface. Il est possible qu’il évolue avec le temps). 9.1 Se connecter à une base de données SQLite en Python Une base de données Sqlite est matérialisée par un fichier dans lequel, sous un format maîtrisé par SQLite, sont stockés tous les éléments de la base de données (schéma et contenu). Pour pouvoir se connecter à une base de données il faudra donc connaître le nom du fichier sous lequel elle a été sauvegardée et fournir cette information à notre programme par exemple en utilisant une variable intermédiaire comme ci-dessous : fichierDonnees = "E:/apprentis/bd_test.sqlite" Vous devrez a priori préciser tout le chemin permettant d'accéder au fichier concerné. Vous devez ensuite créer une variable de type connexion, à l'aide de la fonction connect(). Cet objet assurera l'interface entre votre programme et la base de données. L'opération est tout à fait comparable à l'ouverture d'un simple fichier texte, l'instanciation de l'objet créant le fichier de mémorisation au passage s'il n'existe pas déjà. ma_connexion = sqlite3.connect(fichierDonnees) Une fois l'objet connexion créé, il faut créer un objet dit curseur destiné à mémoriser temporairement les données en cours de traitement et les opérations que vous effectuez sur elles, avant leur transfert définitif dans la base de données. Cette technique permet donc d'annuler si nécessaire une ou plusieurs opérations qui se seraient révélées inadéquates, et de revenir en arrière dans le traitement, sans que la base de données n'en soit affectée. 89 mon_curseur = ma_connexion.cursor() Grâce aux objets connexion et curseur créés, il est désormais possible de « dialoguer » avec la base de données en effectuant des requêtes SQL. Pour rappel, une base de données se compose toujours d'une ou plusieurs tables, qui contiendront les enregistrements, ceux-ci comportant un certain nombre de champs (colonnes) de différents types. 9.2 Interroger une base de données SQLite en Python L'interrogation de la base s'effectue à l'aide de requêtes SQL (sous la forme de chaînes de caractères) par l’intermédiaire de la méthode execute() du curseur. Si nous souhaitons par exemple récupérer toutes les informations provenant d'une table qui s'appellerait membres nous avons à écrire l'instruction suivante : mon_curseur.execute("SELECT * FROM membres") Cette requête demande la sélection d'un ensemble particulier d'enregistrements, qui devront être transférés de la base de données au curseur. Les enregistrements sélectionnés sont donc à présent dans le curseur. Si nous voulons les voir, nous devons les en extraire. Cela peut être réalisé de deux façons qui tirent parti du fait que l'objetcurseur produit par Python est un itérateur, c'est-à-dire un dispositif générateur de séquences. Vous pouvez parcourir directement la séquence qu'il produit, à l'aide d'une boucle for classique. Vous obtenez une série de tuples (séquences analogues aux listes mais non modifiables et bordés par des parenthèses). Par exemple : for un_tuple in mon_curseur: print(un_tuple) pourrait avoir comme résultat ce qui suit si la base de données contient bien les trois membres cités ci-dessous : (21, 'Dupont', 1.83) (15, 'Blumâr', 1.57) (18, 'Özémir', 1.69) Vous pouvez également la recueillir dans une liste en vue d'un traitement ultérieur (à l'aide de la fonction intégrée list()) : mon_curseur.execute("SELECT * FROM membres") liste_resultats = list(cur) #[(21, 'Dupont', 1.83), (15, 'Blumâr', 1.57), (18, 'Özémir', 1.69)] Vous pouvez également, d'une manière plus classique, faire appel à la méthode fetchall() du curseur, laquelle renvoie elle aussi une liste de tuples : mon_curseur.execute("SELECT * FROM membres") liste_resultats = mon_curseur.fetchall() #[(21, 'Dupont', 1.83), (15, 'Blumâr', 1.57), (18, 'Özémir', 1.69)] 90 9.3 Interrogation contextuelle Dans la pratique, les données que l'on souhaite extraire de notre base de données dépendent le plus souvent du contexte dans lequel on se trouve. Par exemple, nous souhaitons avoir les informations sur un client en particulier qui vient de se connecter à notre application de commande en ligne. Du point du vue du programme cela signifie que nos requêtes vont donc dépendre de la valeur de variables ici en Python. Vous devrez donc construire la chaîne de caractères contenant la requête SQL, en y incluant les valeurs tirées de ces variables. Il est cependant fortement déconseillé de faire appel dans ce but aux techniques ordinaires de formatage des chaînes, car cela peut ouvrir une faille de sécurité dans vos programmes, en y autorisant les intrusions par la méthode de piratage connue sous le nom de SQL injection. Il faut donc plutôt confier le formatage de vos requêtes au module d'interface lui-même. La bonne technique est illustrée ci-après : la chaîne « squelette » utilise le point d'interrogation comme balise de conversion, et le formatage proprement dit est pris en charge par la méthode execute() du curseur : nom_client = "Durand" mon_curseur.execute("SELECT * from clients WHERE nom = ?", nom_client) 9.4 Se déconnecter d'une base de données SQLite en Python Une fois les échanges souhaités avec la base de données réalisés, le curseur et la connexion doivent être refermés : mon_curseur.close() ma_connexion.close() Exercice 59 : Les Simpsons On donne la base de données simpsons.sqlite qui contient pour le moment une table nommée personnages ayant trois colonnes nommée ID, Prenom, et Nom. 1. Écrivez un script nommé simpsons.py permettant de se connecter à cette base de données. 2. Ajoutez à votre script une fonction affiche_noms permettant d'afficher tous les noms présents dans la base. Complétez votre programme principal pour tester votre fonction. 3. Ajoutez à votre script une fonction affiche_prenoms qui, étant donné un nom, permet d'afficher les prénoms qui lui sont associés dans la base de donnée. Complétez votre programme pour tester votre fonction avec un nom choisi par l'utilisateur. Pensez à vérifier que l'utilisateur a bien fourni un nom contenu dans la base de données. 91 Exercice 60 : Locations de bateaux Sur de nombreux canaux français, la navigation commerciale a laissé la place à la navigation de plaisance. Ainsi la société Starboat s’est fait une réputation dans la location de pénichettes aux touristes. Pour améliorer sa productivité, elle a besoin d’un système d’information afin d’équiper chacun de ses centres de location d’un logiciel de gestion adapté à leurs activités. Le schéma de base de données retenu pour cela est le suivant : Figure 22 : Schéma de la base de données Starboat Le système d’information conçu pour la société Starboat doit pouvoir fournir – la liste de tous les bateaux en cours de location ; – la liste des bateaux accompagnés des informations correspondant à leur modèle et classés par modèle ; – la liste de tous les bateaux de type Flying Bridge ; – le nom du dernier bateau révisé ; – la liste des bateaux qui ont subi un dommage en cours de location ; – la liste des bateaux disponibles à la location. Écrivez et testez les fonctions nécessaires pour répondre aux attentes précédentes. Pour la deuxième fonctionnalité, vous devrez fournir un fichier contenant les informations désirées avec un bateau par ligne et pour chaque ligne des tabulations entre les différentes valeurs souhaitées. Pour les autres fonctionnalités uniquement un affichage à l'écran est attendu. 92 9.5 Insertions et suppressions Lorsque l'on souhaite rajouter ou modifier de l'information dans une base de données depuis un programme Python, cela se passe de manière analogue à de l'interrogation contextuelle puisqu'à nouveau les données à enregistrer se présenteront dans des variables. Il faudra donc de la même manière concevoir les requêtes adaptées et utiliser le formatage présenté ci-dessus. Voici un exemple : data =[(17,"Durand",1.74),(22,"Berger",1.71),(20,"Weber",1.65)] for tu in data: mon_curseur.execute("INSERT INTO membres(age,nom,taille) VALUES(?,?,?)", tu) ma_connexion.commit() Dans cet exemple, la chaîne de requête comporte 3 points d'interrogation, qui sont nos balises. Elles seront remplacées par les 3 éléments du tuple tu à chaque itération de la boucle, le module d'interface avec SQLite se chargeant de traiter chaque variable correctement en fonction de son type. Pour modifier un ou plusieurs enregistrements, exécutez une requête du type : mon_curseur.execute("UPDATE membres SET nom ='Gerart' WHERE nom='Ricard'") ma_connexion.commit() Pour supprimer un ou plusieurs enregistrements, utilisez une requête telle que : mon_curseur.execute("DELETE FROM membres WHERE nom='Gerart'") ma_connexion.commit() Attention : Notez la présence de l'instruction commit : Toutes les modifications apportées au curseur se passent en mémoire vive, et de ce fait rien n'est enregistré définitivement tant que vous n'exécutez pas l'instruction ma_connexion.commit(). Si vous fermez prématurément la connexion avec l'instruction ma_connexion.close()les modifications seront perdues. Si l'on souhaite annuler toutes les modifications apportées depuis le commit() précédent, il faudra avoir recours à l'instruction : ma_connexion.rollback() 18-A-6. Recherches sélectives dans une base de données▲ 93 9.6 Création du schéma d'une base de données depuis un programme Python La création d’une nouvelle table dans notre base de données se fait par exemple par l’instruction cidessous : mon_curseur.execute("CREATE TABLE membres (age INTEGER, nom TEXT, taille REAL)") Ici nous avons créé une table nommée membres contenant trois champs : age de type entier, nom de type chaîne de caractères et taille de type réel. 94 Séances 8 & 9 10 Projet Les deux dernières séances de ce module seront consacrées à la réalisation d'un projet en binôme dont l'énoncé et les consignes vous seront communiqués en temps utiles. 95