IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Apprendre à programmer avec Python

Apprendre programmer avec Python


prcdentsommairesuivant

Chapitre 16 : Gestion d'une base de donnes

Les bases de donnes sont des outils de plus en plus frquemment utiliss. Elles permettent de stocker des donnes nombreuses dans un seul ensemble bien structur. Lorsqu'il s'agit de bases de donnes relationnelles, il devient en outre tout fait possible d'viter l' enfer des doublons . Vous avez srement t dj confronts ce problme :

Des donnes identiques ont t enregistres dans plusieurs fichiers diffrents. Lorsque vous souhaitez modifier ou supprimer l'une de ces donnes, vous devez ouvrir et modifier tous les fichiers qui la contiennent ! Le risque d'erreur est trs rel, qui conduit invitablement des incohrences, sans compter la perte de temps que cela reprsente.

Les bases de donnes constituent la solution ce type de problme. Python vous permet d'en utiliser de nombreux systmes, mais nous n'en examinerons que deux dans nos exemples : Gadfly et MySQL.

16.1. Les bases de donnes

Il existe de nombreux types de bases de donnes. On peut par exemple dj considrer comme une base de donnes lmentaire, un fichier qui contient une liste de noms et d'adresses.

Si la liste n'est pas trop longue, et si l'on ne souhaite pas pouvoir y effectuer des recherches en fonction de critres complexes, il va de soi que l'on peut accder ce type de donnes en utilisant des instructions simples, telles celles que nous avons abordes page 108.

La situation se complique cependant trs vite si l'on souhaite pouvoir effectuer des slections et des tris parmi les donnes, surtout si celles-ci deviennent trs nombreuses. La difficult augmente encore si les donnes sont rpertories dans diffrents ensembles relis par un certain nombre de relations hirarchiques, et si plusieurs utilisateurs doivent pouvoir y accder en parallle.

Imaginez par exemple que la direction de votre cole vous confie la charge de mettre au point un systme de bulletins informatis. En y rflchissant quelque peu, vous vous rendrez compte rapidement que cela suppose la mise en oeuvre de toute une srie de tables diffrentes : une table des noms d'lves (laquelle pourra bien entendu contenir aussi d'autres informations spcifiques ces lves : adresse, date de naissance, etc.) ; une table contenant la liste des cours (avec le nom du professeur titulaire, le nombre d'heures enseignes par semaine, etc.) ; une table mmorisant les travaux pris en compte pour l'valuation (avec leur importance, leur date, leur contenu, etc.) ; une table dcrivant la manire dont les lves sont groups par classes ou par options, les cours suivis par chacun, etc., etc.

Vous comprenez bien que ces diffrentes tables ne sont pas indpendantes. Les travaux effectus par un mme lve sont lis des cours diffrents. Pour tablir le bulletin de cet lve, il faut donc extraire des donnes de la table des travaux, bien sr, mais en relation avec des informations trouves dans d'autres tables (celles des cours, des classes, des options, etc.)

Nous verrons plus loin comment reprsenter des tables de donnes et les relations qui les lient.

16.1.1. SGBDR - Le modle client/serveur

Les programmes informatiques capables de grer efficacement de tels ensembles de donnes complexes sont forcment complexes, eux aussi. On appelle ces programmes des SGBDR (Systmes de Gestion de Bases de Donnes Relationnelles). Il s'agit d'applications informatiques de premire importance pour les entreprises. Certaines sont les fleurons de socits spcialises (IBM, Oracle, Microsoft, Informix, Sybase...) et sont en gnral vendues des prix fort levs. D'autres ont t dveloppes dans des centres de recherche et d'enseignement universitaires (PostgreSQL, MySQL ...); elles sont alors en gnral tout fait gratuites.

Ces systmes ont chacun leurs spcificits et leurs performances, mais la plupart fonctionnant sur le modle client/serveur : cela signifie que la plus grosse partie de l'application (ainsi que la base de donnes prise en charge) est installe en un seul endroit, en principe sur une machine puissante (cet ensemble constituant donc le serveur), alors que l'autre partie, beaucoup plus simple, est installe sur un nombre indtermin de postes de travail, et on appelle celles-ci des clients.

Les clients sont relis au serveur, en permanence ou non, par divers procds et protocoles (ventuellement par l'intermdiaire de l'internet). Chacun d'entre eux peut accder une partie plus ou moins importante des donnes, avec autorisation ou non de modifier certaines d'entre elles, d'en ajouter ou d'en supprimer, en fonction de rgles d'accs bien dtermines. (Ces rgles sont dfinies par un administrateur de la base de donnes).

Le serveur et ses clients sont en fait des applications distinctes qui s'changent des informations. Imaginez par exemple que vous tes l'un des utilisateurs du systme. Pour accder aux donnes, vous devez lancer l'excution d'une application cliente sur un poste de travail quelconque. Dans son processus de dmarrage, l'application cliente commence par tablir la connexion avec le serveur et la base de donnes61. Lorsque la connexion est tablie, l'application cliente peut interroger le serveur en lui envoyant une requte sous une forme convenue. Il s'agit par exemple de retrouver une information prcise. Le serveur excute alors la requte en recherchant les donnes correspondantes dans la base, puis il expdie en retour une certaine rponse au client.

Cette rponse peut tre l'information demande, ou encore un message d'erreur en cas d'insuccs.

La communication entre le client et le serveur est donc faite de requtes et de rponses. Les requtes sont de vritables instructions expdies du client au serveur, non seulement pour extraire des donnes de la base, mais aussi pour en ajouter, en supprimer, en modifier, etc.

61 il vous faudra certainement entrer quelques informations pour obtenir l'accs : adresse du serveur sur le rseau, nom de la base de donnes, nom d'utilisateur, mot de passe, ...

16.1.2. Le langage SQL - Gadfly

tant donne la diversit des SGBDR existants, on pourrait craindre que chacun d'eux ncessite l'utilisation d'un langage particulier pour les requtes qu'on lui adresse. En fait, de grands efforts ont t accomplis un peu partout pour la mise au point d'un langage commun, et il existe prsent un standard bien tabli : SQL (Structured Query Language, ou langage de requtes structur)62.

Vous aurez probablement l'occasion de rencontrer SQL dans d'autres domaines (bureautique, par exemple). Dans le cadre de cette introduction l'apprentissage de la programmation avec Python, nous allons nous limiter la prsentation de deux exemples : la mise en oeuvre d'un petit SGBDR ralis exclusivement l'aide de Python, et l'bauche d'un logiciel client plus ambitieux destin communiquer avec un serveur de bases de donnes MySQL.

Notre premire ralisation utilisera un module nomm Gadfly. Entirement crit en Python, ce module ne fait pas partie de la distribution standard et doit donc tre install sparment63. Il intgre un large sous-ensemble de commandes SQL. Ses performances ne sont videmment pas comparables celles d'un gros SGBDR spcialis64, mais elles sont tout fait excellentes pour la gestion de bases de donnes modestes. Absolument portable comme Python lui-mme, Gadfly fonctionnera indiffremment sous Windows , Linux ou MacOS. De mme, les rpertoires contenant des bases de donnes produites sous Gadfly pourront tre utilises sans modification depuis l'un ou l'autre de ces systmes.

Si vous souhaitez dvelopper une application qui doit grer des relations relativement complexes dans une petite base de donnes, le module Gadfly peut vous faciliter grandement la tche.

62 Quelques variantes subsistent entre diffrentes implmentations du SQL, pour des requtes trs spcifiques, mais la base reste cependant la mme.

63 Le module Gadfly est disponible gratuitement sur l'internet. Voir http://sourceforge.net/projects/gadfly
L'installation de ce module est dcrite dans l'annexe 17.6 , page 306.

64 Gadfly se rvle relativement efficace pour la gestion de bases de donnes de taille moyenne, en mode monoutilisateur. Pour grer de grosses bases de donnes en mode multi-utilisateur, il faut faire appel des SGDBR plus ambitieux tels que PostgreSQL, pour lesquels des modules clients Python existent aussi (Pygresql, par ex.).

16.2. Mise en oeuvre d'une base de donnes simple avec Gadfly

Nous allons ci-aprs examiner comment mettre en place une application simple, qui fasse office la fois de serveur et de client sur la mme machine.

16.2.1. Cration de la base de donnes

Comme vous vous y attendez certainement, il suffit d'importer le module gadfly pour accder aux fonctionnalits correspondantes. Vous devez ensuite crer une instance (un objet) de la classe gadfly :

 
Sélectionnez

import gadfly
baseDonn = gadfly.gadfly()

L'objet baseDonn ainsi cr est votre moteur de base de donnes local, lequel effectuera la plupart de ses oprations en mmoire vive. Ceci permet une excution trs rapide des requtes.

Pour crer la base de donnes proprement dite, il faut employer la mthode startup de cet objet :

 
Sélectionnez

baseDonn.startup("mydata","E:/Python/essais/gadfly")

Le premier paramtre transmis, mydata, est le nom choisi pour la base de donnes (vous pouvez videmment choisir un autre nom !). Le second paramtre est le rpertoire o l'on souhaite installer cette base de donnes. (Ce rpertoire doit avoir t cr au pralable, et toute base de donnes de mme nom qui prexisterait dans ce rpertoire est crase sans avertissement).

Les trois lignes de code que vous venez d'entrer sont suffisantes : vous disposez ds prsent d'une base de donnes fonctionnelle, dans laquelle vous pouvez crer diffrentes tables, puis ajouter, supprimer ou modifier des donnes dans ces tables.

Pour toutes ces oprations, vous allez utiliser le langage SQL.

Afin de pouvoir transmettre vos requtes SQL l'objet baseDonn , vous devez cependant mettre en oeuvre un curseur. Il s'agit d'une sorte de tampon mmoire intermdiaire, destin mmoriser temporairement les donnes en cours de traitement, ainsi que les oprations que vous effectuez sur elles, avant leur transfert dfinitif dans de vrais fichiers. Cette technique permet donc d'annuler si ncessaire une ou plusieurs oprations qui se seraient rvles inadquates (Vous pouvez en apprendre davantage sur ce concept en consultant l'un des nombreux manuels qui traitent du langage SQL).

Veuillez prsent examiner le petit script ci-dessous, et noter que les requtes SQL sont des chanes de caractres, prises en charge par la mthode execute de l'objet curseur :

 
Sélectionnez

cur = baseDonn.cursor()
cur.execute("create table membres (age integer, nom varchar, taille float)")
cur.execute("insert into membres(age, nom, taille) values (21,'Dupont',1.83)")
cur.execute("INSERT INTO MEMBRES(AGE, NOM, TAILLE) VALUES (15,'Suleau',1.57)")
cur.execute("Insert Into Membres(Age, Nom, Taille) Values (18,'Forcas',1.69)")
baseDonn.commit()

La premire des lignes ci-dessus cre l'objet curseur cur. Les chanes de caractres comprises entre guillemets dans les 4 lignes suivantes contiennent des requtes SQL trs classiques. Notez bien que le langage SQL ne tient aucun compte de la casse des caractres : vous pouvez encoder vos requtes SQL indiffremment en majuscules ou en minuscules (ce qui n'est pas le cas pour les instructions Python environnantes, bien entendu !)

La seconde ligne cre une table nomme membres, laquelle contiendra des enregistrements de 3 champs : le champ age de type nombre entier , le champ nom de type chane de caractres (de longueur variable65) et le champ taille, de type nombre rel ( virgule flottante). Le langage SQL autorise en principe d'autres types, mais ils ne sont pas implments dans Gadfly.

Les trois lignes qui suivent sont similaires. Nous y avons mlang majuscules et minuscules pour bien montrer que la casse n'est pas significative en SQL. Ces lignes servent insrer trois enregistrements dans la table membres.

A ce stade des oprations, les enregistrement n'ont pas encore t transfrs dans de vritables fichiers sur disque. Il est donc possible de revenir en arrire, comme nous le verrons un peu plus loin. Le transfert sur disque est activ par la mthode commit() de la dernire ligne d'instructions.

65 Veuillez noter qu'en SQL, les chanes de caractres doivent tre dlimites par des apostrophes. Si vous souhaitez que la chane contienne elle-mme une ou plusieurs apostrophes, il vous suffit de doubler celles-ci.

16.2.2. Connexion une base de donnes existante

Supposons qu' la suite des oprations ci-dessus, nous dcidions de terminer le script, ou mme d'teindre l'ordinateur. Comment devrons-nous procder par la suite pour accder nouveau notre base de donnes ?

L'accs une base de donnes existante ne ncessite que deux lignes de code :

 
Sélectionnez

import gadfly
baseDonn = gadfly.gadfly("mydata","E:/Python/essais/gadfly")

Ces deux lignes suffisent en effet pour transfrer en mmoire vive les tables contenues dans les fichiers enregistrs sur disque. La base de donnes peut dsormais tre interroge et modifie :

 
Sélectionnez

cur = baseDonn.cursor()
cur.execute("select * from membres")
print cur.pp()

La premire de ces trois lignes ouvre un curseur. La requte mise dans la seconde ligne demande la slection d'un ensemble d'enregistrements, qui seront transfrs de la base de donnes au curseur. Dans le cas prsent, la slection n'en n'est pas vraiment une : on y demande en effet d'extraire tous les enregistrements de la table membres (le symbole * est frquemment utilis en informatique avec la signification tout ou tous ).

La mthode pp() utilise sur le curseur, dans la troisime ligne, provoque un affichage de tout ce qui est contenu dans le curseur sous une forme pr-formate (les donnes prsentes sont automatiquement disposes en colonnes). pp doit en effet tre compris comme pretty print .

Si vous prfrez contrler vous-mme la mise en page des informations, il vous suffit d'utiliser sa place la mthode fetchall() , laquelle renvoie une liste de tuples. Essayez par exemple :

 
Sélectionnez

for x in cur.fetchall():
    print x, x[0], x[1], x[2]

Vous pouvez bien entendu ajouter des enregistrements supplmentaires :

 
Sélectionnez

cur.execute("Insert Into Membres(Age, Nom, Taille) Values (19,'Ricard',1.75)")

Pour modifier un ou plusieurs enregistrements, excutez une requte du type :

 
Sélectionnez

cur.execute("update membres set nom ='Gerart' where nom='Ricard'")

Pour supprimer un ou plusieurs enregistrements, utilisez une requte telle que :

 
Sélectionnez

cur.execute("delete from membres where nom='Gerart'")

Si vous effectuez toutes ces oprations la ligne de commande de Python, vous pouvez en observer le rsultat tout moment en effectuant un pretty print comme expliqu plus haut. tant donn que toutes les modifications apportes au curseur se passent en mmoire vive, rien n'est enregistr dfinitivement tant que vous n'excutez pas l'instruction baseDonn.commit().

Vous pouvez donc annuler toutes les modifications apportes depuis le commit() prcdent, en refermant la connexion l'aide de l'instruction :

 
Sélectionnez

baseDonn.close()

16.2.3. Recherches dans une base de donnes

(16) Exercice

16.1. Avant d'aller plus loin, et titre d'exercice de synthse, nous allons vous demander de crer entirement vous-mme une base de donnes Musique qui contiendra les deux tables suivantes (Cela reprsente un certain travail, mais il faut que vous puissiez disposer d'un certain nombre de donnes pour pouvoir exprimenter les fonctions de recherche et de tri) :

oeuvres
comp (chane)
titre (chane)
duree (entier)
interpr (chane)
Compositeurs
comp (chane)
a_naiss (entier)
a_mort (entier)

Commencez remplir la table Compositeurs avec les donnes qui suivent (... et profitez de cette occasion pour faire la preuve des comptences que vous matrisez dj, en crivant un petit script pour vous faciliter l'entre des informations : une boucle s'impose !)

 
Sélectionnez

comp        a_naiss     a_mort
Mozart      1756        1791
Beethoven   1770        1827
Handel      1685        1759
Schubert    1797        1828
Vivaldi     1678        1741
Monteverdi  1567        1643
Chopin      1810        1849		
Bach        1685        1750

Dans la table oeuvres, entrez les donnes suivantes :

 
Sélectionnez

comp        titre                       duree   interpr
Vivaldi     Les quatre saisons          20      T. Pinnock
Mozart      Concerto piano N12         25      M. Perahia
Brahms      Concerto violon N2         40      A. Grumiaux	
Beethoven   Sonate "au clair de lune"   14      W. Kempf
Beethoven   Sonate "pathtique"         17      W. Kempf
Schubert    Quintette "la truite"       39      SE of London
Haydn       La cration                 109     H. Von Karajan
Chopin      Concerto piano N1          42      M.J. Pires
Bach        Toccata & fugue             9       P. Burmester
Beethoven   Concerto piano N4          33      M. Pollini
Mozart      Symphonie N40              29      F. Bruggen
Mozart      Concerto piano N22         35      S. Richter
Beethoven   Concerto piano N3          37      S. Richter

Les champs a_naiss et a_mort contiennent respectivement l'anne de naissance et l'anne de la mort des compositeurs. La dure des oeuvres est fournie en minutes. Vous pouvez videmment ajouter autant d'enregistrements d'oeuvres et de compositeurs que vous le voulez, mais ceux qui prcdent devraient suffire pour la suite de la dmonstration.

Pour ce qui va suivre, nous supposerons donc que vous avez effectivement encod les donnes des deux tables dcrites ci-dessus. (Si vous prouvez des difficults crire le script ncessaire, nous en donnons un exemple dans les annexes de ces notes, la page 352).

Le petit script ci-dessous est fourni titre purement indicatif. Il s'agit d'un client SQL rudimentaire, qui vous permet de vous connecter la base de donnes musique qui devrait prsent exister dans l'un de vos rpertoires, d'y ouvrir un curseur et d'utiliser celui-ci pour effectuer des requtes. Notez encore une fois que rien n'est transcrit sur le disque tant que la mthode commit() n'a pas t invoque.

 
Sélectionnez

# Utilisation d'une petite base de donnes acceptant les requtes SQL
 
import gadfly
 
baseDonn = gadfly.gadfly("musique","E:/Python/essais/gadfly")
cur = baseDonn.cursor()
while 1:
    print "Veuillez entrer votre requte SQL (ou <Enter> pour terminer) :"
    requete = raw_input()
    if requete =="":
        break
    try:
        cur.execute(requete)        # tentative d'excution de la requte SQL
    except:
        print '*** Requte incorrecte ***'
    else:    
        print cur.pp()              # affichage du rsultat de la requte
    print
 
choix = raw_input("Confirmez-vous l'enregistrement (o/n) ? ")
if choix[0] == "o" or choix[0] == "O":
    baseDonn.commit()
else:
    baseDonn.close()

Cette application trs simple n'est videmment qu'un exemple. Il faudrait y ajouter la possibilit de choisir la base de donnes ainsi que le rpertoire de travail. Pour viter que le script ne se plante lorsque l'utilisateur encode une requte incorrecte, nous avons utilis ici le traitement des exceptions dj dcrit la page 118.

16.2.4. La requte select

L'une des instructions les plus puissantes du langage SQL est l'instruction select, dont nous allons prsent explorer quelques fonctionnalits. Rappelons encore une fois que nous n'abordons ici qu'une trs petite partie du sujet : la description dtaille de SQL peut occuper plusieurs livres.

Lancez donc le script ci-dessus, et analysez attentivement ce qui se passe lorsque vous proposez les requtes suivantes :

 
Sélectionnez

select * from oeuvres
select * from oeuvres where comp = 'Mozart'
select comp, titre, duree from oeuvres order by comp
select titre, comp from oeuvres where comp='Beethoven' or comp='Mozart' order by comp
select count(*) from oeuvres
select sum(duree) from oeuvres
select avg(duree) from oeuvres
select sum(duree) from oeuvres where comp='Beethoven'
select * from oeuvres where duree >35 order by duree desc

Pour chacune de ces requtes, tchez d'exprimer le mieux possible ce qui se passe. Fondamentalement, vous activez sur la base de donnes des filtres de slection et des tris.

Les requtes suivantes sont plus labores, car elles concernent les deux tables la fois.

 
Sélectionnez

select o.titre, c.nom, c.a_naiss from oeuvres o, compositeurs c where o.comp = c.comp
select comp from oeuvres intersect select comp from compositeurs
select comp from oeuvres except select comp from compositeurs
select comp from compositeurs except select comp from oeuvres
select distinct comp from oeuvres union select comp from compositeurs

Il ne nous est pas possible de dvelopper davantage le langage de requtes dans le cadre restreint de ces notes. Nous allons cependant examiner encore un exemple de ralisation Python faisant appel un systme de bases de donnes, mais en supposant cette fois qu'il s'agisse de dialoguer avec un systme serveur indpendant (lequel pourrait tre par exemple un gros serveur de bases de donnes d'entreprise, un serveur de documentation dans une cole, etc.).

16.3. bauche d'un logiciel client pour MySQL

Pour terminer ce chapitre, nous allons vous proposer dans les pages qui suivent un exemple de ralisation concrte. Il ne s'agira pas d'un vritable logiciel (le sujet exigerait qu'on lui consacre un ouvrage spcifique), mais plutt d'une bauche d'analyse, destine vous montrer comment vous pouvez penser comme un programmeur lorsque vous abordez un problme complexe.

Les techniques que nous allons mettre en oeuvre ici sont de simples suggestions, dans lesquelles nous essayerons d'utiliser au mieux les outils que vous avez dcouverts au cours de votre apprentissage dans les chapitres prcdents, savoir : les structures de donnes de haut niveau (listes et dictionnaires), et la programmation par objets. Il va de soi que les options retenues dans cet exercice restent largement critiquables : vous pouvez bien videmment traiter les mmes problmes en utilisant des approches diffrentes.

Notre objectif concret est d'arriver raliser rapidement un client rudimentaire, capable de dialoguer avec un vrai serveur de bases de donnes tel que MySQL. Nous voudrions que notre client reste un petit utilitaire trs gnraliste : qu'il soit capable de mettre en place une petite base de donnes comportant plusieurs tables, qu'il puisse servir produire des enregistrements pour chacune d'elles, qu'il permette de tester le rsultat de requtes SQL basiques.

Dans les lignes qui suivent, nous supposerons que vous avez dj accs un serveur MySQL, sur lequel une base de donnes discotheque aura t cre pour l'utilisateur jules , lequel s'identifie l'aide du mot de passe abcde . Ce serveur peut tre situ sur une machine distante accessible via un rseau, ou localement sur votre ordinateur personnel66.

66 L'installation et la configuration d'un serveur MySQL sortent du cadre de cet ouvrage, mais ce n'est pas une tche bien complique. C'est mme fort simple si vous travaillez sous Linux, install depuis une distribution classique telle que Debian, RedHat, SuSE ou Mandrake. Il vous suffit d'installer les paquetages MySQL-server et Python-MySQL, de dmarrer le service MySQL, puis d'entrer les commandes :

mysqladmin -u root password xxxx

Cette premire commande dfinit le mot de passe de l'administrateur principal de MySQL. Elle doit tre excute par l'administrateur du systme Linux ('root'), avec un mot de passe de votre choix. On se connecte ensuite au serveur sous le compte administrateur ainsi dfini (le mot de passe sera demand) :

mysql -u root mysql -p
grant all privileges on *.* to jules@localhost identified by 'abcde';
grant all privileges on *.* to jules@"%" identified by 'abcde';
\q

Ces commandes dfinissent un nouvel utilisateur jules pour le systme MySQL, et cet utilisateur devra se connecter le mot de passe abcde (Les deux lignes autorisent respectivement l'accs local et l'accs via rseau).
Le nom d'utilisateur est quelconque : il ne doit pas ncessairement correspondre un utilisateur systme.
L'utilisateur jules peut prsent se connecter et crer des bases de donnes :

mysql -u jules -p
create database discotheque;
\q


... etc. ce stade, le serveur MySQL est prt dialoguer avec le client Python dcrit dans ces pages.

16.3.1. Dcrire la base de donnes dans un dictionnaire d'application

Une application dialoguant avec une base de donnes est presque toujours une application complexe. Elle comporte donc de nombreuses lignes de code, qu'il s'agit de structurer le mieux possible en les regroupant dans des classes (ou au moins des fonctions) bien encapsules.

En de nombreux endroits du code, souvent fort loigns les uns des autres, des blocs d'instructions doivent prendre en compte la structure de la base de donnes, c'est--dire son dcoupage en un certain nombre de tables et de champs, ainsi que les relations qui tablissent une hirarchie dans les enregistrements.

Or, l'exprience montre que la structure d'une base de donnes est rarement dfinitive. Au cours d'un dveloppement, on ralise souvent qu'il est ncessaire de lui ajouter ou de lui retirer des champs, parfois mme de remplacer une table mal conue par deux autres, etc. Il n'est donc pas prudent de programmer des portions de code trop spcifiques d'une structure particulire, en dur .

Au contraire, il est hautement recommandable de dcrire plutt la structure complte de la base de donnes en un seul endroit du programme, et d'utiliser ensuite cette description comme rfrence pour la gnration semi-automatique des instructions particulires concernant telle table ou tel champ. On vite ainsi, dans une large mesure, le cauchemar de devoir traquer et modifier un grand nombre d'instructions un peu partout dans le code, chaque fois que la structure de la base de donnes change un tant soit peu. Au lieu de cela, il suffit de changer seulement la description de rfrence, et la plus grosse partie du code reste correcte sans ncessiter de modification.
Nous tenons l une ide matresse pour raliser des applications robustes :

Un logiciel destin au traitement de donnes devrait toujours tre construit sur la base d'un dictionnaire d'application.

Ce que nous entendons ici par dictionnaire d'application ne doit pas ncessairement revtir la forme d'un dictionnaire Python. N'importe quelle structure de donnes peut convenir, l'essentiel tant de se construire une rfrence centrale dcrivant les donnes que l'on se propose de manipuler, avec peut-tre aussi un certain nombre d'informations concernant leur mise en forme.

Du fait de leur capacit rassembler en une mme entit des donnes de n'importe quel type, les listes, tuples et dictionnaires de Python conviennent parfaitement pour ce travail. Dans l'exemple des pages suivantes, nous avons utilis nous-mmes un dictionnaire, dont les valeurs sont des listes de tuples, mais vous pourriez tout aussi bien opter pour une organisation diffrente des mmes informations.

Tout cela tant bien tabli, il nous reste encore rgler une question d'importance : o allonsnous installer concrtement ce dictionnaire d'application ?

Ses informations devront pouvoir tre consultes depuis n'importe quel endroit du programme. Il semble donc obligatoire de l'installer dans une variable globale, de mme d'ailleurs que d'autres donnes ncessaires au fonctionnement de l'ensemble de notre logiciel. Or vous savez que l'utilisation de variables globales n'est pas recommande : elle comporte des risques, qui augmentent avec la taille du programme. De toute faon, les variables dites globales ne sont en fait globales qu' l'intrieur d'un mme module. Si nous souhaitons organiser notre logiciel comme un ensemble de modules (ce qui constitue par ailleurs une excellente pratique), nous n'aurons accs nos variables globales que dans un seul d'entre eux.

Pour rsoudre ce petit problme, il existe cependant une solution simple et lgante : regrouper dans une classe particulire toutes les variables qui ncessitent un statut global pour l'ensemble de l'application. Ainsi encapsules dans l'espace de noms d'une classe, ces variables peuvent tre utilises sans problme dans n'importe quel module : il suffit en effet que celui-ci importe la classe en question. De plus, l'utilisation de cette technique entrane une consquence intressante : le caractre global des variables dfinies de cette manire apparat trs clairement dans leur nom qualifi, puisque ce nom commence par celui de la classe contenante.

Si vous choisissez, par exemple, un nom explicite tel que Glob pour la classe destine accueillir vos variables globales , vous vous assurez de devoir faire rfrence ces variables partout dans votre code avec des noms tout aussi explicites tels que Glob.ceci , Glob.cela , etc67.

C'est cette technique que vous allez dcouvrir prsent dans les premires lignes de notre script. Nous y dfinissons effectivement une classe Glob(), qui n'est donc rien d'autre qu'un simple conteneur. Aucun objet ne sera instanci partir de celle classe, laquelle ne comporte d'ailleurs aucune mthode. Nos variables globales y sont dfinies comme de simples variables de classe, et nous pourrons donc y faire rfrence dans tout le reste du programme en tant qu'attributs de Glob. Le nom de la base de donnes, par exemple, pourra tre retrouv partout dans la variable Glob.dbName ; le nom ou l'adresse IP du serveur dans la variable Glob.host, etc. :

 
Sélectionnez

1. class Glob:
2.     """Espace de noms pour les variables et fonctions <pseudo-globales>"""
3.
4.     dbName = "discotheque"      # nom de la base de donnes
5.     user = "jules"              # propritaire ou utilisateur
6.     passwd = "abcde"            # mot de passe d'accs
7.     host = "192.168.0.235"      # nom ou adresse IP du serveur
8.
9.     # Structure de la base de donnes.  Dictionnaire des tables & champs :
10.    dicoT ={"compositeurs":[('id_comp', "k", "cl primaire"),
11.                            ('nom', 25, "nom"),
12.                            ('prenom', 25, "prnom"),
13.                            ('a_naiss', "i", "anne de naissance"),
14.                            ('a_mort', "i", "anne de mort")],
15.            "oeuvres":[('id_oeuv', "k", "cl primaire"),
16.                       ('id_comp', "i", "cl compositeur"),
17.                       ('titre', 50, "titre de l'oeuvre"),
18.                       ('duree', "i", "dure (en minutes)"),
19.                       ('interpr', 30, "interprte principal")]}

Le dictionnaire d'application dcrivant la structure de la base de donnes est contenu dans la variable Glob.dicoT.

Il s'agit d'un dictionnaire Python, dont les cls sont les noms des tables. Quant aux valeurs, chacune d'elles est une liste contenant la description de tous les champs de la table, sous la forme d'autant de tuples.

Chaque tuple dcrit donc un champ particulier de la table. Pour ne pas encombrer notre exercice, nous avons limit cette description trois informations seulement : le nom du champ, son type et un bref commentaire. Dans une vritable application, il serait judicieux d'ajouter encore d'autres informations ici, concernant par exemple des valeurs limites ventuelles pour les donnes de ce champ, le formatage leur appliquer lorsqu'il s'agit de les afficher l'cran ou de les imprimer, le texte qu'il faut placer en haut de colonne lorsque l'on veut les prsenter dans un tableau, etc.

Il peut vous paratre assez fastidieux de dcrire ainsi trs en dtail la structure de vos donnes, alors que vous voudriez probablement commencer tout de suite une rflexion sur les divers algorithmes mettre en oeuvre afin de les traiter. Sachez cependant que si elle est bien faite, une telle description structure vous fera certainement gagner beaucoup de temps par la suite, parce qu'elle vous permettra d'automatiser pas mal de choses. Vous en verrez une dmonstration un peu plus loin. En outre, vous devez vous convaincre que cette tche un peu ingrate vous prpare bien structurer aussi le reste de votre travail : organisation des formulaires, tests effectuer, etc.

67 Vous pourriez galement placer vos variables globales dans un module nomm Glob.py, puis importer celui-ci. Utiliser un module ou une classe comme espace de noms pour stocker des variables sont donc des techniques assez similaires. L'utilisation d'une classe est peut-tre un peu plus souple et plus lisible, puisque la classe peut accompagner le reste du script, alors qu'un module est ncessairement un fichier distinct.

16.3.2. Dfinir une classe d'objets-interfaces

La classe Glob() dcrite la rubrique prcdente sera donc installe en dbut de script, ou bien dans un module spar import en dbut de script. Pour la suite de l'expos, nous supposerons que c'est cette dernire formule qui est retenue : nous avons sauvegard la classe Glob() dans un module nomm dict_app.py, d'o nous pouvons prsent l'importer dans le script suivant.

Ce nouveau script dfinit une classe d'objets-interfaces. Nous voulons en effet essayer de mettre profit ce que nous avons appris dans les chapitres prcdents, et donc privilgier la programmation par objets, afin de crer des portions de code bien encapsules et largement rutilisables.

Les objets-interfaces que nous voulons construire seront similaires aux objets-fichiers que nous avons abondamment utiliss pour la gestion des fichiers au chapitre 9. Vous vous rappelez par exemple que nous ouvrons un fichier en crant un objet-fichier, l'aide de la fonction-fabrique open(). D'une manire similaire, nous ouvrirons la communication avec la base de donnes en commenant par crer un objet-interface l'aide de la classe GestionBD(), ce qui tablira la connexion. Pour lire ou crire dans un fichier ouvert, nous utilisons diverses mthodes de l'objetfichier. D'une manire analogue, nous effectuerons nos oprations sur la base de donnes par l'intermdiaire des diverses mthodes de l'objet-interface.

 
Sélectionnez

1. import MySQLdb, sys
2. from dict_app import *
3.
4. class GestionBD:
5.     """Mise en place et interfaage d'une base de donnes MySQL"""
6.     def __init__(self, dbName, user, passwd, host, port =3306):
7.         "tablissement de la connexion - Cration du curseur"
8.         try:
9.             self.baseDonn = MySQLdb.connect(db =dbName,
10.                  user =user, passwd =passwd, host =host, port =port)
11.        except Exception, err:
12.            print 'La connexion avec la base de donnes a chou :\n'\
13.                  'Erreur dtecte :\n%s' % err
14.            self.echec =1
15.        else:    
16.            self.cursor = self.baseDonn.cursor()   # cration du curseur
17.            self.echec =0
18.
19.    def creerTables(self, dicTables):
20.        "Cration des tables dcrites dans le dictionnaire <dicTables>."
21.        for table in dicTables:            # parcours des cls du dict.
22.            req = "CREATE TABLE %s (" % table
23.            pk =''
24.            for descr in dicTables[table]:
25.                nomChamp = descr[0]        # libell du champ  crer
26.                tch = descr[1]             # type de champ  crer
27.                if tch =='i':
28.                    typeChamp ='INTEGER'
29.                elif tch =='k':
30.                    # champ 'cl primaire' (incrment automatiquement)
31.                    typeChamp ='INTEGER AUTO_INCREMENT'   
32.                    pk = nomChamp
33.                else:
34.                    typeChamp ='VARCHAR(%s)' % tch                
35.                req = req + "%s %s, " % (nomChamp, typeChamp)
36.            if pk == '':
37.                req = req[:-2] + ")"
38.            else:
39.                req = req + "CONSTRAINT %s_pk PRIMARY KEY(%s))" % (pk, pk)
40.            self.executerReq(req)
41.
42.    def supprimerTables(self, dicTables):
43.        "Suppression de toutes les tables dcrites dans <dicTables>"
44.        for table in dicTables.keys():
45.            req ="DROP TABLE %s" % table
46.            self.executerReq(req) 
47.        self.commit()                       # transfert -> disque
48.
49.    def executerReq(self, req):
50.        "Excution de la requte <req>, avec dtection d'erreur ventuelle"
51.        try:
52.            self.cursor.execute(req)
53.        except Exception, err:
54.            # afficher la requte et le message d'erreur systme :
55.            print "Requte SQL incorrecte :\n%s\nErreur dtecte :\n%s"\
56.                   % (req, err)
57.            return 0
58.        else:
59.            return 1
60.
61.    def resultatReq(self):
62.        "renvoie le rsultat de la requte prcdente (un tuple de tuples)"
63.        return self.cursor.fetchall()
64.
65.    def commit(self):
66.        if self.baseDonn:
67.            self.baseDonn.commit()         # transfert curseur -> disque        
68.
69.    def close(self):
70.        if self.baseDonn:
71.            self.baseDonn.close()

Commentaires :

  • Lignes 1-2 : Outre notre propre module dict_app qui contient les variables globales , nous importons le module sys qui contient quelques fonctions systme, et le module MySQLdb qui contient tout ce qui est ncessaire pour communiquer avec MySQL. Rappelons que ce module ne fait pas partie de la distribution standard de Python, et qu'il doit donc tre install sparment.
  • Ligne 5 : Lors de la cration des objets-interfaces, nous devrons fournir les paramtres de la connexion : nom de la base de donnes, nom de son utilisateur, nom ou adresse IP de la machine o est situ le serveur. Le n du port de communication est habituellement celui que nous avons prvu par dfaut. Toutes ces informations sont supposes tre en votre possession.
  • Lignes 8 17 : Il est hautement recommandable de placer le code servant tablir la connexion l'intrieur d'un gestionnaire d'exceptions try-except-else (voir page 118), car nous ne pouvons pas prsumer que le serveur sera ncessairement accessible. Remarquons au passage que la mthode __init__() ne peut pas renvoyer de valeur ( l'aide de l'instruction return), du fait qu'elle est invoque automatiquement par Python lors de l'instanciation d'un objet. En effet : ce qui est renvoy dans ce cas au programme appelant est l'objet nouvellement construit. Nous ne pouvons donc pas signaler la russite ou l'chec de la connexion au programme appelant l'aide d'une valeur de retour. Une solution simple ce petit problme consiste mmoriser le rsultat de la tentative de connexion dans un attribut d'instance (variable self.echec), que le programme appelant peut ensuite tester quand bon lui semble.
  • Lignes 19 40 : Cette mthode automatise la cration de toutes les tables de la base de donnes, en tirant profit de la description du dictionnaire d'application, lequel doit lui tre transmis en argument. Une telle automatisation sera videmment d'autant plus apprciable, que la structure de la base de donnes sera plus complexe (Imaginez par exemple une base de donnes contenant 35 tables !). Afin de ne pas alourdir la dmonstration, nous avons restreint les capacits de cette mthode la cration de champs des types integer et varchar . Libre vous d'ajouter les instructions ncessaires pour crer des champs d'autres types.

    Si vous dtaillez le code, vous constaterez qu'il consiste simplement construire une requte SQL pour chaque table, morceau par morceau, dans la chane de caractres req. Celle-ci est ensuite transmise la mthode executerReq() pour excution. Si vous souhaitez visualiser la requte ainsi construite, vous pouvez videmment ajouter une instruction print req juste aprs la ligne 40.

    Vous pouvez galement ajouter cette mthode la capacit de mettre en place les contraintes d'intgrit rfrentielle, sur la base d'un complment au dictionnaire d'application qui dcrirait ces contraintes. Nous ne dveloppons pas cette question ici, mais cela ne devrait pas vous poser de problme si vous savez de quoi il retourne.
  • Lignes 42 47 : Beaucoup plus simple que la prcdente, cette mthode utilise le mme principe pour supprimer toutes les tables dcrites dans le dictionnaire d'application.
  • Lignes 49 59 : Cette mthode transmet simplement la requte l'objet curseur. Son utilit est de simplifier l'accs celui-ci et de produire un message d'erreur si ncessaire.
  • Lignes 61 71 : Ces mthodes ne sont que de simples relais vers les objets produits par le module MySQLdb : l'objet-connecteur produit par la fonction-fabrique MySQLdb.connect(), et l'objet curseur correspondant. Elles permettent de simplifier lgrement le code du programme appelant.

16.3.3. Construire un gnrateur de formulaires

Nous avons ajout cette classe notre exercice pour vous expliquer comment vous pouvez utiliser le mme dictionnaire d'application afin d'laborer du code gnraliste. L'ide dveloppe ici est de raliser une classe d'objets-formulaires capables de prendre en charge l'encodage des enregistrements de n'importe quelle table, en construisant automatiquement les instructions d'entre adquates grce aux informations tires du dictionnaire d'application.

Dans une application vritable, ce formulaire trop simpliste devrait certainement tre fortement remani, et il prendrait vraisemblablement la forme d'une fentre spcialise, dans laquelle les champs d'entre et leurs libells pourraient encore une fois tre gnrs de manire automatique. Nous ne prtendons donc pas qu'il constitue un bon exemple, mais nous voulons simplement vous montrer comment vous pouvez automatiser sa construction dans une large mesure. Tchez de raliser vos propres formulaires en vous servant de principes semblables.

 
Sélectionnez

1. class Enregistreur:
2.     """classe pour grer l'entre d'enregistrements divers"""
3.     def __init__(self, bd, table):
4.         self.bd =bd
5.         self.table =table
6.         self.descriptif =Glob.dicoT[table]   # descriptif des champs
7.
8.     def entrer(self):
9.         "procdure d'entre d'un enregistrement entier"
10.        champs ="("           # bauche de chane pour les noms de champs
11.        valeurs ="("          # bauche de chane pour les valeurs
12.        # Demander successivement une valeur pour chaque champ :
13.        for cha, type, nom in self.descriptif:
14.            if type =="k":    # on ne demandera pas le n d'enregistrement
15.                continue      #  l'utilisateur (numrotation auto.)
16.            champs = champs + cha + ","
17.            val = raw_input("Entrez le champ %s :" % nom)
18.            if type =="i":
19.                valeurs = valeurs + val +","
20.            else: 
21.                valeurs = valeurs + "'%s'," % (val)
22.                
23.        champs = champs[:-1] + ")"    # supprimer la dernire virgule, 
24.        valeurs = valeurs[:-1] + ")"  # ajouter une parenthse
25.        req ="INSERT INTO %s %s VALUES %s" % (self.table, champs, valeurs)
26.        self.bd.executerReq(req)
27.        
28.        ch =raw_input("Continuer (O/N) ? ")
29.        if ch.upper() == "O":
30.            return 0
31.        else:
32.            return 1

Commentaires :

  • Lignes 1 6 : Au moment de leur instanciation, les objets de cette classe reoivent la rfrence de l'une des tables du dictionnaire. C'est ce qui leur donne accs au descriptif des champs.
  • Ligne 8 : Cette mthode entrer() gnre le formulaire proprement dit. Elle prend en charge l'entre des enregistrements dans la table, en s'adaptant leur structure propre grce au descriptif trouv dans le dictionnaire.
    Sa fonctionnalit concrte consiste encore une fois construire morceau par morceau une chane de caractres qui deviendra une requte SQL, comme dans la mthode creerTables() de la classe GestionBD() dcrite la rubrique prcdente.

    Vous pourriez bien entendu ajouter la prsente classe encore d'autres mthodes, pour grer par exemple la suppression et/ou la modification d'enregistrements.
  • Lignes 12 21 : L'attribut d'instance self.descriptif contient une liste de tuples, et chacun de ceux-ci est fait de trois lments, savoir le nom d'un champ, le type de donnes qu'il est cens recevoir, et sa description en clair . La boucle for de la ligne 13 parcourt cette liste et affiche pour chaque champ un message d'invite construit sur la base de la description qui accompagne ce champ. Lorsque l'utilisateur a entr la valeur demande, celle-ci et formate dans une chane en construction. Le formatage s'adapte aux conventions du langage SQL, conformment au type requis pour le champ.
  • Lignes 23 26 : Lorsque tous les champs ont t parcourus, la requte proprement dite est assemble et excute. Si vous souhaitez visualiser cette requte, vous pouvez bien videmment ajouter une instruction print req juste aprs la ligne 25.

16.3.4. Le corps de l'application

Il ne nous parat pas utile de dvelopper davantage encore cet exercice dans le cadre d'un manuel d'initiation. Si le sujet vous intresse, vous devriez maintenant en savoir assez pour commencer dj quelques expriences personnelles. Veuillez alors consulter les bons ouvrages de rfrence, comme par exemple Python : How to program de Deitel & coll., ou encore les sites web consacrs aux extensions de Python.

Le script qui suit est celui d'une petite application destine tester les classes dcrites dans les pages qui prcdent. Libre vous de la perfectionner, ou alors d'en crire une autre tout fait diffrente !

 
Sélectionnez

1. ###### Programme principal : #########
2.
3. # Cration de l'objet-interface avec la base de donnes : 
4. bd = GestionBD(Glob.dbName, Glob.user, Glob.passwd, Glob.host)
5. if bd.echec:
6.     sys.exit()
7.    
8. while 1:
9.     print "\nQue voulez-vous faire :\n"\
10.          "1) Crer les tables de la base de donnes\n"\
11.          "2) Supprimer les tables de la base de donnes ?\n"\
12.          "3) Entrer des compositeurs\n"\
13.          "4) Entrer des oeuvres\n"\
14.          "5) Lister les compositeurs\n"\
15.          "6) Lister les oeuvres\n"\
16.          "7) Excuter une requte SQL quelconque\n"\
17.          "9) terminer ?                         Votre choix :",
18.    ch = int(raw_input())
19.    if ch ==1:
20.        # cration de toutes les tables dcrites dans le dictionnaire :
21.        bd.creerTables(Glob.dicoT)
22.    elif ch ==2:
23.        # suppression de toutes les tables dcrites dans le dic. :
24.        bd.supprimerTables(Glob.dicoT)     
25.    elif ch ==3 or ch ==4:
26.        # cration d'un <enregistreur> de compositeurs ou d'oeuvres :
27.        table ={3:'compositeurs', 4:'oeuvres'}[ch]
28.        enreg =Enregistreur(bd, table)
29.        while 1:
30.            if enreg.entrer():
31.                break
32.    elif ch ==5 or ch ==6:
33.        # listage de tous les compositeurs, ou toutes les oeuvres :
34.        table ={5:'compositeurs', 6:'oeuvres'}[ch]
35.        if bd.executerReq("SELECT * FROM %s" % table):
36.            # analyser le rsultat de la requte ci-dessus :
37.            records = bd.resultatReq()      # ce sera un tuple de tuples
38.            for rec in records:             # => chaque enregistrement
39.                for item in rec:            # => chaque champ dans l'enreg.
40.                    print item,
41.                print
42.    elif ch ==7:
43.        req =raw_input("Entrez la requte SQL : ")
44.        if bd.executerReq(req):
45.            print bd.resultatReq()          # ce sera un tuple de tuples
46.    else:
47.        bd.commit()
48.        bd.close()
49.        break

Commentaires :

  • On supposera bien videmment que les classes dcrites plus haut soient prsentes dans le mme script, ou qu'elles aient t importes.
  • Lignes 3 6 : L'objet-interface est cr ici. Si la cration choue, l'attribut d'instance bd.echec contient la valeur 1. Le test des lignes 5 et 6 permet alors d'arrter l'application immdiatement (la fonction exit() du module sys sert spcifiquement cela).
  • Ligne 8 : Le reste de l'application consiste proposer sans cesse le mme menu, jusqu' ce que l'utilisateur choisisse l'option n 9.
  • Lignes 27 et 28 : La classe Enregistreur() accepte de grer les enregistrements de n'importe quelle table. Afin de dterminer laquelle doit tre utilise lors de l'instanciation, on utilise un petit dictionnaire qui indique quel nom retenir, en fonction du choix opr par l'utilisateur (option n 3 ou n 4).
  • Lignes 29 31 : La mthode entrer() de l'objet-enregistreur renvoie une valeur 0 ou 1 suivant que l'utilisateur ait choisi de continuer entrer des enregistrements, ou bien d'arrter. Le test de cette valeur permet d'interrompre la boucle de rptition en consquence.
  • Lignes 35 et 44 : La mthode executerReq() renvoie une valeur 0 ou 1 suivant que la requte ait t accepte ou non par le serveur. On peut donc tester cette valeur pour dcider si le rsultat doit tre affich ou non.

Exercices :

16.2. Modifiez le script dcrit dans ces pages de manire ajouter une table supplmentaire la base de donnes. Ce pourrait tre par exemple une table orchestres , dont chaque enregistrement contiendrait le nom de l'orchestre, le nom de son chef, et le nombre total d'instruments.

16.3. Ajoutez d'autres types de champ l'une des tables (par exemple un champ de type float (rel) ou de type date), et modifiez le script en consquence.


prcdentsommairesuivant

Licence Creative Commons
Le contenu de cet article est rédigé par Gérard Swinnen et est mis à disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transposé.
Les logos Developpez.com, en-tête, pied de page, css, et look & feel de l'article sont Copyright © 2013 Developpez.com.