Programmation Python pour Débutants
Programmation Python pour Débutants
1 CONCEPTS DE BASE
1.1 COMPOSANTES MATERIELLES D'UN ORDINATEUR (HARDWARE)
1.1.1 Processeur et mémoire
1.1.2 Unités périphériques
1.2 LE LOGICIEL
1.2.1 Le binaire, le langage machine et le code octet
1.2.2 Étapes de création et d’exécution d’un programme Python avec
Windows
1.2.3 Exécution d’un script Python
1.2.4 Expressions simples avec un interprète Python
1.2.5 Objet, classe, type et littéral
1.2.6 Notion de variable
1.3 REFERENCES
2 PRINCIPES DE BASE DE LA PROGRAMMATION PYTHON
2.1 COMMENTAIRE PYTHON
2.2 APPEL DE FONCTION
2.3 EXCEPTIONS
2.4 GENIE LOGICIEL ET SPECIFICATION DU LOGICIEL
2.5 DISPOSITION DU CODE PYTHON
3 STRUCTURES DE CONTROLE
3.1 LA SEQUENCE
3.2 LA REPETITION AVEC L’ENONCE WHILE
3.3 EXPRESSION DE COMPARAISON
3.4 LA REPETITION AVEC L’ENONCE FOR
3.5 L’ALTERNATIVE (CHOIX, DECISION) AVEC IF
3.6 INTERRUPTION D’UNE REPETITION
3.7 QUALITE DU LOGICIEL, TESTS ET DEBOGAGE
4 TYPES ET EXPRESSIONS
4.1 TYPES NUMERIQUES ET EXPRESSIONS
4.2 MODULE, PACKAGE ET MODULES NUMERIQUES PREDEFINIS
4.3 EXPRESSIONS BOOLEENNES
4.4 TYPE STR : LITTERAUX ET OPERATIONS
4.4.1 Opérations du type str
4.4.2 Itération avec un for sur une séquence
4.4.3 Recherche de sous-chaîne avec in
4.4.4 Extraction d’une tranche (slice) d’une chaîne
4.4.5 Méthodes du type str
4.5 TYPE LIST
4.5.1 Accès par indice
4.5.2 Itération avec for
2
4.5.3 Test d’appartenance à la liste avec in
4.5.4 Extraction d’une tranche d’une liste
4.5.5 Liste : séquence muable (mutable)
4.5.6 Méthodes et fonctions du type list
4.5.7 Représentation interne du type list
4.5.8 Liste en compréhension
4.6 TYPE TUPLE
4.6.1 Type tuple immuable (immutable)
4.6.2 Tuple et affectation multiple
4.7 TYPE SET
4.8 TYPE DICT
4.9 REPRESENTATION DU TEMPS (TIME)
5 GRAPHISME 2D ET FONCTIONS
5.1 GRAPHISME 2D AVEC PYGAME
5.2 SIMPLIFICATION DU PROGRAMME PAR UNE FONCTION AVEC PARAMETRES
5.2.1 Création d’une fonction
5.2.2 Documentation d’une fonction et abstraction
5.2.3 Passage de paramètre par valeur, par objet, ou par référence
5.2.4 Fonction avec une valeur de retour
5.2.5 Valeur de défaut et paramètres nommés
5.2.6 Nombre variable d’arguments (*parametres, **parametresnommes)
et argument de type dict
5.3 TRAITEMENT DES EVENEMENTS DE SOURIS AVEC PYGAME.EVENT
6 INTRODUCTION A L’ANIMATION 2D
6.1 UNE PREMIERE TENTATIVE D’ANIMATION
6.2 ANIMATION PAR DOUBLE TAMPON
7 DEVELOPPEMENT DE CLASSES : CONCEPTION OBJET
7.1 DECOUPAGE D’UN PROGRAMME EN CLASSES
7.2 VARIABLE ET METHODE DE CLASSE
7.3 HIERARCHIE DE CLASSES, HERITAGE, SOUS-CLASSE, SUPER-CLASSE
7.4 CREATION D’UN MODULE
7.5 OBJET ITERABLE, OBJET ITERATEUR, FONCTION GENERATRICE ET
EXPRESSION GENERATRICE
7.6 CREATION D’UN ITERATEUR AVEC LA FONCTION ZIP()
7.7 PROGRAMMATION FONCTIONNELLE ET FONCTION LAMBDA (ANONYME)
7.8 NAMEDTUPLE : TYPE TUPLE AVEC NOMS D’ATTRIBUTS
8 ANIMATION 2D ET DEVELOPPEMENT D’UN JEU SIMPLE
9 EXCEPTIONS
9.1 TRY, EXCEPT, RAISE ET CLASSES D’EXCEPTIONS
9.2 ASSERT ET PROGRAMMATION DEFENSIVE
3
9.3 DEVELOPPEMENT DE TESTS AVEC UNITTEST
10 FORMATAGE ET ANALYSE DE CHAINES DE CARACTERES
10.1 FORMATAGE DE CHAINES DE CARACTERES
10.2 ANALYSE ET EXTRACTION DE CHAINES DE CARACTERES AVEC LES
EXPRESSIONS REGULIERES (MODULE RE)
11 TRAITEMENT DE FICHIERS
11.1 LECTURE D’UN FICHIER EN MODE TEXTE
11.2 LECTURE D’UN FICHIER EN MODE BINAIRE
11.3 ÉCRITURE DANS UN FICHIER EN MODE TEXTE
11.4 ÉCRITURE DANS UN FICHIER EN MODE BINAIRE
11.5 LECTURE D’UN FICHIER TEXTE EN FORMAT CSV
11.6 TRAITEMENT D’ENREGISTREMENTS DANS UN FICHIER BINAIRE
11.7 STOCKAGE D’OBJETS AVEC PICKLE
11.8 STOCKAGE D’OBJETS SOUS LE FORMAT JSON
11.9 GESTION DE REPERTOIRE AVEC OS
12 STRUCTURES DE DONNEES, ALGORITHMES ET COMPLEXITE
12.1 RECHERCHE LINEAIRE DANS UNE LISTE PYTHON
12.2 RECHERCHE DANS UNE LISTE TRIEE
12.3 FORMULATION RECURSIVE DE LA RECHERCHE BINAIRE
13 DEVELOPPEMENT D’APPLICATIONS WEB
13.1 NOTIONS WEB : HTTP, HTML, JSON, ETC.
13.1.1 HTTP/HTTPS
13.1.2 XML
13.1.3 HTML
13.1.4 CSS
13.1.5 JSON
13.1.6 JavaScript
13.2 ENVIRONNEMENTS VIRTUELS
13.3 UTILISATION DE FLASK
13.4 SERVEUR SECURISE
13.5 INTEGRATION D'UNE BASE DE DONNEES SQL
14 DEVELOPPEMENT D’APPLICATIONS WEBSOCKET
ASYNCHRONES
14.1 WEBSOCKET
14.2 PROGRAMMATION ASYNCHRONE
14.3 PING-PONG WEBSOCKET
14.4 EXEMPLE D'APPLICATION
14.5 CONCLUSION
4
Avant-propos
Ce livre introduit les concepts fondamentaux de la programmation et du langage
Python. Il vise un public large et ne nécessite pas de connaissances préalables en
programmation. Le manuel peut être utilisé dans un cours d’introduction à la
programmation et au langage Python. Il peut aussi servir à débutant curieux
d’apprendre les mécanismes de base de l’animation par ordinateur employé
dans les jeux vidéo. Il peut aussi servir comme préalable à un cours visant
l’analyse de données et l’apprentissage machine, qui est l’objet d’un second livre
développé dans cette optique.
Le manuel est aussi conçu de manière à être bénéfique aux lecteurs qui ont déjà
une expérience de programmation dans un autre langage que Python. Plusieurs
sujets relativement avancés viennent compléter le matériel, comme la
programmation asynchrone.
https://github.com/RobertGodin/CodePython
1En Word, une portion de texte souligné et de couleur bleue représente un lien Web qui peut
être accédé en cliquant dessus avec la touche <CTRL> enfoncée.
5
Transparents
Remerciements
Nous tenons à remercier tous ceux qui ont participé de près ou de loin à la
réalisation de ce livre. La pandémie n’a pas eu que de mauvais côtés. Un gros
merci à notre collègue Louis Martin qui, avec sa vaste expertise en
programmation et en génie logiciel, a proposé plusieurs corrections et
améliorations.
6
1 Concepts de base
" L'art de douter est le meilleur secret pour apprendre ", Marcel Prévost
7
Périphériques
Processeur central
(unité centrale de Disque
Disque dur Unité SSD Ethernet WIFI
traitement) optique
Mémoire
centrale
(vive, primaire) Souris Clavier
Écran Imprimante
Bus
8
programme est habituellement chargé en mémoire centrale à partir d’une
unité périphérique (souvent une mémoire secondaire tel que le disque dur)
avant d’être exécuté. Les données doivent aussi être chargée en mémoire
centrale avant d’être traitée par le programme. La mémoire centrale est
constituée d'une séquence de cases (cellules, mots) de taille fixe.
Adresse-mémoire
Une case de la mémoire centrale est identifiée par une adresse (adresse-
mémoire). Dans le cas le plus simple, l’adresse est un entier dans un intervalle
de 0 à n-12, où n est la taille de la mémoire centrale. La taille d'une case peut
varier selon le processeur utilisé.
0
1
2
3
...
n-1
2Par exemple, si la taille de la mémoire est n=16, les cases seront numérotées de 0 à 15.
En réalité, le schéma d’adressage peut être plus compliqué...
9
l'information en permanence au-delà des interruptions de courant. Il faut
comprendre que les interruptions de courant ne sont pas toujours
volontaires et peuvent provenir, par exemple, d'une panne d'électricité. Il
est donc important de placer, en mémoire secondaire ou en ROM, les
éléments qui doivent être conservés de manière persistante, i.e. survivre aux
programmes ou aux anomalies de fonctionnement.
x 15
opération + du
y 5 processeur
central
z 20
3 La réalité est un peu plus complexe. L’accès à la mémoire centrale peut être géré et
accéléré par l’intermédiaire de divers mécanismes tels que la mémoire virtuelle,
l’antémémoire (cache memory) et les registres.
10
instructions. L’UAL effectue les calculs arithmétiques et logiques tel que
l’addition illustrée ci-haut. La puissance d'un ordinateur vient de sa capacité
à exécuter un très grand nombre d’opérations simples à une vitesse
extrême.
entrée/lecture
Mémoire
centrale Périphérique
(vive, primaire) sortie/écriture
11
Entrée, lecture (input)
Périphérique d'entrée
Les périphériques d'entrée permettent les opérations d’entrée. Par analogie avec
l’humain, ce sont en quelque sorte les sens de l’ordinateur. En particulier,
certains périphériques permettent à l’ordinateur de recevoir des
informations des utilisateurs humains. La souris, le clavier, l’écran tactile, le
microphone et la caméra sont des unités d'entrée bien connus. Au-delà des
périphériques d’entrée pour l’humain, toutes sortes de capteurs existent
pour saisir des données de diverses natures.
Périphérique de sortie
Les interfaces réseau (modem, carte réseau, ...) et les mémoires secondaires (disque dur,
mémoire SSD, disque optique, ...) sont aussi des périphériques très répandus.
Plusieurs de ces périphériques permettent à la fois les entrées et les sorties.
Par exemple, il est possible de lire et d'écrire des informations sur le disque
dur. Il est possible d’envoyer et de recevoir des informations par un réseau.
12
Mémoire secondaire (secondary storage), de masse, auxiliaire,
permanente, externe, stable, non volatile ou persistante
Une mémoire secondaire est une mémoire habituellement plus lente que la
mémoire centrale mais qui a la caractéristique d'être permanente. Son
contenu ne disparaît pas lorsque le courant électrique est interrompu. Les
informations (données et programmes) qui doivent être conservées en
permanence seront donc toujours placées en mémoire secondaire. Ce type
de mémoire est aussi appelé mémoire de masse, auxiliaire, permanente, externe,
stable, non volatile ou persistante.
Interface réseau
4La distance possible entre les ordinateurs d’un réseau local varie en fonction du matériel
utilisé.
13
réseau de réseaux ! Ainsi, un ordinateur branché à un réseau local peut
accéder à Internet lorsque le réseau local est lui-même branché à Internet.
Deux interfaces populaires à un réseau local sont l’interface avec fil Ethernet
ou l’interface sans fils (WIFI). Les appareils mobiles peuvent passer par le
réseau cellulaire pour l’accès à Internet.
1.2 Le logiciel
Un ordinateur fonctionne en exécutant des programmes. On utilise le
terme logiciel (software) pour désigner les programmes. Lorsqu'on démarre un
ordinateur5, il y a un premier programme qui est automatiquement exécuté,
appelé le programme de démarrage (boot program). Ce premier programme est
habituellement dans la mémoire centrale à une adresse fixe, connue à
l'avance. À cet effet, il y a une petite partie de la mémoire centrale qui est
permanente, appelée mémoire morte (Read Only Memory - ROM), qui
contient le programme de démarrage. Le programme de démarrage a pour
rôle essentiel de charger en mémoire centrale un plus gros programme
appelé le système d'exploitation6, à partir d'une mémoire secondaire,
habituellement le disque dur7.
5 On allume l'ordinateur en enfonçant le bouton ON qui est parfois bien caché pour que
les non-initiés éprouvent un sentiment d’humiliation la première fois qu'ils essaient de le
faire fonctionner. Curieusement, il faut parfois enfoncer le bouton ON pour éteindre
certains ordinateurs…
6 Pour être plus précis, le programme de démarrage inclut déjà certaines parties du système
d'exploitation nécessaires pour accéder aux unités périphériques. Dans les ordinateurs PC
compatibles, cette portion du système d'exploitation est appelée le BIOS (Basic Input Output
System).
7 Il est aussi possible de charger le système d’exploitation à partir d’une autre mémoire
secondaire (clé USB, disque optique, etc.) mais ceci est habituellement effectué dans des
circonstances spéciales, par exemple, lorsqu’un problème survient avec le disque dur.
14
Système d'exploitation (Operating System - OS)
8 Pourquoi la cherche-t'on ?
15
Programme d'application (ou simplement application)
16
d’un dossier de travail courant dans le contexte d’une interaction avec le
système.
C:\Users\Robert\HelloWorld.py
Dans le cas d’un système Unix ou IOS, la racine est identifiée par / et les
noms des dossiers à parcourir sont séparés par des /. Ainsi, le chemin
précédent serait représenté par :
/Users/Robert/HelloWorld.py
Un chemin relatif part de l’hypothèse qu’un dossier courant est déjà déterminé
par suite d’une interaction précédente avec le système. Par exemple,
supposons que le dossier courant sous Windows est :
C:\Users
Robert\HelloWorld.py
17
Dans un chemin relatif, le « . » représente le dossier courant. On peut donc
utiliser la formulation équivalente :
.\ Robert\HelloWorld.py
Parallélisme
2510 = 000110012
= 0 × 27 + 0 × 26 + 0 × 25 + 1 × 2 4 + 1 × 23 + 0 × 22 +
0 × 21 + 1 × 20
= 0 + 0 + 0 + 16 + 8 + 0 + 0 + 1.
Chacun des bits correspond à un exposant en base 2. Pour une
représentation plus compacte des nombres binaires dans le contexte de la
représentation textuelle, on a souvent recours au système de numération
octal (base 8) ou hexadécimal (base 16). Par exemple, l’entier 25 en décimal
est représenté en hexadécimal par :
19
Les mémoires modernes ont des tailles impressionnantes qui sont
habituellement mesurées en employant les conventions du système
international détaillées dans le tableau suivant.
20
a b c \r \n 1 2 \r \n
Sous Windows, la fin de ligne est représentée par la séquence des caractères
spéciaux ASCII, retour de chariot (\r) et saut de ligne (\n)9. Le code ASCII ne
permet pas de traiter les caractères de toutes les langues. La norme Unicode
(www.unicode.org) présentée à la section 4.4 est une norme plus générale
qui permet d’encoder les caractères d’un grand nombre de langues en
employant plus d’un octet au besoin.
Interprète (interpréteur)
9La manière de représenter les fins de ligne peut différer en fonction de la plate-forme.
Unix, par exemple, emploie uniquement le saut de ligne ('\n'). Ceci cause souvent des
problèmes lors du transfert de fichiers de texte entre systèmes différents.
21
curieux, l’encadré suivant donne des détails au sujet de la compilation, de
l’interprétation et de l’aspect hybride de Python.
23
Code source
Python
Compilateur de Compilation
code-octet
Code-octet
Processeur de
code-octet
Interprète de Compilateur de
(machine
code-octet code-octet
Python)
Sous macOS, vous pouvez lancer l’application Terminal qui se trouve dans
Finder sous Applications, Utilitaires. Le résultat est similaire à Windows.
Notez cependant que pour invoquer la version 3 de Python, sous macOS,
il peut être nécesssaire de taper python3 plutôt que python. C’est vrai aussi
sous d’autres systèmes où la version 2 de Python est installée par défault.
25
L’interface Python affiche un message décrivant la version employée.
26
Note au sujet de l’installation de Python
Si un message d’erreur indique que « python » n’est pas reconnu et que vous
êtes sous Windows, il est possible que ce soit parce que le chemin qui mène
au programme python.exe ne soit pas inclus dans la variable d’environnement
Path de Windows. Dans ce cas, une option consiste à spécifier le chemin
complet du programme Python. Une autre option consiste à inclure le
chemin dans la variable d’environnement Path qui identifie des chemins de
dossiers que le système d’exploitation Windows parcoure afin de retrouver
les programmes à exécuter. Cette option est habituellement disponible dans
le programme d’installation de Python.
Si la commande n’est pas reconnue, mais que vous avez bien installé
Python sous Windows, nous vous suggérons de consulter le site
Utiliser Python sous Windows :
https://docs.python.org/fr/3/using/windows.html.
L’invite de commande >>> indique que le shell Python est en attente d’un
énoncé du langage Python. Le code Python de notre exemple de
programme est très simple :
print('Hello, World!')
27
Notation <fin de ligne>
28
Erreur de de syntaxe
Si le programme source est incorrect selon les règles de syntaxe du langage
Python, des messages d’erreur sont affichés afin de faciliter le repérage et
la correction des erreurs de syntaxe.
Attention !
Outre les erreurs de syntaxe, il est possible que d’autres types d’erreurs
soient rencontrées.
29
extension « .py » et faire exécuter le code à partir du fichier. Un tel fichier
est appelé un script Python. Vous pouvez vous y prendre de différentes
manières.
python HelloWorld.py
30
Nous vous conseillons d’utiliser Visual Studio Code comme point de
départ. Pour rendre l’opération aisée, installez l’extension Python (de
Microsoft) au sein de Visual Studio Code. Choisissez dans le menu d’icônes
de gauche l’icône « Extension » (composé généralement de 4 carrés). Une
boîte de saisie devrait apparaître, tapez Python. Dans les premiers résultats,
vous devriez trouver l’extension Python publiée par Microsoft. Après
l’avoir sélectionnée, tapez Installer (Install).
Pour exécuter le nouveau programme, tapez F1 alors que vous êtes dans
Visual Studio Code. Sur certains claviers, il peut être nécessaire de taper sur
une touche function ou fn au même moment. Une boîte de saisie devrait
alors apparaître, tapez « Python : Exécuter le fichier ». Sélectionnez avec
votre souris la commande suggérée correspondant à votre saisie. Vous
devriez alors voir votre programme s’exécuter dans la console.
>>> 3+2
5
>>> 5-4
1
La multiplication par * :
>>> 3*4
12
La division par / :
>>> 20/5
4.0
>>> 11//2
5
>>> 11%2
1
10La notation nom_fonction() avec les parenthèses qui suivent le nom d’une fonction
est une convention qui sert à désigner une fonction
32
>>> type(20)
<class 'int'>
>>> type(4.0)
<class 'float'>
Dans un premier temps, type et classe peuvent être considérés comme des
synonymes. Des nuances seront introduites plus loin au sujet de la
distinction entre ces concepts.
>>> id(20)
1509712640
L’identité d’un objet sert à lui faire référence et n’a pas de lien direct avec
sa valeur. Dans notre exemple, la valeur de l’objet est le nombre entier 20
et l’identité de l’objet, qu’on peut assimiler à une adresse mémoire, est
1509712640. L’importance de la notion d’objet sera détaillée par la suite.
Dans un premier temps, on peut simplement penser aux données en faisant
abstraction des détails des mécanismes d’objets.
Une donnée de type float est représentée en virgule flottante qui est une
représentation approximative d’un nombre réel. En effet, la représentation
interne est composée d’une partie mantisse et d’une partie exposant qui sont
d’une précision limitée. Cette limite de précision peut devenir contraignante
pour des applications qui manipulent des nombres de très grande ou très
petite taille relativement à la précision permise.
33
Dans le code Python, une donnée particulière est représentée par une suite
(chaîne) de caractères appelée un littéral. Par exemple, la chaîne des deux
caractères « 20 » est un littéral qui représente l’entier 20. Lorsqu’un littéral
est lu par l’interprète Python, il est converti sous forme d’un objet du type
approprié. Python s’occupe de la représentation interne sous forme d’un
entier binaire et de l’allocation de mémoire à l’objet. Le programmeur n’a
pas à se soucier de ces détails dans la plupart des cas. Le tableau suivant
montre quelques types de base et des exemples de littéraux.
Type Description
Deux valeurs : False ou True (équivalent à 0 ou 1 du type
bool
int)
Entier d’une précision illimitée (borné par la mémoire
int disponible). Exemples : 12, -46, 0o14 (octal), 0x31
(hexadécimal)
Nombre réel double précision (précision de 64 bits IEEE
754-1985) entre -1.7*10308 et 1.7*10308 (15 chiffres
float significatifs). Exemples : 5., 5.0, 3.1416, 15.56e4 ou
15.56E4 (équivalent à 155600.0), 245.1e-5 (équivalent à
0.002451)
complex Nombre complexe. Exemple : 5.23+67.241j
Chaîne de caractères. Suite de caractères Unicode entre
apostrophes (peut inclure guillemets) ou guillemets (peut
inclure apostrophes) ou triples apostrophes/guillemets
str
(permet de chevaucher les lignes). Exemples :
'aB3$"5Fde63', ''XY'12 $%a'Bc'', '''peut chevaucher
plusieurs lignes'''
En plus des classes prédéfinies, il est possible de créer de nouvelles classes
et ceci est un aspect fondamental d’un langage de programmation objet tel
que Python. Souvent, il n’est pas nécessaire de créer de nouvelles classes
parce qu’il y a beaucoup de bibliothèques de classes qui ont déjà été
développées pour divers types d’applications.
Bibliothèque (Library)
34
variable pour désigner cette valeur par la suite. La valeur à affecter est
produite par une expression qui est à droite du = et la variable est à gauche.
>>> a=2+3
>>> a
5
>>> print(a)
5
a 5
Il est à noter que le résultat de l’expression n’est pas affiché par le shell à la
suite de l’exécution d’un énoncé d’affectation. D’autre part, si on tape une
expression qui est simplement le nom d’une variable, le shell affiche la valeur
correspondante, comme si on faisait appel à la fonction print().
Après avoir affecté un objet à une variable, il est possible par la suite
d’employer le nom de la variable pour faire référence à l’objet tel qu’illustré
par l’expression suivante :
>>> 3*a
15
>>> a=7
>>> a
7
a 7
>>> a=a+1
>>> a
8
>>> b = 5
>>> type(b)
<class 'int'>
>>> b = 3.4
>>> type(b)
<class 'float'>
Cet exemple illustre le fait qu’une variable Python peut faire référence à des
objets de types différents au cours de l’exécution d’un programme.
En Python, une variable n’a pas de type comme tel. Elle peut faire référence
à un objet de n’importe quel type à un point donné d’un script, et la variable
peut être modifiée par la suite pour faire référence à un objet de n’importe
quel autre type. Cette approche est parfois qualifiée de typage dynamique.
D’autres langages (Java, C, etc.) emploient le typage statique où il faut
spécifier explicitement le type d’une variable. Un avantage du typage
statique est l’opportunité de vérifier et d’optimiser certains aspects du
programme avant de l’exécuter. Le typage dynamique simplifie le code et
permet plus de flexibilité à l’exécution.
https://docs.python.org/fr/3/library/typing.html
Que devient l’objet qui correspond à 5 qui était référencé par a avant cette
nouvelle affectation ? L’espace mémoire occupé par l’objet est récupéré
36
ultimement par un processus de ramasse-miettes (garbage collection) comme
illustré par la figure suivante :
a 7
1.3 Références
Un grand nombre de ressources Web sont disponibles pour l’apprentissage
de Python. En particulier, le site suivant maintient plusieurs liens vers des
ressources pour débutants :
https://wiki.python.org/moin/BeginnersGuide
Plusieurs publications sur divers aspects de Python sont disponibles sur le site :
https://www.freecodecamp.org/news/tag/python/
37
2 Principes de base de la
programmation Python
Ce chapitre introduit les principes de base de la programmation avec
Python à partir d’un exemple simple. Le script Python suivant permet de
saisir deux nombres entiers par le clavier de l’ordinateur et d'en afficher la
somme.
CodePython/chapitre2/Exemple1.py11
1. ''' Exemple1.py
2. Ce programme saisit deux entiers et en affiche la somme '''
3.
4. # Saisir les deux chaînes de caractères qui représentent des
nombres entiers
5. chaine1 = input("Entrez un premier nombre entier :")
6. chaine2 = input("Entrez un second nombre entier :")
7.
8. # Convertir les deux chaînes de caractères en entiers
9. entier1 = int(chaine1)
10. entier2 = int(chaine2)
11.
12. # Calculer la somme des deux entiers
13. somme = entier1 + entier2
14.
15. # Afficher la somme
16. print("La somme des deux entiers est ",somme)
17.
Exercice. Editez ce script, sauvegardez-le dans un fichier nommé
Exemple1.py et faites-le exécuter.
python Exemple1.py
11Ce titre est un lien vers le répertoire GitHub qui contient le code des exemples et des exercices. Le
préfixe CodePython correspond à https://github.com/RobertGodin/CodePython
38
L’utilisateur entre 12 suivi de <fin de ligne>. Le script affiche ensuite
un message qui invite l’utilisateur à entrer un second entier.
''' Exemple1.py
Ce programme saisit deux entiers et en affiche la somme '''
Toute portion du code source qui débute par trois apostrophes (''') ou
trois guillemets (""") et se termine de la même manière est considérée
comme un commentaire en Python et n'a aucun effet du point de vue de
l’exécution du programme. Ainsi, tous les commentaires peuvent être
supprimés sans changer le fonctionnement du programme. L'objectif
principal d'un commentaire est de faciliter la compréhension du
39
programme par les humains (programmeurs)12. Un commentaire de ce type
peut s'étendre sur une ou plusieurs lignes.
# Saisir les deux chaînes de caractères qui représentent des nombres entiers
Diagramme syntaxique
Nom de variable
Les noms de variables doivent respecter des règles précises qui s’appliquent
à tous les identificateurs Python. Un identificateur doit débuter par une
lettre ou un soulignement _ suivi d’une suite d’un nombre quelconque de
caractères limités aux lettres, chiffres, et au soulignement _. Les lettres
peuvent être majuscules ou minuscules et la casse est significative. Par
exemple, « somme » et « Somme » sont deux noms différents. Certains
identificateurs sont dits réservés et ne peuvent servir de nom de variable.
Les identificateurs réservés ont un sens prédéfini en Python (voir
https://docs.python.org/3/reference/lexical_analysis.html#keywords ).
En particulier, pour le nom des variables, il est proposé de séparer les mots
par un soulignement _ tel que dans :
un_nom_de_variable
13Ce guide est un PEP (Python Enhancement Proposal). Les PEPs sont des propositions
pour l’évolution du langage faite à la communauté qui permettent de gérer l’évolution de
manière consensuelle.
41
Le nom de variable formé d’un soulignement _ seul peut servir à désigner
un nom de variable dont la valeur est sans importance mais qui doit être
présent dans l’énoncé.
variable = expression
appel de fonction :
Dans l’appel d’une fonction, il faut préciser les valeurs des arguments (aussi
appelés paramètres réels) entre parenthèses après le nom de la fonction. Un
argument représente une valeur qui est utilisée par la fonction.
42
expression de la partie droite d’une affectation, il faut que la fonction
retourne une valeur. Dans notre exemple, la valeur retournée par la
fonction est affectée à la variable chaine1.
Insistons sur le fait que la séquence de caractères lue n’est pas interprétée
comme un nombre entier à ce point-ci mais comme une simple chaîne de
caractères. Il faut par la suite convertir cette chaîne en un entier. La figure
suivante illustre l’effet du code dans le contexte de ce scénario. Le nom de
variable chaine1 est créé, et la fonction input() crée un objet qui prend
comme valeur la chaîne de caractère "12". Dans la figure, la boîte
représente l’objet en mémoire. Le nom de variable chaine1 fait référence
à l’objet qui représente la chaîne de caractère "12". La flèche représente la
référence à l’objet par la variable. Concrètement, la référence correspond à
l’identité de l’objet. L’interprète Python doit donc mémoriser pour chacune
des variables, la valeur de l’identité d’objet de la donnée à laquelle la variable
fait référence. La table des noms représente cette correspondance que
l’interprète Python doit maintenir. Mais ceci est invisible au programmeur
qui n’a pas à se préoccuper de la valeur de l’identité de l’objet. Ainsi, le nom
de variable chaine1 devient en quelque sorte un alias pour la chaîne de
caractère "12" jusqu’à ce que la variable soit modifiée.
Mémoire centrale
de l'ordinateur
chaine1 "12"
Mémoire centrale
de l'ordinateur
chaine1 "12"
chaine2 "8"
entier1 = int(chaine1)
43
chaine1 "12"
entier1 12
entier2 = int(chaine2)
chaine1 "12"
chaine2 "8"
entier1 12 int(”8")
entier2 8
chaine1 "12"
chaine2 "8"
entier1 12
entier2 8 +
somme 20
Cet exemple montre qu’un énoncé du langage peut être un simple appel à
une fonction. Le langage Python offre un grand nombre de fonctions
prédéfinies. Plusieurs de ces fonctions seront présentées tout au long de cet
ouvrage. La liste des fonctions prédéfinies est énumérée à :
https://docs.python.org/3/library/functions.html#built-in-functions
Exemple.
2.3 Exceptions
Un programme Python peut lever une exception si une condition anormale
se présente :
C:\Users\Robert\CodePython>python Exemple1.py
Entrez un premier nombre entier :1
Entrez un second nombre entier :e
Traceback (most recent call last):
File "Exemple1.py", line 10, in <module>
entier2 = int(chaine2)
ValueError: invalid literal for int() with base 10: 'e'
Dans l’exemple, l’utilisateur a entré la lettre "e" par erreur pour le second
nombre entier. Lorsque la fonction int() est appelée avec l’argument "e",
une exception est levée par la fonction parce qu’elle a détecté que
l’argument n’est pas une chaîne de caractère qui représente un nombre
entier. L’exception provoque un arrêt du programme et un message
45
explicatif est affiché. Le message indique la ligne de code qui a provoqué
l’exception. Il précise aussi le type d’exception, ici ValueError suivi d’une
explication plus détaillée du problème.
CodePython/chapitre2/Exercice1.py
1. ''' Exercice1.py
2. Ce programme saisit trois entiers et en affiche la somme '''
3.
4. # Saisir les deux chaînes de caractères qui représentent des
nombres entiers
5. chaine1 = input("Entrez un premier nombre entier :")
6. chaine2 = input("Entrez un second nombre entier :")
7. chaine3 = input("Entrez un troisième nombre entier :")
8.
9.
10. # Convertir les trois chaînes de caractères en entiers
11. entier1 = int(chaine1)
12. entier2 = int(chaine2)
13. entier3 = int(chaine3)
14.
15. # Calculer la somme des trois entiers
16. somme = entier1 + entier2 + entier3
17.
46
18. # Afficher la somme
19. print("La somme des trois entiers est ",somme)
Exercice. Écrivez un programme qui effectue le même traitement que le
précédent mais en utilisant une seule variable de type str (plutôt que trois)
et une variable int plutôt que quatre !
CodePython/chapitre2/Exercice2.py
1. ''' Exercice2.py
2. Ce programme saisit trois entiers et en affiche la somme en
employant une seule
3. variable de type str et une de type int'''
4.
5. chaine = input("Entrez un premier nombre entier :")
6. somme = int(chaine)
7.
8. chaine = input("Entrez un second nombre entier :")
9. somme = somme + int(chaine)
10.
11. chaine = input("Entrez un troisième nombre entier :")
12. somme = somme + int(chaine)
13.
14. # Afficher la somme
15. print("La somme des trois entiers est ",somme)
16.
Qualité du logiciel : mémoire consommée, temps de calcul, clarté du
programme
47
2.5 Disposition du code Python
Les règles de Python concernant la disposition du code sont assez flexibles
au sens où les éléments du langage peuvent être séparés par une suite
quelconque d’espaces blancs. Le terme espace blanc désigne un espace, une
marque de tabulation ou une fin de ligne. Cependant, certaines instructions
doivent respecter des règles précises concernant l’indentation tel
qu’expliqué au prochain chapitre.
>>> print(
... 'abc'
... )
abc
>>>
48
3 Structures de contrôle
Il y a trois manières fondamentales d’enchaîner les opérations d’un
programme : la séquence, la répétition et l’alternative (choix, décision). Ce chapitre
présente les énoncés de base Python qui permettent d’exprimer ces trois
types d’enchaînement.
3.1 La séquence
Les exemples vus jusqu’à présent sont des séquences. Une séquence
d’énoncés est de la forme générale suivante :
énoncé1
énoncé2
…
énoncén
Chacun des énoncés débute au premier caractère de la ligne s’il n’est pas
dans un bloc comme nous le verrons plus loin. Il se termine normalement
par la <fin de ligne>.
Les énoncés placés en séquence les uns après les autres et sont exécutés
dans cet ordre.
49
développement peut souvent être démarré par la production de divers
artefacts incluant des diagrammes. Le langage UML inclut plusieurs types
de diagrammes à cet effet.
énoncé 1
énoncé 2
énoncé n
Si les énoncés d’une séquence sont courts, il est aussi possible d’en placer
plusieurs sur la même ligne en les séparant par des ;.
CodePython/chapitre3/ExempleSequenceSurUneLigne.py
50
Dans le cas d’une séquence d’affectations, il est possible d’employer une
affectation multiple selon la syntaxe suivante :
CodePython/chapitre3/ExempleAffectationMultiple.py
1. # Saisir les deux chaînes de caractères qui représentent des nombres entiers
2. chaine1 = input("Entrez un premier nombre entier :")
3. chaine2 = input("Entrez un second nombre entier :")
4.
5. # Convertir les deux chaînes de caractères en entiers et calculer la somme
6. entier1, entier2, somme = int(chaine1), int(chaine2), entier1 + entier2
7.
8. # Afficher la somme
9. print("La somme des deux entiers est ",somme)
CodePython/chapitre3/ExempleAfficher1a5.py
1. """
2. Afficher les entiers de 1 à 5
3. """
4.
5. print(1)
6. print(2)
7. print(3)
8. print(4)
9. print(5)
10.
S’il fallait afficher les entiers de 1 à 1 000 000 de cette manière, le
programme serait long à écrire … Pour éviter de répéter les énoncés dans
le programme, on peut employer une répétition (aussi appelée boucle ou
itération). L’énoncé while Python est un des énoncés qui permet d’effectuer
une répétition. ExempleWhile illustre la notion de répétition avec
compteur en employant un énoncé while. Ce programme a le même effet
que le précédent.
CodePython/chapitre3/ExempleWhile.py
1. """
2. Exemple d'une boucle while avec un compteur
3. """
4.
5. compteur = 1
6. while compteur <=5:
51
7. print(compteur)
8. compteur = compteur + 1
9.
Voici la syntaxe d’un énoncé while :
énoncé while :
Un bloc de code Python est une séquence d’un ou plusieurs énoncés indentés
de manière identique par rapport à une entête qui se termine par « : ». Un
entête peut-être un while ou d’autres types d’énoncés. La convention
suggérée est d’indenter systématiquement avec 4 espaces mais ce n’est pas
obligatoire. Il est aussi permis d’indenter avec les tabulations. Cependant, il
ne faut pas mélanger les deux. Le guide de style PEP8 recommande
d’employer systématiquement 4 espaces pour l’indentation. Ceci peut être
facilité en paramétrant l’éditeur de code à cet effet.
52
compteur = 1
[compteur > 5]
[compteur <= 5]
print(compteur)
compteur = compteur +1
S’il y a plus d’un énoncé à répéter, comme c’est le cas de notre exemple, il
faut les regrouper dans un bloc, et les indenter de manière identique.
compteur <= 5
Ainsi, tant que cette condition s’avère vraie (tant que le compteur est plus
petit ou égal à 5), les énoncés indentés suivants sont exécutés en boucle :
print(compteur)
compteur = compteur + 1
53
3.3 Expression de comparaison
Le nombre de répétition du while est contrôlé par une expression
booléenne qui retourne True ou False. Une expression booléenne peut
être formée en comparant des valeurs à l’aide des opérateurs de
comparaison du tableau suivant. Il y a quelques différences avec la notation
mathématique usuelle pour accommoder les ensembles de caractères des
claviers simples.
Opérateur de
Signification
comparaison
< Plus petit que
<= Plus petit ou égal à
> Plus grand que
>= Plus grand ou égal à
== Égal à
!= N’est pas égal à
Il est possible de former des expressions booléennes plus complexes avec
les opérateurs booléens (voir chapitre 4). Le test d’égalité entre deux valeurs
est exprimé par == pour marquer la distinction avec un énoncé d’affection
exprimé par =.
Solution. CodePython/chapitre3/ExerciceWhile1.py
1. """
2. Afficher 0,2,4,6,8,10
3. """
4.
5. compteur = 0
6. while compteur <=10:
7. print(compteur)
8. compteur = compteur + 2
Exercice. Modifiez l’exemple afin d’afficher 5, 4, 3, 2, 1, 0, -1, -2, -3, -4, -
5.
Solution. CodePython/chapitre3/ExerciceWhile2.py
1. """
2. Afficher les valeurs de compteur 5,4,3,2,1,0,-1,-2,-3,-4,-5
3. """
4.
54
5. compteur = 5
6. while compteur >= -5:
7. print(compteur)
8. compteur = compteur -1
9.
Exercice. Reprenons l’exemple de lecture d’entiers afin d’en afficher la
somme. Nous avons vu le cas de deux et de trois entiers. Dans cet exercice,
il faut additionner 10 entiers ! Une solution consiste à répéter dix fois la
lecture, la conversion et l’accumulation dans somme. Dans cet exercice, il
faut plutôt utiliser une boucle while pour lire dix entiers et en afficher la
somme.
Solution. CodePython/chapitre3/ExerciceWhile3.py
1. """
2. Lire dix entiers et en afficher la somme avec un while
3. """
4.
5. somme=0
6. compteur = 1
7. while compteur <= 10:
8. chaine = input("Entrez un nombre entier :")
9. somme = somme + int(chaine)
10. compteur = compteur+1
11. print("La somme des dix entiers est:",somme)
12.
Exercice. Maintenant supposons que le nombre d’entiers à lire est inconnu
à l’avance. Une technique souvent employée pour arrêter la répétition est
l’utilisation d’une valeur spéciale appelée sentinelle qui provoque l’arrêt de la
répétition. Par exemple, supposons que le nombre 0 représente la
sentinelle. Vous devez écrire un programme qui lit une série d’entiers
jusqu’à ce que l’entier 0 soit entré et qui produit la somme de ces entiers.
Solution. CodePython/chapitre3/ExerciceWhileSentinelle.py
1. """
2. Lire une suite d'entiers jusqu'à ce que l'entier 0 soit entré et
afficher la somme des entiers lus.
3. """
4.
5. somme=0
6. chaine = input("Entrez un nombre entier, 0 pour terminer :")
7. while chaine != '0':
8. somme = somme + int(chaine)
9. chaine = input("Entrez un nombre entier, 0 pour terminer :")
10. print("La somme des entiers est:",somme)
Une solution plus compacte et élégante est possible avec l’opérateur morse
(walrus) := introduit avec Python 3.8 qui permet une affectation à l’intérieur
55
d’une expression. Ceci permet de tester une condition de fin de boucle à
partir d’une expression et de réutiliser par la suite la valeur de l’expression
sans devoir la réévaluer.
Exemple. CodePython/chapitre3/ExempleOperateurMorse.py
1. """
2. Lire une suite d'entiers jusqu'à ce que l'entier 0 soit entré et afficher la
somme des entiers lus.
3. Solution avec l'opérateur morse :=
4. """
5.
6. somme=0
7. while (chaine := input("Entrez un nombre entier, 0 pour terminer :")) != '0':
8. somme = somme + int(chaine)
9. print("La somme des entiers est:", somme)
10.
1
12
123
1234
12345
123456
1234567
12345678
123456789
Indice : il est permis d’imbriquer une boucle while dans une autre boucle
while.
print(une_donnee, end='')
Solution. CodePython/chapitre3/ExerciceWhileImbriques.py
1. """
2. Exercice deux while imbriqués
3. """
4. compteur1 = 1
5. while compteur1 <= 9:
6. compteur2 = 1
7. while compteur2 <= compteur1:
8. print(compteur2, end='')
9. compteur2 = compteur2 + 1
10. print('')
11. compteur1 = compteur1 + 1
56
3.4 La répétition avec l’énoncé for
L’utilisation d’une répétition avec compteur est très fréquente. La boucle
for simplifie l’écriture de telles boucles. Le programme suivant produit le
même effet que ExempleWhile en affichant les entiers de 1 à 5. Dans un
énoncé for, l’initialisation du compteur, l’expression de fin de répétition et
la mise-à-jour du compteur sont remplacés par l’itération sur une séquence
générée par la fonction range().
CodePython/chapitre3/ExempleForRange.py
1. """
2. Exemple d'une boucle for avec la fonction range()
3. """
4.
5. for compteur in range(1,6):
6. print(compteur)
Après le mot-clé for, il faut spécifier une variable, ici compteur, qui
prendra successivement les valeurs d’une séquence spécifiée après le mot-
clé in. L’appel à range(1,6) génère la séquence 1, 2, 3, 4, 5. Le premier
argument est la valeur initiale. La valeur du deuxième argument est la borne
supérieure mais elle n’est pas incluse. L’incrément est 1 par défaut.
Attention !
énoncé for :
57
Dans l’exemple précédent, la séquence est produite par la fonction
range(). D’autres possibilités sont explorées plus loin.
1
12
123
1234
12345
123456
1234567
12345678
123456789
Solution. CodePython/chapitre3/ExerciceForImbriques.py
1. """
2. Exercice for imbriqué
3. """
4. for compteur1 in range(1,10):
5. for compteur2 in range(1,compteur1+1):
6. print(compteur2, end='')
7. print('')
58
CodePython/chapitre3/ExempleIf.py
1. """
2. Exemple simple illustrant l'énoncé if
3. """
4.
5. un_int = int(input("entrez un nombre entier: "))
6. if un_int > 10:
7. print(un_int,"est plus grand que 10")
8. else :
9. print(un_int,"est plus petit ou égal à 10")
Dans notre exemple, si la condition
un_int > 10
sera exécuté. Si l’expression se révèle fausse, l’énoncé qui sera exécuté est :
print(unInt,"est plus grand que 10") print(unInt,"est plus petit ou égal à 10")
La syntaxe du if est :
énoncé if :
59
La partie else est optionnelle. En son absence, lorsque l’expression de
condition est fausse, rien n’est exécuté et l’interprète passe à l’énoncé
suivant le if. Il faut faire attention de bien indenter le else afin de produire
l’effet désiré.
Les trois manières d’enchaîner les énoncés ont été introduites : séquence,
répétition et alternative. Le diagramme syntaxique suivant résume les
différents cas d’énoncés vus jusqu’à présent :
énoncé :
affectation
appel de fonction
énoncé while
énoncé for
énoncé if
etc.
CodePython/chapitre3/ExempleIfImbrique.py
1. """
2. Exemple if sans else et if imbriqué
3. """
4.
5. un_int = int(input("entrez un nombre entier: "))
6. if un_int > 10:
7. if un_int > 20:
8. print(un_int,"est plus grand que 20")
9. else :
10. print(un_int,"est plus grand que 10 et plus petit ou égal à 20")
Pour que l’interprétation soit correcte, il est important que le else soit
imbriqué au même niveau que le if imbriqué. L’exemple suivant illustre le
problème d’une mauvaise imbrication.
CodePython/chapitre3/ExempleImbricationIncorrecte.py
1. """
2. Exemple if sans else et if imbriqué
60
3. """
4.
5. un_int = int(input("entrez un nombre entier: "))
6. if un_int > 10:
7. if un_int > 20:
8. print(un_int,"est plus grand que 20")
9. else :
10. print(un_int,"est plus grand que 10 et plus petit ou égal à 20")
Solution. CodePython/chapitre3/ExerciceIfDiviseur0.py
1. """
2. Exercice if division. Si le diviseur est nul, afficher un message
3. """
4.
5. dividende = int(input("entrez un nombre entier, le dividende : "))
6. diviseur = int(input("entrez un nombre entier, le diviseur : "))
7. if diviseur != 0:
8. print("Le résultat de ", dividende, "divisé par",diviseur,"est :", dividende//diviseur)
9. else :
10. print("Le diviseur ne peut être nul (0)")
Exercice. Lire deux entiers et afficher le maximum des deux. N.B. S’ils
sont égaux, il n’y a qu’à afficher un des deux !
Solution. CodePython/chapitre3/ExerciceMax2Entiers.py
1. """
2. Exemple afficher le maximum de deux entiers
3. """
4.
5. entier1 = int(input("entrez un nombre entier: "))
6. entier2 = int(input("entrez un nombre entier: "))
7. if entier1 > entier2:
8. print("Le maximum des deux entiers est :", entier1)
9. else :
10. print("Le maximum des deux entiers est :", entier2)
Exercice. Lire trois entiers et afficher le maximum des trois.
Solution. CodePython/chapitre3/ExerciceMax3Entiers.py
1. """
2. Exemple afficher le maximum de trois entiers
3. """
4.
5. entier1 = int(input("entrez un nombre entier: "))
6. entier2 = int(input("entrez un nombre entier: "))
7. entier3 = int(input("entrez un nombre entier: "))
61
8.
9. if entier1 > entier2:
10. if entier1 > entier3:
11. print("Le maximum des trois entiers est :", entier1)
12. else:
13. print("Le maximum des trois entiers est :", entier3)
14. else:
15. if entier2 > entier3:
16. print("Le maximum des trois entiers est :", entier2)
17. else:
18. print("Le maximum des trois entiers est :", entier3)
Cette approche devient de plus en plus lourde et compliquée au fur et à
mesure que le nombre d’entiers à comparer augmente. Dans l’exercice
suivant, utilisez une répétition pour simplifier le code.
Solution. CodePython/chapitre3/ExerciceMax5Entiers.py
1. """
2. Afficher le maximum de 5 entiers lus
3. """
4.
5. le_maximum_actuel = int(input("entrez un nombre entier: "))
6. for compteur in range(4):
7. entier_lu = int(input("entrez un nombre entier: "))
8. if entier_lu > le_maximum_actuel:
9. le_maximum_actuel = entier_lu
10. print("Le maximum des 5 entiers lus est:",le_maximum_actuel)
Exercice *. Afficher les nombres premiers plus petits que 100.
0<=note<60 E
60<=note<70 D
70<=note<80 C
80<=note<90 B
90<=note<=100 A
Solution. CodePython/chapitre3/ExerciceNoteABCDE.py
1. """
2. Afficher la note littérale correspondant à la note numérique
3. """
4.
5. note = int(input("entrez une note entre 0 et 100 : "))
6. if note < 0:
7. print("La note doit ne peut être inférieure à 0")
62
8. else:
9. if note < 60:
10. print("E")
11. else :
12. if note < 70:
13. print("D")
14. else:
15. if note < 80:
16. print("C")
17. else:
18. if note < 90:
19. print("B")
20. else:
21. if note <= 100:
22. print("A")
23. else:
24. print("La note ne peut être supérieure à 100")
La clause elif qui est une abréviation de else if permet de produire le
même effet que dans la solution précédente en limitant l’indentation. Dans
un if, il peut y avoir plusieurs elif et à la fin un seul else optionnel.
L’effet est analogue à l’énoncé CASE ou SWITCH offert dans d’autres
langages.
CodePython/chapitre3/ExempleElif.py
1. """
2. Afficher la note littérale correspondant à la note numérique
avec la clause elif
3. """
4.
5. note = int(input("entrez une note entre 0 et 100 : "))
6. if note < 0:
7. print("La note doit ne peut être inférieure à 0")
8. elif note < 60:
9. print("E")
10. elif note < 70:
11. print("D")
12. elif note < 80:
13. print("C")
14. elif note < 90:
15. print("B")
16. elif note <= 100:
17. print("A")
18. else:
19. print("La note ne peut être supérieure à 100")
La version 3.10 de Python introduit l’énoncé match qui permet de
simplifier la syntaxe de comparaisons multiples en ajoutant des
fonctionnalités supplémentaires qui en font une structure très puissante et
versatile. L’exemple précédent peut être exprimé avec match de la manière
suivante :
63
1. """
2. Afficher la note littérale correspondant à la note numérique
avec match (python 3.10)
3. """
4.
5. note = int(input("entrez une note entre 0 et 100 : "))
6. match note :
7. case note if note < 0:
8. print("La note doit ne peut être inférieure à 0")
9. case note if note < 60:
10. print("E")
11. case note if note < 70:
12. print("D")
13. case note if note < 80:
14. print("C")
15. case note if note < 90:
16. print("B")
17. case note if note <= 100:
18. print("A")
19. case _:
20. print("La note ne peut être supérieure à 100")
CodePython/chapitre3/ExempleWhileBreak.py
1. """
2. Lire une suite d'entiers jusqu'à ce que l'entier 0 soit entré et afficher la
somme
3. des entiers lus. Exemple du break.
4. """
5.
6. somme=0
7. while True:
8. chaine = input("Entrez un nombre entier, 0 pour terminer :")
9. if chaine != '0' :
10. somme = somme + int(chaine)
11. else:
12. break
13. print("La somme des entiers est:",somme)
64
soit entré et produire la somme de ces entiers. Il faut omettre les entiers
négatifs du calcul. Dans la répétition, si l’entier lu est négatif, le continue
passe à l’itération suivante.
CodePython/chapitre3/ExempleContinue.py
1. """
2. Lire une suite d'entiers jusqu'à ce que l'entier 0 soit entré et afficher la
somme
3. des entiers lus. Omettre les entiers négatifs. Exemple du continue.
4. """
5.
6. somme=0
7. while True:
8. entier_lu = int(input("Entrez un nombre entier, 0 pour terminer :"))
9. if entier_lu > 0 :
10. somme = somme + entier_lu
11. elif entier_lu < 0:
12. continue
13. else:
14. break
15. print("La somme des entiers non négatifs est:",somme)
Solution. CodePython/chapitre3/ExerciceSommeNonNegMorse.py
1. """
2. Lire une suite d'entiers jusqu'à ce que l'entier 0 soit entré et afficher la somme
3. des entiers lus. Omettre les entiers négatifs. Exemple avec opérateur morse.
4. """
5.
6. somme=0
7. while (entier_lu := int(input("Entrez un nombre entier, 0 pour terminer :"))) != 0:
8. if entier_lu > 0 :
9. somme = somme + entier_lu
10. print("La somme des entiers non négatifs est:",somme)
65
Numéro de test Input Output
1 15 165
120
30
0
2 10 30
-5
20
0
3 0 0
4 2a Exception
Ce genre de test est dit fonctionnel étant donné qu’il vérifie que le
fonctionnement du programme est valide par rapport à sa spécification
fonctionnelle. Dans des programmes plus complexes, d’autres aspects
peuvent aussi être mesurés tel que le temps de calcul, la mémoire
consommée ou d’autres aspects dits non fonctionnels.
Dans l’approche de test par boîte noire ou opaque (black box testing), les tests
sont choisis sans examiner le code lui-même. On cherche à choisir les cas
de tests de manière à produire différentes combinaisons d’input qui
couvrent les différentes possibilités prévues dans la spécification du
programme. Dans l’approche de test par boîte blanche ou transparente
(white box testing, glass box testing), les tests sont conçus en tenant compte du
code. En particulier, il faut tenter de parcourir toutes les parties du code
par l’ensemble des tests.
66
des énoncés qui affichent l’état de certaines variables à différents endroits
du programme pour en suivre le déroulement d’une manière plus détaillée.
67
4 Types et expressions
Les types de base ont été introduits à la section 1.2.5. Ce chapitre
approfondit les notions de type et d’expression en Python.
Opération Priorité
+, - binaires 0 Basse
*, /, //, % 1
+, - unaires 2
** 3
(,) 4 Haute
L’expression suivante
3 + 2 * 6 - 3 - 2 * 4
est équivalente à
dont le résultat est 4. L’évaluation procède donc selon les étapes suivantes :
2+4**2*5+10/2
68
Conseil de génie logiciel
3.4 + 7
L’expression suivante
3.4 + 7
est équivalente à
3.4 + float(7)
69
du programme. La conversion en une représentation pour des entiers plus
larges est effectuée automatiquement au besoin.
>>> 2**500
3273390607896141870013189696827599152216642046043064789483291368096133796404
674554883270092325904157150886684127560071009217256545885393053328527589376
70
Python offre plusieurs modules prédéfinis. Le module math contient les
fonctions mathématiques usuelles (arrondissement, logarithmes,
trigonométrie, …) du langage C.
>>> math.ceil(3.4)
4
>>> math.floor(3.4)
3
>>> math.sqrt(16)
4.0
>>> math.log(8,2)
3.0
>>> math.log(math.e)
1.0
>>> math.cos(1)
0.5403023058681398
71
>>> math.cos(math.pi)
-1.0
>>> math.sin(math.pi/2)
1.0
En plus des types numériques de base, Python offre le type Decimal qui
représente des nombres décimaux exacts par opposition à float dont la
précision est limitée. Il y a aussi le type Fraction qui représente les nombres
rationnels. Le module random permet de produire des nombres pseudo
aléatoires selon différentes distributions. Le module statistics permet
de calculer diverses fonctions statistiques de base (moyenne, médiane,
mode, écart-type, etc.). Le projet SciPy contient plusieurs modules
employés dans des applications numériques avancés dont le très populaire
NumPy pour le calcul matriciel.
>>> a = 10
>>> a > 5 and a < 15
True
>>> a > 5 and a < 8
False
>>> a > 15 or a < 12
True
>>> a < 0 or a > 20
False
>>> a%5 == 0 or a%7 == 0
True
>>> a%3 == 0 or a%4 == 0
False
72
>>> 4*5+7 == 3*9 and 8/2*6 == 2*12 and not 4 < 6
False
Comme le and a une plus grande priorité que le or, le and est exécuté avant
dans l’exemple suivant :
Le and n’évalue pas la deuxième partie si la première est False. On dit que
l’évaluation est court-circuitée. En effet, si la première partie est fausse, le
résultat de la condition sera nécessairement faux. Il n’est donc pas
nécessaire d’évaluer la deuxième partie. L’exemple suivant montre une
utilisation avantageuse de cette manière de procéder. Le code vérifie que a
est un diviseur de 20. La deuxième condition n’a pas besoin d’être évaluée
si a est 0 ! Si elle était évaluée, une exception serait levée à cause de division
par 0.
>>> a = 10
>>> a != 0 and 20%a == 0
True
>>> a = 0
>>> a != 0 and 20%a == 0
False
>>> 20%0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: integer division or modulo by zero
73
classe n’implémente la méthode __bool__() qui peut retourner la valeur
False pour certains objets ou encore la méthode __len__() dont la valeur
0 se traduit par False.
- None et False.
- La valeur zéro pour chacun des types numériques : 0, 0.0, 0j,
Decimal(0), Fraction(0, 1)
- Les séquences et collections vides : '', (), [], {}, set(),
range(0)
74
Le tableau suivant contient des exemples de séquences d’échappement en
Python :
>>> print("\101\102\103")
ABC
>>> print("\x41\x42\x43")
ABC
75
plus répandue. Les codes Unicode, appelés points de code, sont énumérés
dans :#
http://www.unicode.org/Public/UNIDATA/UnicodeData.txt.
Chacun des points de code correspond à un entier qui est représenté par
quatre chiffres hexadécimaux. Par exemple, la lettre « A » correspond au
nombre hexadécimal à quatre chiffres 004116.
>>> print("\u0041\u0042\u0043")
ABC
UTF-8 est le codage employé par défaut pour le code source Python depuis
la version 3. Auparavant, c’était le code ASCII. Il est possible d’altérer le
codage employé par défaut en mettant un commentaire spécial à la première
ou deuxième ligne du code Python. Le commentaire suivant force
l’utilisation du codage UTF-8 :
76
>>> print(r"\u0041\u0042\u0043")
\u0041\u0042\u0043
Ainsi, l’opération + n’a pas le même effet sur une chaîne de caractère que
sur un nombre. Cependant, la concaténation sur les chaînes est en quelque
sorte analogue à l’addition sur les nombres.
>>> 3*'abc'
'abcabcabc'
>>> 3*'abc'*2
'abcabcabcabcabcabc'
>>> 3*'abc'+2*'def'
'abcabcabcdefdef'
>>> len('abcdef')
6
Le type str fait partie des séquences Python. Python offre plusieurs opérations
communes à tous les types séquence, dont l’accès par un indice à un
élément de la séquence. La notation une_chaine[i] retourne le caractère
en position i de la chaîne où i est un entier dans l’intervalle de 0 à n-1, n
est la longueur de la chaîne. Dans ce contexte, i joue le rôle d’un indice.
77
position (indice) 0 1 2 3 4 5
caractère a b c d e f
Un objet de type str est représenté par une suite contiguë de cases
mémoires.
Tableau (array)
position (indice) 0 1 2 3 4 5
caractère a b c d e f
78
En Python, il y a deux catégories d’objets importantes à distinguer : les
objets muables et immuables. Un objet muable peut être modifié par
opposition à un objet immuable.
Un str est immuable. Dans l’exemple suivant, une erreur est soulevée par
la tentative de modification d’un caractère de la chaîne :
Exercice. Lire une chaîne et vérifier si la lettre a est dans la chaîne lue.
Solution. CodePython/chapitre4/ExerciceCherchera.py
1. """
2. Lire une chaîne et vérifier si 'a' est dans la chaîne
3. """
4.
5. une_chaine = input("Entrez une chaîne de caractères :")
6. indice = 0
7. trouve = False
8. while indice < len(une_chaine) and not(trouve) :
9. if une_chaine[indice] == 'a' :
10. trouve = True
11. indice = indice + 1
12. print(trouve)
Cette manière de procéder est une implémentation de la recherche linéaire
qui sera étudiée plus en détails au chapitre 12. Une autre solution serait
d’interrompre le while par un break lorsqu’un a est trouvé. Ceci évite de
tester la variable booléenne trouve à chacune des itérations.
CodePython/chapitre4/ExempleBreak.py
1. """
2. Lire une chaîne et vérifier si 'a' est dans la chaîne
3. """
4.
5. une_chaine = input("Entrez une chaîne de caractères :")
6. indice = 0
7. trouve = False
8. while indice < len(une_chaine) :
9. if une_chaine[indice] == 'a' :
10. trouve = True
11. break
79
12. indice = indice + 1
13. print(trouve)
4.4.2 Itération avec un for sur une séquence
Le for permet d’itérer sur n’importe quelle séquence en parcourant chacun
de ses éléments un à un avec une syntaxe simple à coder et à comprendre.
Cette possibilité d’itérer sur les éléments est possible parce qu’un str
Python est un objet itérable. Cet aspect est étudié en détails à la section 7.5.
L’exemple suivant montre l’emploi du for pour itérer sur les éléments
d’une chaîne de caractères.
CodePython/chapitre4/ExempleForInChaine.py
1. """
2. Lire une chaîne et vérifier si 'a' est dans la chaîne avec un
for
3. """
4.
5. une_chaine = input("Entrez une chaîne de caractères :")
6. trouve = False
7. for un_caractere in une_chaine :
8. if un_caractere == 'a' :
9. trouve = True
10. break
11. print(trouve)
12.
La variable un_caractere prend successivement comme valeur chacun
des caractères de la chaîne. Cet exemple montre que le break peut aussi
être employé avec le for.
1. """
2. Lire une chaîne et vérifier si 'a' est dans la chaîne avec un
for et un indice
3. """
4.
5. une_chaine = input("Entrez une chaîne de caractères :")
6. trouve = False
7. for indice in range(len(une_chaine)) :
8. if une_chaine[indice] == 'a' :
9. trouve = True
10. break
11. print(trouve)
12.
Cette approche est typique de l’utilisation du for dans la plupart des
langages de programmation. Cependant, cette solution est moins conforme
80
à l’approche Python, dite pythonique, qui permet d’itérer directement avec
une variable qui parcoure la séquence itérable. De plus, le passage par un
indice est moins performant.
D’autre part, il est possible d’itérer sur les éléments et leurs indices de
manière pythonique en passant par la fonction enumerate(). L’exemple
suivant vérifie si le caractère a est présent et retourne sa position dans la
chaîne si c’est le cas :
CodePython/chapitre4/ExempleForEnumerate.py
1. """
2. Lire une chaîne et vérifier si 'a' est dans la chaîne.
3. Afficher la position en passant par un for avec enumerate.
4. """
5.
6. une_chaine = input("Entrez une chaîne de caractères :")
7. trouve = False
8.
9. for indice, un_caractere in enumerate(une_chaine) :
10. if un_caractere == 'a' :
11. trouve = True
12. indice_a = indice
13. break
14. if trouve :
15. print("Le caractère a est présent à la position :",
indice_a)
16. else :
17. print("Le caractère a n'est pas dans la chaîne")
Le enumerate() permet d’itérer sur les couples (indice, élément)
pour chacun des éléments de la séquence.
>>> s ='0123456789'
>>> s[3:7]
'3456'
81
La notation s[i:] retourne la sous-séquence des éléments d’indice k, tel
que i<=k<len(s).
>>> s[4:]
'456789'
>>> s[:5]
'01234'
>>> s[2:9:2]
'2468'
>>> s[5:2:-1]
'543'
>>> s[::-1]
'9876543210'
>>> s[-1:-11:-1]
'9876543210'
>>> s[8:2:-2]
'864'
Solution.
'987654321'
Solution.
''
82
Exercice. Quel est le résultat de s[len(s):-2:-1]
Solution.
'9'
Solution.
'98765432'
>>> un_str.find('e')
-1
>>> un_str.find('a',4,7)
5
83
2
>>> un_str.count('a',4,7)
1
>>> un_str.count('a',4,9)
2
https://docs.python.org/fr/3/library/stdtypes.html#str
>>> len(liste_de_int)
6
La fonction list() permet de produire une liste à partir d’un autre type
de séquence tel qu’un range() :
>>> list(range(4))
[0, 1, 2, 3]
Un tableau à deux dimensions peut être représenté par une liste de listes.
>>> t = [[1,2,3],[4,5,6]]
>>> t
84
[[1, 2, 3], [4, 5, 6]]
>>> liste_fruits = []
>>> liste_fruits = liste_fruits + ['orange']
>>> liste_fruits
['orange']
>>> liste_fruits = liste_fruits + ['pomme']
>>> liste_fruits
['orange', 'pomme']
>>> liste_de_int[2]
7
>>> liste_de_noms[1]
'Jean'
>>> liste_melangee[3]
['golf', 'tennis', 'hockey']
Les objets d’une liste sont ordonnés en ce sens qu’ils sont en ordre de
l’indice. Cependant, l’ordre dépend de la manière dont les objets sont placés
dans la liste soit à sa création ou par les opérations de modification, et non
pas l’ordre des objets eux-mêmes. Par exemple, dans liste_de_int, les
entiers ne sont pas en ordre numérique. La méthode sort() permet de
trier les objets en fonction de l’ordre défini sur ces objets (voir plus loin).
Dans le cas d’une liste de listes, il est possible d’employer une suite d’indices
qui sont appliqués en série.
>>> t
[[1, 2, 3], [4, 5, 6]]
>>> t[1]
[4, 5, 6]
>>> t[1][2]
6
85
4.5.2 Itération avec for
Le for permet de parcourir les éléments d’une liste de manière analogue à
l’itération sur une chaîne. Dans l’exemple suivant, le for parcoure les
éléments de la liste un par un et les affiche.
CodePython/chapitre4/ExerciceSuperieurAMoyenne.py
1. """
2. Lire 5 entiers, afficher la moyenne et les entiers supérieurs à
la moyenne.
3. """
4. somme = 0
5. liste_de_int = []
6. for indice in range(5):
7. un_int = int(input('Entrez un entier:'))
8. somme = somme + un_int
9. liste_de_int = liste_de_int + [un_int]
10. moyenne = somme / 5
11. print('La moyenne est :',moyenne)
12. print('Liste des entiers lus supérieurs à la moyenne :')
13. for un_int in liste_de_int:
86
14. if un_int> moyenne :
15. print(un_int)
Un aspect important à considérer dans cet exercice concerne l’utilisation
d’une liste pour mémoriser les données lues afin de pouvoir les parcourir à
nouveau après le calcul de la moyenne.
>>> liste_de_int
[5, 2, 7, 3, 10, -4]
>>> liste_de_int[2] = 5
>>> liste_de_int
[5, 2, 5, 3, 10, -4]
Il est permis de modifier une tranche d’une liste en remplaçant celle-ci par
une liste dont la taille peut être différente.
87
[5, 2, 9, -4]
>>> liste_de_int[1:3] = []
>>> liste_de_int
[5, -4]
>>> liste_de_int.count(3)
4
>>> liste_de_int.append(2)
>>> liste_de_int
[4, 2, 3, 3, 5, 1, 8, 3, 4, 3, 2]
>>> liste_de_int.pop(4)
5
>>> liste_de_int
[4, 2, 3, 3, 1, 8, 3, 4, 3, 2]
88
>>> liste_de_int = [4,2,3,3,5,1,8,3,4,3]
>>> liste_triee = sorted(liste_de_int)
>>> liste_de_int
[4, 2, 3, 3, 5, 1, 8, 3, 4, 3]
>>> liste_triee
[1, 2, 3, 3, 3, 3, 4, 4, 5, 8]
list1
P a u l 10
7 4 6
list2
list1 list3
P a u l 10
7 4 6
list4
>>> list1[1]=20
>>> list1
['Paul', 20, [7, 4, 6]]
>>> list2
['Paul', 20, [7, 4, 6]]
>>> list3
['Paul', 10, [7, 4, 6]]
>>> list4
['Paul', 10, [7, 4, 6]]
20
list2
list1 list3
P a u l 10
7 4 6
list4
>>> list1[2][0]=15
>>> list1
['Paul', 20, [15, 4, 6]]
>>> list2
['Paul', 20, [15, 4, 6]]
>>> list3
['Paul', 10, [15, 4, 6]]
>>> list4
['Paul', 10, [7, 4, 6]]
91
20
list2
list1 list3
P a u l 10
15
7 4 6
list4
92
20 list5
list2
list1 list3
P a u l 10
15
7 4 6
list4
>>> list1[1]=30
>>> list5
[20, [15, 4, 6]]
>>> list1[2][1]=25
>>> list5
[20, [15, 25, 6]]
93
30 20 list5
list2
list1 list3
P a u l 10
15 25
7 4 6
list4
Solution:
>>> list1
['Paul', 100, 30, ['golf', 'tennis', 'surf']]
>>> list2
['Paul', 100, 30, ['golf', 'tennis', 'surf']]
>>> list3
['Paul', 150, 30, ['golf', 'tennis', 'surf']]
>>> list4
['Paul', 150, 30, ['golf', 'tennis', 'hockey']]
>>> list5
[150, 30, ['golf', 'tennis', 'surf']]
94
4.5.8 Liste en compréhension
Le mécanisme de liste en compréhension permet de construire une liste
avec une syntaxe simplifiée. L’exercice suivant permet de motiver le
concept.
Solution.
>>> liste_de_int_pairs = []
>>> for indice in range(0,11,2) :
... liste_de_int_pairs.append(indice)
...
>>> liste_de_int_pairs
[0, 2, 4, 6, 8, 10]
Il est possible d’inclure une clause if pour exclure des éléments du résultat.
Ici, la liste retourne les entiers entre 0 et 9 qui ne sont pas des multiples de
3.
Enfin, il est permis de combiner plusieurs for imbriqués. Ici, les for
imbriqués produisent la liste des paires d’entiers [i,j] où 0<=i <3,
0<=j <3 :
95
4.6 Type tuple
Le type tuple (n-uplet) est très proche du type list. Comme pour le type
list, le type tuple permet de représenter une séquence d’éléments de
types variés possiblement hétérogène. Cependant, le type tuple est
immuable.
>>> t = ('Paul',150,30,['golf','tennis','hockey'])
>>> t
('Paul', 150, 30, ['golf', 'tennis', 'hockey'])
>>> tuple(range(4))
(0, 1, 2, 3)
>>> tuple([4,2,7])
(4, 2, 7)
>>> nuplet_de_int
(5, 2, 7, 3, 10, -4)
>>> nuplet_de_int[2] = 5
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'tuple' object does not support item assignment
96
En revanche, si le tuple contient un objet muable comme une list, il est
permis de modifier le contenu de la liste :
>>> t = ('Paul',150,30,['golf','tennis','hockey'])
>>> t
('Paul', 150, 30, ['golf', 'tennis', 'hockey'])
>>> t[3][1]=5
>>> t
('Paul', 150, 30, ['golf', 5, 'hockey'])
>>> t = 1,2+1,3*4
>>> t
(1, 3, 12)
>>> t = (1,2+1,3*4)
>>> t
(1, 3, 12)
Lorsqu’une expression unique est suivie d’une virgule, elle est convertie en
un n-uplet qui contient un élément (singleton).
>>> t = 2,
>>> t
(2,)
Note
Une particularité de Python est de permettre une virgule sans qu’un élément
ne suive dans le contexte d’une suite d’éléments séparés par de virgules.
Ainsi (2,) est équivalent à (2).
97
On peut aussi employer la syntaxe suivante qui rend plus explicite le tuple
sous-jacent.
>>> s1 = set([1,2,3,2])
>>> s1
{1, 2, 3}
Il est aussi possible d’employer les accolades pour désigner un littéral set.
>>> s1 = {1,2,3,2}
>>> s1
{1, 2, 3}
>>> s3 = {}
>>> s3
{}
>>> type(s3)
<class 'dict'>
>>> set_melangee=set(['Paul',150,30,('golf','tennis','hockey')])
>>> s5 = set(['Paul',150,30,['golf','tennis','hockey']])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'list'
>>> set_melangee
{'Paul', ('golf', 'tennis', 'hockey'), 150, 30}
Un set n’est pas une séquence Python. Les éléments ne sont pas ordonnés
et l’adressage par indice n’est pas permis :
>>> s1
{1, 2, 3}
>>> s1[1]
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'set' object is not subscriptable
>>> 2 in s1
True
>>> 5 in s1
False
>>> len(s1)
3
Les opérations ensemblistes sont définies pour le type set. L’union est
exprimée par |, l’intersection par &, la différence par – et la différence
symétrique par ^ :
99
>>> s1
{1, 2, 3}
>>> s2 = {4,3,2}
>>> s2
{2, 3, 4}
>>> s1|s2
{1, 2, 3, 4}
>>> s1&s2
{2, 3}
>>> s1-s2
{1}
>>> s1^s2
{1, 4}
Ces opérations peuvent aussi être effectuées par les méthodes union(),
intersection(), difference() et symmetric_difference(). Les opérations ensemblistes ne
sont pas permises sur les list :
Les opérations d’inclusion ensembliste sont définies par les opérateurs <,
<=, >, >= :
>>> {2,3}<s1
True
>>> s1<=s1
True
>>> s1<s2
False
Un set est muable même si ses éléments ne sont pas muables, et plusieurs
opérations et méthodes permettent de le modifier. Pour chacun des
opérateurs ensemblistes (|,&,-,^), il y a un opérateur de modification
correspondant. Le |= ajoute les éléments du set à droite de l’opération à la
variable set à gauche, ce qui est l’équivalent d’affecter l’union des deux
ensembles à la variable de gauche.
>>> s1 = {3,1,2}
>>> s2 = {3,4,2}
>>> s1 |= s2
>>> s1
{1, 2, 3, 4}
>>> s1 = {3,1,2}
100
>>> s2 = {3,4,2}
>>> s1 &= s2
>>> s1
{2, 3}
>>> s1 = {3,1,2}
>>> s2 = {3,4,2}
>>> s1 -= s2
>>> s1
{1}
>>> s1 = {3,1,2}
>>> s2 = {3,4,2}
>>> s1 ^= s2
>>> s1
{1, 4}
>>> s1 = {3,1,2}
>>> s1.add(5)
>>> s1
{1, 2, 3, 5}
>>> s1 = {3,1,2}
>>> s1.remove(2)
>>> s1
{1, 3}
>>> s1 = {3,1,2}
>>> s1.remove(7)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 7
>>> fs1=frozenset([3,1,2])
>>> fs1.add(4)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'frozenset' object has no attribute 'add'
101
Les opérateurs de mise à jour sont permis mais l’effet est de créer un nouvel
objet. L’exemple suivant montre qu’après la mise-à-jour par l’union des deux
ensembles, le id de fs1 a changé.
>>> id(fs1)
1313491197504
>>> fs1|= frozenset([4])
>>> fs1
frozenset({1, 2, 3, 4})
>>> id(fs1)
1313491197056
Un set est représenté avec une table de hachage (hash table). Le principe du
hachage est d’appliquer une fonction de hachage à un élément qui
détermine son adresse dans un tableau. Il est important que la fonction
répartisse de manière uniforme les éléments dans le tableau. Lorsque
différents éléments produisent la même adresse par la fonction de hachage,
il y a collision et différentes stratégies peuvent être appliquées pour
résoudre les collisions. Comme le calcul se fait rapidement, l’accès est très
rapide. Les éléments doivent être hachables.
Un littéral dict est encadré par des accolades et contient une suite de
couples clé :valeur séparés par des virgules. L’exemple suivant exemple
crée un répertoire téléphonique qui associe un numéro de téléphone (la
valeur) à un nom (la clé) :
102
{'Pierre': '514-333-3333', 'Jean': '514-222-2222', 'Jacques': '514-555-
5555'}
>>> repertoire_telephonique['Jean']
'514-222-2222'
>>> repertoire_telephonique['Paul']
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'Paul'
La méthode keys() retourne la liste des clés du dict sous forme d’une
view :
>>> repertoire_telephonique.keys()
dict_keys(['Pierre', 'Jean', 'Jacques'])
>>> repertoire_telephonique.values()
dict_values(['514-333-3333', '514-222-2222', '514-555-5555'])
>>> len(repertoire_telephonique)
3
103
La syntaxe del nom_dict[cle] supprime le couple correspondant à la clé
spécifiée :
104
Indice Attribut Valeurs
0 tm_year Année à quatre chiffres, 1993
105
Le module datetime permet d’effectuer des calculs de temps en
incorporant le type timedelta qui représente une durée de temps avec les
unités plus usuelles (seconde, heure, jour, mois, …).
106
5 Graphisme 2D et fonctions
Ce chapitre présente les principes de base des graphiques en deux
dimensions (2D) et de la gestion d’évènements avec la bibliothèque
Pygame. Il introduit aussi la création de fonction en la justifiant dans le
contexte d’un programme simple de graphisme 2D.
Pygame nécessite Python ; si vous ne l'avez pas encore installé sur votre
machine, vous devez maintenant le faire. La meilleure façon d'installer
pygame est d'utiliser l'outil pip. Notez que cet outil est fourni avec les
versions récentes de Python. Nous utilisons le drapeau --user pour lui
demander d'installer dans le répertoire personnel, plutôt que globalement.
Rappelez-vous que si vous êtes sur une machine où Python 2 est présent
par défaut, il peut être nécessaire de remplacer la commande python par
python3. Pour voir si cela fonctionne, exécutez l'un des exemples inclus :
python -m pygame.examples.aliens
Si un jeu démarre, vous êtes prêt à partir ! Si ce n'est pas le cas, des
instructions plus détaillées et spécifiques à la plate-forme vous concernant
sont disponibles en ligne : https://www.pygame.org/wiki/GettingStarted
107
Le programme ExempleDessin2DPygame dessine un bonhomme simple
(appelons-le Bot) dans une fenêtre produite avec Pygame.
CodePython/chapitre5/ ExempleDessin2DEtFermetureDeFenetre.py
1. """
2. Exemple de dessin 2D avec pygame
3. """
4. # Importer la librairie de pygame et initialiser
5. import sys, pygame
6. pygame.init()
7.
8. LARGEUR_FENETRE = 400
9. HAUTEUR_FENETRE = 600
10. fenetre = pygame.display.set_mode((LARGEUR_FENETRE, HAUTEUR_FENETRE)) # Ouvrir
la fenêtre
11.
12. pygame.display.set_caption('Exemple de dessin 2D avec pygame') # Définir le
titre dans le haut de la fenêtre
13.
14. # Définir les couleurs employées dans le dessin
15. BLANC = (255,255,255)
16. ROUGE = (255,0,0)
17. NOIR = (0,0,0)
18. VERT = (0,255,0)
19.
20. fenetre.fill(BLANC) # Dessiner le fond de la surface de dessin
21.
22. pygame.draw.ellipse(fenetre, VERT, ((100,100),(200,200))) # Dessiner la tête
23. pygame.draw.rect(fenetre, NOIR, ((150,150),(20,20))) # L'oeil gauche
24. pygame.draw.rect(fenetre, NOIR, ((230,150),(20,20))) # L'oeil droit
25. pygame.draw.line(fenetre, NOIR, (150,250),(250,250),2) # La bouche
26. pygame.draw.rect(fenetre, ROUGE, ((100,300),(200,200))) # Le corps
27.
28. pygame.display.flip() # Mettre à jour la fenêtre graphique
29.
30. # Traiter la fermeture de la fenêtre
31. while True:
32. for event in pygame.event.get():
33. if event.type == pygame.QUIT: # Vérifier si l'utilisateur a cliqué
pour fermer la fenêtre
34. pygame.quit() # Terminer pygame
35. sys.exit()
36.
108
La fenêtre suivante est affichée :
109
0 100 150 200 230 300 x
0
100
(150,150) (230,150)
20
150
200
(150,250) (250,250)
250
300
400
500
import pygame
pygame.init()
Constantes en Python
Par convention les noms de variables qui représentent des constantes sont
en majuscules. Cependant, ce ne sont pas de vrais constantes et la valeur de
110
ces variables peut être modifiée. La version 3.4 de Python introduit le
concept d’énumération (enum) qui permet de définir des constantes.
LARGEUR_FENETRE = 400
HAUTEUR_FENETRE = 600
fenetre = pygame.display.set_mode((LARGEUR_FENETRE, HAUTEUR_FENETRE)) #
Ouvrir la fenêtre
Les lignes suivantes définissent des constantes pour les couleurs employées
dans le dessin. Chacune des couleurs est un n-uplet de trois entiers entre 0
et 255 qui correspondent aux trois couleurs, rouge (Red), vert (Green) et bleu
(Bleu), du système RVB. Il y a aussi un quatrième argument optionnel,
l’opacité, qui n’est pas employé ici. Chacun des entiers définit l’intensité
lumineuse de la couleur correspondante. Le 0 est l’absence de couleur et
255 est l’intensité maximale. Le blanc est la combinaison des trois couleurs
dans un tel système. Le noir est l’absence de couleur.
BLANC = (255,255,255)
ROUGE = (255,0,0)
NOIR = (0,0,0)
VERT = (0,255,0)
Pour déterminer les valeurs RVB d’une couleur particulière, il est possible
d’employer un outil tel que :
http://www.colorpicker.com/
CodePython/chapitre5/ExempleDessin2DClasseColor.py
1. """
2. Exemple de dessin 2D avec pygame qui emploie la classe pygame.Color
3. """
112
4. # Importer la librairie de pygame et initialiser
5. import sys, pygame
6. from pygame import Color
7. pygame.init()
8.
9. LARGEUR_FENETRE = 400
10. HAUTEUR_FENETRE = 600
11. fenetre = pygame.display.set_mode((LARGEUR_FENETRE, HAUTEUR_FENETRE)) # Ouvrir la fenêtre
12.
13. pygame.display.set_caption('Exemple de dessin 2D avec pygame') # Définir le titre dans le
haut de la fenêtre
14.
15. fenetre.fill(Color('white')) # Dessiner le fond de la surface de dessin
16.
17. pygame.draw.ellipse(fenetre, Color('green'), ((100,100),(200,200))) # Dessiner la tête
18. pygame.draw.rect(fenetre, Color('black'), ((150,150),(20,20))) # L'oeil gauche
19. pygame.draw.rect(fenetre, Color('black'), ((230,150),(20,20))) # L'oeil droit
20. pygame.draw.line(fenetre, Color('black'), (150,250),(250,250),2) # La bouche
21. pygame.draw.rect(fenetre, Color('red'), ((100,300),(200,200))) # Le corps
22.
23. pygame.display.flip() # Mettre à jour la fenêtre graphique
24.
25. # Traiter la fermeture de la fenêtre
26. while True:
27. for event in pygame.event.get():
28. if event.type == pygame.QUIT: # Vérifier si l'utilisateur a cliqué pour fermer la
fenêtre
29. pygame.quit() # Terminer pygame
30. sys.exit()
Solution. CodePython/chapitre5/ExerciceDessinIti.py
1. """
2. Exercice : bonhomme Iti
3. """
4. # Importer la bibliothèque de pygame et initialiser
5. import sys, pygame
6. from pygame import Color
7. pygame.init()
8.
113
9. LARGEUR_FENETRE = 300
10. HAUTEUR_FENETRE = 300
11. fenetre = pygame.display.set_mode((LARGEUR_FENETRE, HAUTEUR_FENETRE)) # Ouvrir la fenêtre
12.
13. pygame.display.set_caption('Exercice bonhomme Iti avec pygame') # Définir le titre dans le
haut de la fenêtre
14.
15. fenetre.fill(Color('white')) # Dessiner le fond de la surface de dessin
16.
17. pygame.draw.ellipse(fenetre, Color('pink'), ((133, 50), (33, 50))) # Dessiner la tête
18. pygame.draw.arc(fenetre, Color('black'),((140,75),(19,15)),3.1416,0,1) # Le sourire
19. pygame.draw.ellipse(fenetre, Color('black'), ((138,66),(8,8))) # L'oeil gauche
20. pygame.draw.ellipse(fenetre, Color('black'), ((154,66),(8,8))) # L'oeil droit
21. pygame.draw.line(fenetre, Color('black'), (150,100), (150,200), 2) # Le corps
22. pygame.draw.line(fenetre, Color('black'), (100,100), (150,150), 2) # Bras gauche
23. pygame.draw.line(fenetre, Color('black'), (200,100), (150,150), 2) # Bras droit
24. pygame.draw.line(fenetre, Color('black'), (100,250), (150,200), 2) # Jambe gauche
25. pygame.draw.line(fenetre, Color('black'), (200,250), (150,200), 2) # Jambe droite
26.
27. pygame.display.flip() # Mettre à jour la fenêtre graphique
28.
29. # Traiter la fermeture de la fenêtre
30. while True:
31. for event in pygame.event.get():
32. if event.type == pygame.QUIT: # Vérifier si l'utilisateur a cliqué pour fermer la
fenêtre
33. pygame.quit() # Terminer pygame
34. sys.exit()
114
Solution. CodePython/chapitre5/ExerciceDessin2Bots.py
1. """
2. Exercice : dessin de 2 bots
3. """
4. # Importer la bibliothèque de pygame et initialiser
5. import sys, pygame
6. from pygame import Color
7. pygame.init()
8.
9. LARGEUR_FENETRE = 400
10. HAUTEUR_FENETRE = 600
11. fenetre = pygame.display.set_mode((LARGEUR_FENETRE, HAUTEUR_FENETRE)) # Ouvrir la fenêtre
12.
13. pygame.display.set_caption('Dessin de 2 Bots') # Définir le titre dans le haut de la
fenêtre
14.
15. fenetre.fill(Color('white')) # Dessiner le fond de la surface de dessin
16. # Le premier Bot
17. pygame.draw.ellipse(fenetre, Color('green'), ((100, 100),(200, 200))) # Dessiner la tête
18. pygame.draw.rect(fenetre, Color('black'), ((150, 150),(20, 20))) # L'oeil gauche
19. pygame.draw.rect(fenetre, Color('black'), ((230, 150),(20, 20))) # L'oeil droit
20. pygame.draw.line(fenetre, Color('black'), (150,250),(250,250), 2) # La bouche
21. pygame.draw.rect(fenetre, Color('red'), ((100, 300),(200, 200))) # Le corps
22.
23. # Le deuxième Bot
24. pygame.draw.ellipse(fenetre, Color('green'), ((25,50),(100,100))) # Dessiner la tête
25. pygame.draw.rect(fenetre, Color('black'), ((50, 75),(10, 10))) # L'oeil gauche
26. pygame.draw.rect(fenetre, Color('black'), ((90,75),(10,10))) # L'oeil droit
27. pygame.draw.line(fenetre, Color('black'), (50,125),(100,125), 2) # La bouche
28. pygame.draw.rect(fenetre, Color('red'), ((25,150),(100,100))) # Le corps
29.
30. pygame.display.flip() # Mettre à jour la fenêtre graphique
31.
32. # Traiter la fermeture de la fenêtre
33. while True:
34. for event in pygame.event.get():
35. if event.type == pygame.QUIT: # Vérifier si l'utilisateur a cliqué pour fermer la
fenêtre
36. pygame.quit() # Terminer pygame
37. sys.exit()
115
La solution précédente rappelle les mêmes fonctions deux fois pour
dessiner les deux Bot. Pour la position et la taille du Bot, il faut calculer de
nouvelles valeurs des arguments à chaque fois. Une manière plus élégante
de traiter ce problème consiste à chercher une solution plus générale au
dessin d’un Bot où sa position et sa taille sont variables. Une technique
souvent employée en graphisme 2D consiste à définir un rectangle englobant
à l’intérieur duquel est dessiné la figure tel que vu précédemment pour le
dessin d’une ellipse avec pygame.draw.ellipse(). Ainsi, le Bot est
dessiné à l’échelle à l’intérieur du rectangle englobant. Comme pour
pygame.draw.ellipse(), quatre paramètres sont définis pour
représenter le rectangle englobant : les coordonnées x et y du coin
supérieur gauche, la largeur et la hauteur du rectangle englobant. La
figure suivante montre les dimensions des différentes parties du Bot
relativement à ces paramètres.
116
0 x
0
(x,y)
largeur
(x+largeur/4,y+hauteur/8) (x+largeur*3/4-largeur/10,y+hauteur/8)
largeur/10
(x+largeur/4,y+hauteur*3/8) (x+largeur*3/4,y+hauteur*3/8)
hauteur
La version suivante dessine le même Bot que notre premier exemple mais
en utilisant des variables qui représentent le rectangle englobant. Les
paramètres des figures correspondent aux valeurs de la figure précédente.
CodePython/chapitre5/ExempleBotRectangleEnglobant.py
1. """
2. Exemple de dessin du Bot dans un rectangle englobant
3. """
4. # Importer la bibliothèque de pygame et initialiser
5. import sys,pygame
6. from pygame import Color
7. pygame.init()
8.
9. LARGEUR_FENETRE = 400
117
10. HAUTEUR_FENETRE = 600
11. fenetre = pygame.display.set_mode((LARGEUR_FENETRE, HAUTEUR_FENETRE)) # Ouvrir la fenêtre
12.
13. pygame.display.set_caption('Exemple de dessin du Bot dans un rectangle englobant') #
Définir le titre dans le haut de la fenêtre
14.
15. fenetre.fill(Color('white')) # Dessiner le fond de la surface de dessin
16.
17. r = pygame.Rect((100,100),(200,400)) # le rectangle englobant
18.
19. # Dessiner le Bot relativement au rectangle englobant r
20. pygame.draw.ellipse(fenetre, Color('green'), ((r.x,r.y),(r.width, r.height/2))) # Dessiner
la tête
21. pygame.draw.rect(fenetre, Color('black'),
((r.x+r.width/4,r.y+r.height/8),(r.width/10,r.height/20))) # L'oeil gauche
22. pygame.draw.rect(fenetre, Color('black'), ((r.x+r.width*3/4-
r.width/10,r.y+r.height/8),(r.width/10,r.height/20))) # L'oeil droit
23. pygame.draw.line(fenetre, Color('black'),
(r.x+r.width/4,r.y+r.height*3/8),(r.x+r.width*3/4,r.y+r.height*3/8), 2) # La bouche
24. pygame.draw.rect(fenetre, Color('red'), ((r.x,r.y+r.height/2),(r.width,r.height/2))) # Le
corps
25.
26. pygame.display.flip() # Mettre à jour la fenêtre graphique
27.
28. # Traiter la fermeture de la fenêtre
29. while True:
30. for event in pygame.event.get():
31. if event.type == pygame.QUIT: # Vérifier si l'utilisateur a cliqué pour fermer la
fenêtre
32. pygame.quit() # Terminer pygame
33. sys.exit()
CodePython/chapitre5/Exemple2BotsRectangleEnglobant.py
1. """
2. Exemple de dessin de 2 Bots dans un rectangle englobant
3. """
4. # Importer la bibliothèque de pygame et initialiser
5. import sys,pygame
6. from pygame import Color
7. pygame.init()
8.
9. LARGEUR_FENETRE = 400
10. HAUTEUR_FENETRE = 600
11. fenetre = pygame.display.set_mode((LARGEUR_FENETRE, HAUTEUR_FENETRE)) # Ouvrir la fenêtre
12.
13. pygame.display.set_caption('Exemple de dessin du Bot dans un rectangle englobant') #
Définir le titre dans le haut de la fenêtre
14.
15. fenetre.fill(Color('white')) # Dessiner le fond de la surface de dessin
16.
17. # Rectangle englobant du premier Bot
18. r = pygame.Rect((100,100),(200,400)) # le rectangle englobant
19.
20. # Dessiner le Bot relativement au rectangle englobant r
21. pygame.draw.ellipse(fenetre, Color('green'), ((r.x,r.y),(r.width, r.height/2))) # Dessiner
la tête
22. pygame.draw.rect(fenetre, Color('black'),
((r.x+r.width/4,r.y+r.height/8),(r.width/10,r.height/20))) # L'oeil gauche
23. pygame.draw.rect(fenetre, Color('black'), ((r.x+r.width*3/4-
r.width/10,r.y+r.height/8),(r.width/10,r.height/20))) # L'oeil droit
24. pygame.draw.line(fenetre, Color('black'),
(r.x+r.width/4,r.y+r.height*3/8),(r.x+r.width*3/4,r.y+r.height*3/8), 2) # La bouche
25. pygame.draw.rect(fenetre, Color('red'), ((r.x,r.y+r.height/2),(r.width,r.height/2))) # Le
corps
26.
27. # Rectangle englobant pour le deuxième Bot
118
28. r = pygame.Rect((25,50),(100,200)) # le rectangle englobant
29.
30. # Dessiner le Bot relativement au rectangle englobant r
31. pygame.draw.ellipse(fenetre, Color('green'), ((r.x,r.y),(r.width, r.height/2))) # Dessiner
la tête
32. pygame.draw.rect(fenetre, Color('black'),
((r.x+r.width/4,r.y+r.height/8),(r.width/10,r.height/20))) # L'oeil gauche
33. pygame.draw.rect(fenetre, Color('black'), ((r.x+r.width*3/4-
r.width/10,r.y+r.height/8),(r.width/10,r.height/20))) # L'oeil droit
34. pygame.draw.line(fenetre, Color('black'),
(r.x+r.width/4,r.y+r.height*3/8),(r.x+r.width*3/4,r.y+r.height*3/8), 2) # La bouche
35. pygame.draw.rect(fenetre, Color('red'), ((r.x,r.y+r.height/2),(r.width,r.height/2))) # Le
corps
36.
37. pygame.display.flip() # Mettre à jour la fenêtre graphique
38.
39. # Traiter la fermeture de la fenêtre
40. while True:
41. for event in pygame.event.get():
42. if event.type == pygame.QUIT: # Vérifier si l'utilisateur a cliqué pour fermer la
fenêtre
43. pygame.quit() # Terminer pygame
44. sys.exit()
45.
CodePython/chapitre5/ExempleFonctionDessinerBot.py
1. """
2. Exemple de fonction dessiner_bot
3. """
4.
5. import sys,pygame
6. from pygame import Color
7.
8. def dessiner_bot(fenetre,r):
9. """ Dessiner un Bot.
10.
11. fenetre : la surface de dessin
12. r : rectangle englobant de type pygame.Rect
13. """
14.
15. # Dessiner le Bot relativement au rectangle englobant r
16. pygame.draw.ellipse(fenetre, Color('green'), ((r.x,r.y),(r.width, r.height/2))) #
Dessiner la tête
17. pygame.draw.rect(fenetre, Color('black'),
((r.x+r.width/4,r.y+r.height/8),(r.width/10,r.height/20))) # L'oeil gauche
18. pygame.draw.rect(fenetre, Color('black'), ((r.x+r.width*3/4-
r.width/10,r.y+r.height/8),(r.width/10,r.height/20))) # L'oeil droit
19. pygame.draw.line(fenetre, Color('black'),
(r.x+r.width/4,r.y+r.height*3/8),(r.x+r.width*3/4,r.y+r.height*3/8), 2) # La bouche
20. pygame.draw.rect(fenetre, Color('red'), ((r.x,r.y+r.height/2),(r.width,r.height/2))) #
Le corps
21.
22. pygame.init() # Initialiser Pygame
23. LARGEUR_FENETRE = 400
119
24. HAUTEUR_FENETRE = 600
25. fenetre = pygame.display.set_mode((LARGEUR_FENETRE, HAUTEUR_FENETRE)) # Ouvrir la fenêtre
26. pygame.display.set_caption('Exemple de dessin du Bot dans un rectangle englobant') #
Définir le titre dans le haut de la fenêtre
27.
28. BLANC = (255,255,255)
29. fenetre.fill(Color('white')) # Dessiner le fond de la surface de dessin
30.
31. # Dessiner deux Bots en appelant la fonction à deux reprises
32. dessiner_bot(fenetre,pygame.Rect((100,100),(200,400)))
33. dessiner_bot(fenetre,pygame.Rect((25,50),(100,200)))
34.
35. pygame.display.flip() # Mettre à jour la fenêtre graphique
36.
37. # Traiter la fermeture de la fenêtre
38. while True:
39. for event in pygame.event.get():
40. if event.type == pygame.QUIT: # Vérifier si l'utilisateur a cliqué pour fermer la
fenêtre
41. pygame.quit() # Terminer pygame
42. sys.exit()
def dessiner_bot(fenetre,r):
120
des paramètres comme s’ils étaient des variables. Par exemple, dans la ligne
suivante, les variables fenetre et r sont les paramètres de la fonction :
dessiner_bot(fenetre,pygame.Rect((100,100),(200,400)))
121
CodePython/chapitre5/ExempleFonctionNomParametreDifferent.py
1. """
2. Exemple de fonction dessiner_bot
3. Nom de paramètre et d'argument différents
4. """
5.
6. import sys, pygame
7. from pygame import Color
8.
9. def dessiner_bot(f,r):
10. """ Dessiner un Bot.
11.
12. fenetre : la surface de dessin
13. r : rectangle englobant de type pygame.Rect
14. """
15.
16. # Dessiner le Bot relativement au rectangle englobant r
17. pygame.draw.ellipse(f, Color('green'), ((r.x,r.y),(r.width, r.height/2))) # Dessiner la
tête
18. pygame.draw.rect(f, Color('black'),
((r.x+r.width/4,r.y+r.height/8),(r.width/10,r.height/20))) # L'oeil gauche
19. pygame.draw.rect(f, Color('black'), ((r.x+r.width*3/4-r.width/10, r.y+r.height/8),
(r.width/10,r.height/20))) # L'oeil droit
20. pygame.draw.line(f, Color('black'), (r.x+r.width/4,r.y+r.height*3/8), (r.x+r.width*3/4,
r.y+r.height*3/8), 2) # La bouche
21. pygame.draw.rect(f, Color('red'), ((r.x,r.y+r.height/2), (r.width,r.height/2))) # Le
corps
22.
23. pygame.init() # Initialiser Pygame
24. LARGEUR_FENETRE = 400
25. HAUTEUR_FENETRE = 600
26. fenetre = pygame.display.set_mode((LARGEUR_FENETRE, HAUTEUR_FENETRE)) # Ouvrir la fenêtre
27. pygame.display.set_caption('Exemple de dessin du Bot dans un rectangle englobant') #
Définir le titre dans le haut de la fenêtre
28.
29. BLANC = (255,255,255)
30. fenetre.fill(BLANC) # Dessiner le fond de la surface de dessin
31.
32. # Dessiner deux Bots en appelant la fonction à deux reprises
33. dessiner_bot(fenetre, pygame.Rect((100,100),(200,400)))
34. dessiner_bot(fenetre, pygame.Rect((25,50),(100,200)))
35.
36. pygame.display.flip() # Mettre à jour la fenêtre graphique
37.
38. # Traiter la fermeture de la fenêtre
39. while True:
40. for event in pygame.event.get():
41. if event.type == pygame.QUIT: # Vérifier si l'utilisateur a cliqué pour fermer la
fenêtre
42. pygame.quit() # Terminer pygame
43. sys.exit()
Les règles exactes qui régissent la portée des variables peuvent être
complexes et varient d’un langage à l’autre. Pour limiter les ambiguïtés, il
122
est possible de choisir des noms différents pour désigner des variables qui
ont des rôles différents !
CodePython/chapitre5/ExempleFonctionAccesVariableGlobale.py
1. """
2. Exemple de fonction dessiner_bot
3. """
4.
5. import sys, pygame
6. from pygame import Color
7.
8. def dessiner_bot(r):
9. """ Dessiner un Bot.
10.
11. r : rectangle englobant de type pygame.Rect
12. """
13.
14. # Dessiner le Bot relativement au rectangle englobant r
15. pygame.draw.ellipse(fenetre, Color('green'), ((r.x,r.y),(r.width, r.height/2))) #
Dessiner la tête
16. pygame.draw.rect(fenetre, Color('black'),
((r.x+r.width/4,r.y+r.height/8),(r.width/10,r.height/20))) # L'oeil gauche
17. pygame.draw.rect(fenetre, Color('black'), ((r.x+r.width*3/4-
r.width/10,r.y+r.height/8),(r.width/10,r.height/20))) # L'oeil droit
18. pygame.draw.line(fenetre, Color('black'),
(r.x+r.width/4,r.y+r.height*3/8),(r.x+r.width*3/4,r.y+r.height*3/8), 2) # La bouche
19. pygame.draw.rect(fenetre, Color('red'), ((r.x,r.y+r.height/2),(r.width,r.height/2))) #
Le corps
20.
21. pygame.init() # Initialiser Pygame
22. LARGEUR_FENETRE = 400
23. HAUTEUR_FENETRE = 600
24. fenetre = pygame.display.set_mode((LARGEUR_FENETRE, HAUTEUR_FENETRE)) # Ouvrir la fenêtre
25. pygame.display.set_caption('Exemple de dessin du Bot dans un rectangle englobant') #
Définir le titre dans le haut de la fenêtre
26.
27. fenetre.fill(Color('white')) # Dessiner le fond de la surface de dessin
28.
29. # Dessiner deux Bots en appelant la fonction à deux reprises
30. dessiner_bot(pygame.Rect((100,100),(200,400)))
31. dessiner_bot(pygame.Rect((25,50),(100,200)))
32.
33. pygame.display.flip() # Mettre à jour la fenêtre graphique
34.
35. # Traiter la fermeture de la fenêtre
36. while True:
37. for event in pygame.event.get():
38. if event.type == pygame.QUIT: # Vérifier si l'utilisateur a cliqué pour fermer la
fenêtre
39. pygame.quit() # Terminer pygame
40. sys.exit()
L’accès à une variable globale à l’intérieur d’une fonction est une pratique
qui est souvent déconseillée parce qu’en examinant le code de la fonction,
il est difficile de retrouver d’où vient la variable. Ceci crée une dépendance
indirecte entre la fonction et le code qui l’appelle. Pour faciliter le
développement des programmes, il est important de viser à limiter ce genre
de dépendance. Lorsqu’on appelle une fonction, il est préférable que la
123
fonction ne touche que les paramètres afin de rendre le couplage entre le
code appelant et la fonction plus transparent.
• Les variables qui ne sont référencées que dans une fonction sont
implicitement globales.
1. x = 15
2.
3. def change():
4. global x
5. x = x + 5
6.
7. change()
8. print(x)
Dans cet exemple, nous avons défini une variable x en dehors de la
fonction change(). À l’intérieur de la fonction change(), nous avons
déclaré x comme étant globale en utilisant le mot-clé global. Nous avons
ensuite incrémenté la valeur de x de 5. Enfin, nous avons appelé la
fonction change() et imprimé la valeur de x. La sortie de ce programme
sera 20.
CodePython/chapitre5/ExerciceFonctionDessinerIti.py
1. """
2. Exercice: fonction dessiner_iti
3. """
4.
5. import sys, pygame
6. from pygame import Color
7.
8. def dessiner_iti(fenetre,r):
9. """ Dessiner un Iti.
10.
11. fenetre : la surface de dessin
12. r : rectangle englobant de type pygame.Rect
13. """
14.
15. # Coordonnées du milieu du rectangle englobant pour faciliter les calculs
16. milieux = r.x + r.width/2;
17. milieuy = r.y + r.height/2;
18.
19. pygame.draw.ellipse(fenetre, Color('pink'),
((r.x+r.width/3,r.y),(r.width/3,r.height/4))) # Dessiner la tête
20. pygame.draw.arc(fenetre, Color('black'),((milieux-
r.width/12,r.y+r.height/8),(r.width/6,r.height/14)),3.1416,0,2) # Le sourire
21. pygame.draw.ellipse(fenetre, Color('black'), ((milieux-
r.width/8,r.y+r.height/12),(r.width/12,r.height/24))) # L'oeil gauche
22. pygame.draw.ellipse(fenetre, Color('black'), ((milieux+r.width/8-
r.width/12,r.y+r.height/12),(r.width/12,r.height/24))) # L'oeil droit
23. pygame.draw.line(fenetre, Color('black'),
(milieux,r.y+r.height/4),(milieux,r.y+r.height*3/4), 2) # Le corps
24. pygame.draw.line(fenetre, Color('black'), (r.x,r.y+r.height/4),(milieux,milieuy), 2) #
Bras gauche
25. pygame.draw.line(fenetre, Color('black'),
(r.x+r.width,r.y+r.height/4),(milieux,milieuy), 2) # Bras droit
26. pygame.draw.line(fenetre, Color('black'),
(r.x,r.y+r.height),(milieux,r.y+r.height*3/4), 2) # Jambe gauche
27. pygame.draw.line(fenetre, Color('black'),
(r.x+r.width,r.y+r.height),(milieux,r.y+r.height*3/4), 2) # Jambe droite
28.
29. pygame.init() # Initialiser Pygame
30. LARGEUR_FENETRE = 400
31. HAUTEUR_FENETRE = 600
32. fenetre = pygame.display.set_mode((LARGEUR_FENETRE, HAUTEUR_FENETRE)) # Ouvrir la fenêtre
33. pygame.display.set_caption('Exemple de dessin du Iti dans un rectangle englobant') #
Définir le titre dans le haut de la fenêtre
34.
35. fenetre.fill(Color('white')) # Dessiner le fond de la surface de dessin
36.
37. # Dessiner deux Iti en appelant la fonction à deux reprises
38. dessiner_iti(fenetre,pygame.Rect((100,100),(30,60)))
39. dessiner_iti(fenetre,pygame.Rect((25,50),(100,200)))
40.
41. pygame.display.flip() # Mettre à jour la fenêtre graphique
125
42.
43. # Traiter la fermeture de la fenêtre
44. while True:
45. for event in pygame.event.get():
46. if event.type == pygame.QUIT: # Vérifier si l'utilisateur a cliqué pour fermer la
fenêtre
47. pygame.quit() # Terminer pygame
48. sys.exit()
Exemple.
help(dessiner_bot)
Résultat :
dessiner_bot(fenetre, r)
Dessiner un Bot.
126
5.2.3 Passage de paramètre par valeur, par objet, ou par
référence
Le fait de copier la valeur de l’argument en l’affectant au paramètre
correspond au concept de passage de paramètre par valeur. Ainsi, dans la
fonction, si la valeur du paramètre est modifiée, ceci n’affecte pas
l’argument. En Python, c’est la seule manière de passer un paramètre.
D’autres langages offrent une autre option : le passage de paramètre par
référence qui permet de modifier directement la valeur de l’argument. Dans
le cas d’un paramètre objet, comme c’est toujours le cas en Python, la valeur
passée est une copie de la référence à l’objet, aussi désigné par passage de
paramètre par objet (call by object). Ceci implique que même si la valeur de
l’argument, le pointeur à l’objet, ne peut être modifiée comme tel, le
contenu de l’objet auquel l’argument fait référence peut être modifié dans
le cas où il est muable.
CodePython/chapitre5/ExempleModifierParametre.py
1. """
2. Exemple modification de paramètre
3. """
4.
5. def f1(x):
6. x=1
7. a=0
8. print("Valeur de a avant l'appel de f1:",a)
9. f1(a)
10. print("Valeur de a après l'appel de f1:",a)
11.
Résultat :
127
CodePython/chapitre5/ExempleModifierListe.py
1. """
2. Exemple de modification d'une liste par une fonction
3. """
4.
5. def f2(une_liste):
6. une_liste[0] = 1
7. liste = [5,6,7]
8. print("Valeur de la liste avant l'appel de f2:",liste)
9. f2(liste)
10. print("Valeur de la liste avant l'appel de f2:",liste)
Résultat :
CodePython/chapitre5/ ExempleModifierArgumentCompletListe.py
1. """
2. Exemple de modification de l'argument complet de type liste par
une fonction
3. Même effet qu'un passage par valeur
4. """
5.
6. def f3(une_liste):
7. une_liste = [1,2,3,4]
8. liste = [5,6,7]
9. print("Valeur de la liste avant l'appel de f3:",liste)
10. f3(liste)
11. print("Valeur de la liste après l'appel de f3:",liste)
Résultat :
128
5.2.4 Fonction avec une valeur de retour
Une fonction peut retourner une valeur qui peut être récupérée après
l’appel de la fonction. Ceci a déjà été vu à plusieurs occasions dans le
chapitre précédent pour des exemples d’expression qui contiennent des
appels de fonction.
CodePython/chapitre5/ExempleFonctionSurface.py
1. """
2. Exemple de fonction surface qui retourne une valeur
3. """
4. def surface(largeur,
hauteur):
5. return largeur*hauteur
6. s = surface(3,5)
7. print("La surface est :",s)
8.
Résultat :
La surface est : 15
Pour retourner une valeur, la fonction doit contenir un énoncé return suivi
de la valeur à retourner. La valeur à retourner peut être une expression :
return largeur*hauteur
s = surface(3,5)
En Python, une fonction qui ne retourne pas de valeur, retourne en fait une
valeur spéciale None de type NoneType. Cette valeur spéciale désigne en
quelque sorte l’absence de valeur retournée par un return. Dans l’exemple
suivant, comme la fonction f1() n’a pas d’énoncé return, c’est la valeur
None qui est retournée.
129
CodePython/chapitre5/ExempleRetourNone.py
1. def f1(x):
2. print("Valeur de x :",x)
3. r = f1(5)
4. print("Valeur retournée par f1 :",r)
Résultat :
Valeur de x : 5
Valeur retournée par f1 : None
CodePython/chapitre5/ExempleParametreOptionnel.py
1. """
2. Exemple de fonction dessiner_bot avec paramètre optionnel et valeur de défaut
3. """
4.
5. import sys, pygame
6. from pygame import Color
7.
8. def dessiner_bot(fenetre,r,couleur_tete = Color('green'),couleur_corps = Color('red')):
9. """ Dessiner un Bot.
10.
11. fenetre : la surface de dessin
12. r : rectangle englobant de type pygame.Rect
13. """
14.
15. # Dessiner le Bot relativement au rectangle englobant r
16. pygame.draw.ellipse(fenetre, couleur_tete, ((r.x,r.y),(r.width, r.height/2))) #
Dessiner la tête
17. pygame.draw.rect(fenetre, Color('black'),
((r.x+r.width/4,r.y+r.height/8),(r.width/10,r.height/20))) # L'oeil gauche
18. pygame.draw.rect(fenetre, Color('black'), ((r.x+r.width*3/4-
r.width/10,r.y+r.height/8),(r.width/10,r.height/20))) # L'oeil droit
19. pygame.draw.line(fenetre, Color('black'),
(r.x+r.width/4,r.y+r.height*3/8),(r.x+r.width*3/4,r.y+r.height*3/8), 2) # La bouche
20. pygame.draw.rect(fenetre, couleur_corps, ((r.x,r.y+r.height/2),(r.width,r.height/2))) #
Le corps
21.
22. pygame.init() # Initialiser Pygame
23. LARGEUR_FENETRE = 400
24. HAUTEUR_FENETRE = 600
25. fenetre = pygame.display.set_mode((LARGEUR_FENETRE, HAUTEUR_FENETRE)) # Ouvrir la fenêtre
26. pygame.display.set_caption('Exemple de dessin du Bot dans un rectangle englobant') #
Définir le titre dans le haut de la fenêtre
27.
28. fenetre.fill(Color('white')) # Dessiner le fond de la surface de dessin
29.
30. # Variantes d'appel de fonction avec paramètres optionnels
31. dessiner_bot(fenetre,pygame.Rect((25,50),(100,200))) # Employer valeurs de défaut
130
32. BLEU = (0,0,255)
33. dessiner_bot(fenetre,pygame.Rect((25,350),(75,150)),couleur_tete = Color('blue')) #
Spécifier une couleur pour la tete
34. dessiner_bot(fenetre, pygame.Rect((250,75),(100,200)),couleur_corps = Color('blue')) #
Spécifier une couleur pour le corps
35. dessiner_bot(fenetre, pygame.Rect((250,300),(75,150)),couleur_corps =
Color('blue'),couleur_tete = Color('red')) # Spécifier les deux
36.
37. pygame.display.flip() # Mettre à jour la fenêtre graphique
38.
39. # Traiter la fermeture de la fenêtre
40. while True:
41. for event in pygame.event.get():
42. if event.type == pygame.QUIT: # Vérifier si l'utilisateur a cliqué pour fermer la
fenêtre
43. pygame.quit() # Terminer pygame
44. sys.exit()
45.
Résultat :
dessiner_bot(fenetre, pygame.Rect((250,300),(75,150)),couleur_corps =
Color('blue'),couleur_tete = Color('red'))
131
Enfin, les arguments des paramètres positionnels peuvent aussi être
spécifiés par leur nom et l’ordre peut changer dans ce cas. Dans l’exemple
suivant, l’argument pour r est désigné par nom et il apparaît en premier
dans l’appel de la fonction alors qu’il est en deuxième dans la définition de
la fonction.
CodePython/chapitre5/ ExempleArgumentParNomOrdreChange.py
1. def dessiner_bot(fenetre,r,couleur_tete =
Color('green'),couleur_corps = Color('red')):
2. """ Dessiner un Bot.
3.
4. fenetre : la surface de dessin
5. r : rectangle englobant de type pygame.Rect
6. """
7.
8. …
9.
10. # Variantes d'appel de fonction avec paramètres optionnels
11. dessiner_bot(r=pygame.Rect((25,50),(100,200)),fenetre=fenetre)
# Employer valeurs de défaut
12.
Exercice. Ajouter des paramètres optionnels pour les couleurs de votre
bonhomme (ou le Iti) dans la fonction de dessin et appelez la fonction à
plusieurs reprises en variant les arguments.
132
...
>>> f("La ","vie","est","belle")
La
vie
est
belle
Il est aussi possible de passer un dict en argument pour spécifier les valeurs
des paramètres en identifiant leurs noms. L’argument doit être précédé par
** ce qui a pour effet de dégrouper les éléments du dict :
L’ordre des paramètres dans le dict n’est pas nécessairement l’ordre des
paramètres dans la définition de la fonction :
133
c : 15
CodePython/chapitre5/ExempleEventMouse.py
1. """
2. Exemple de détection d’évènement de souris
3. """
4. # Importer la librairie de pygame et initialiser
5. import pygame
6. from pygame import Color
7.
8. def dessiner_bot(fenetre,r):
9. """ Dessiner un Bot.
10.
11. fenetre : la surface de dessin
12. r : rectangle englobant de type pygame.Rect
13. """
14. …
15.
16. pygame.init() # Initialiser les modules de Pygame
17.
18. LARGEUR_FENETRE = 400
19. HAUTEUR_FENETRE = 600
20. fenetre = pygame.display.set_mode((LARGEUR_FENETRE, HAUTEUR_FENETRE)) # Ouvrir la fenêtre
21. pygame.display.set_caption('Exemple de gestion de la souris') # Définir le titre dans le
haut de la fenêtre
22.
23. fin = False
24.
25. # Position initiale du Bot
26. x=100
27. y=100
28. # Itérer jusqu'à ce qu'un évènement provoque la fermeture de la fenêtre
29. while not fin:
30. event = pygame.event.wait() # Chercher le prochain évènement à traiter
31. if event.type == pygame.QUIT: # Utilisateur a cliqué sur la fermeture de fenêtre ?
32. fin = True # Fin de la boucle du jeu
33. elif event.type == pygame.MOUSEBUTTONUP: # Utilisateur a cliqué dans la fenêtre ?
34. x=event.pos[0] # Position x de la souris
35. y=event.pos[1] # Position y de la souris
36. fenetre.fill(Color('white')) # Dessiner le fond de la surface de dessin
37. dessiner_bot(fenetre,pygame.Rect((x-30/2,y-60/2),(30,60))) # Dessiner le Bot à la
position de la souris
38. pygame.display.flip() # Mettre à jour la fenêtre graphique
39.
40. pygame.quit() # Terminer pygame
La boucle while traite les évènements de souris l’un après l’autre jusqu’à
ce que la variable Booléenne fin devienne True :
134
while not fin:
135
6 Introduction à l’animation 2D
Ce chapitre présente les concepts de base de l’animation graphique 2D.
L’exemple simple d’animation qui est développé sert à introduire les
concepts de programmation objet au chapitre suivant.
CodePython/chapitre6/ExempleAnimationSimple.py
1. """
2. Exemple d'animation simple
3. """
4. # Importer la librairie de pygame et initialiser
5. import pygame
6. from pygame import Color
7.
8. def dessiner_bot(fenetre,r):
9. """ Dessiner un Bot.
10.
11. fenetre : la surface de dessin
12. r : rectangle englobant de type pygame.Rect
13. """
14.
15. # Dessiner le Bot relativement au rectangle englobant r
16. pygame.draw.ellipse(fenetre, Color('green'), ((r.x,r.y),(r.width, r.height/2))) #
Dessiner la tête
17. pygame.draw.rect(fenetre, Color('black'),
((r.x+r.width/4,r.y+r.height/8),(r.width/10,r.height/20))) # L'oeil gauche
18. pygame.draw.rect(fenetre, Color('black'), ((r.x+r.width*3/4-
r.width/10,r.y+r.height/8),(r.width/10,r.height/20))) # L'oeil droit
19. pygame.draw.line(fenetre, Color('black'),
(r.x+r.width/4,r.y+r.height*3/8),(r.x+r.width*3/4,r.y+r.height*3/8), 2) # La bouche
20. pygame.draw.rect(fenetre, Color('red'), ((r.x,r.y+r.height/2),(r.width,r.height/2))) #
Le corps
21.
22. pygame.init() # Initialiser les modules de Pygame
23. LARGEUR_FENETRE = 400
24. HAUTEUR_FENETRE = 600
25. fenetre = pygame.display.set_mode((LARGEUR_FENETRE, HAUTEUR_FENETRE)) # Ouvrir la fenêtre
26. pygame.display.set_caption("Exemple d'animation simple") # Définir le titre dans le haut de
la fenêtre
27.
28. horloge = pygame.time.Clock() # Pour contrôler la fréquence des scènes
29. position_verticale = 100 # Position verticale du Bot
30. VITESSE_DEPLACEMENT = 5 # En pixels par scène
31. TAILLE_BOT = (50,100)
32. # Boucle d'animation par une suite de scènes
33. # Le Bot avance de la bordure gauche jusqu'à la droite
34. for position_horizontale in range(0,LARGEUR_FENETRE-TAILLE_BOT[0],VITESSE_DEPLACEMENT) :
35. fenetre.fill(Color('white')) # Dessiner le fond de la surface de dessin
36. dessiner_bot(fenetre,pygame.Rect((position_horizontale,position_verticale),TAILLE_BOT))
136
37. pygame.display.flip() # Mettre à jour la fenêtre graphique
38. horloge.tick(60) # Pour animer avec 60 images par seconde
39.
40. pygame.quit() # Terminer pygame
Solution. CodePython/chapitre6/ExerciceBotRebondissant.py
1. """
2. Exercice du Bot qui rebondit
3. """
4. # Importer la librairie de pygame et initialiser
5. import pygame
6. from pygame import Color
7.
8. def dessiner_bot(fenetre,r):
9. """ Dessiner un Bot.
10.
11. fenetre : la surface de dessin
137
12. r : rectangle englobant de type pygame.Rect
13. """
14.
15. # Dessiner le Bot relativement au rectangle englobant r
16. pygame.draw.ellipse(fenetre, Color('green'), ((r.x,r.y),(r.width, r.height/2))) #
Dessiner la tête
17. pygame.draw.rect(fenetre, Color('black'),
((r.x+r.width/4,r.y+r.height/8),(r.width/10,r.height/20))) # L'oeil gauche
18. pygame.draw.rect(fenetre, Color('black'), ((r.x+r.width*3/4-
r.width/10,r.y+r.height/8),(r.width/10,r.height/20))) # L'oeil droit
19. pygame.draw.line(fenetre, Color('black'),
(r.x+r.width/4,r.y+r.height*3/8),(r.x+r.width*3/4,r.y+r.height*3/8), 2) # La bouche
20. pygame.draw.rect(fenetre, Color('red'), ((r.x,r.y+r.height/2),(r.width,r.height/2))) #
Le corps
21.
22. pygame.init() # Initialiser les modules de Pygame
23. LARGEUR_FENETRE = 400
24. HAUTEUR_FENETRE = 600
25. fenetre = pygame.display.set_mode((LARGEUR_FENETRE, HAUTEUR_FENETRE)) # Ouvrir la fenêtre
26. pygame.display.set_caption("Exercice du Bot qui rebondit") # Définir le titre dans le haut
de la fenêtre
27.
28. horloge = pygame.time.Clock() # Pour contrôler la fréquence des scènes
29. (x_bot,y_bot) = (175,0) # Position du Bot
30. vitesse_deplacement = 5 # En pixels par scène
31. TAILLE_BOT = (50,100)
32.
33. # Boucle d'animation : Le Bot rebondit verticalement
34. fin = False
35. while not fin :
36. event = pygame.event.poll() # Chercher le prochain évènement à traiter
37. if event.type == pygame.QUIT: # Utilisateur a cliqué sur la fermeture de fenêtre ?
38. fin = True # Fin de la boucle du jeu
39. else :
40. # Déplacer le Bot : Inverser la direction sur le bord est atteint
41. if y_bot+vitesse_deplacement > HAUTEUR_FENETRE-TAILLE_BOT[1] or
y_bot+vitesse_deplacement < 0 :
42. vitesse_deplacement = -vitesse_deplacement # Inverser la direction
43. y_bot = y_bot+vitesse_deplacement
44.
45. fenetre.fill(Color('white')) # Dessiner le fond de la surface de dessin
46. dessiner_bot(fenetre,pygame.Rect((x_bot,y_bot),TAILLE_BOT)) # Dessiner le Bot
47. pygame.display.flip() # Mettre à jour la fenêtre graphique
48.
49. horloge.tick(60) # Pour animer avec 60 images par seconde
50.
51. pygame.quit() # Terminer pygame
Solution. CodePython/chapitre6/ExerciceItiRebondissant.py
1. """
2. Exercice du Iti qui rebondit
3. """
4. # Importer la librairie de pygame et initialiser
5. import pygame
6. from pygame import Color
7.
8. def dessiner_iti(fenetre,r):
9. """ Dessiner un Iti.
10.
11. fenetre : la surface de dessin
12. r : rectangle englobant de type pygame.Rect
13. """
14. …
15.
138
16. pygame.init() # Initialiser les modules de Pygame
17. LARGEUR_FENETRE = 400
18. HAUTEUR_FENETRE = 600
19. fenetre = pygame.display.set_mode((LARGEUR_FENETRE, HAUTEUR_FENETRE)) # Ouvrir la fenêtre
20. pygame.display.set_caption("Exercice du Iti qui rebondit") # Définir le titre dans le haut
de la fenêtre
21.
22. horloge = pygame.time.Clock() # Pour contrôler la fréquence des scènes
23. x_iti = 175 # Position du Iti sur l'axe x
24. y_iti = 0 # Position initiale du Iti sur l'axe y
25. vitesse_deplacement = 5 # En pixels par scène
26. TAILLE_ITI = (50,100)
27.
28. # Boucle d'animation : Le Iti rebondit verticalement
29. fin = False
30. while not fin :
31. event = pygame.event.poll() # Chercher le prochain évènement à traiter
32. if event.type == pygame.QUIT: # Utilisateur a cliqué sur la fermeture de fenêtre ?
33. fin = True # Fin de la boucle du jeu
34. else :
35. # Déplacer le Iti : Inverser la direction sur le bord est atteint
36. if y_iti+vitesse_deplacement > HAUTEUR_FENETRE-TAILLE_ITI[1] or
y_iti+vitesse_deplacement < 0 :
37. vitesse_deplacement = -vitesse_deplacement # Inverser la direction
38. y_iti = y_iti+vitesse_deplacement
39.
40. fenetre.fill(Color('white')) # Dessiner le fond de la surface de dessin
41. dessiner_iti(fenetre,pygame.Rect((x_iti,y_iti),TAILLE_ITI)) # Dessiner le Bot
42. pygame.display.flip() # Mettre à jour la fenêtre graphique
43.
44. horloge.tick(60) # Pour animer avec 60 images pas seconde
45.
46. pygame.quit() # Terminer pygame
Solution. CodePython/chapitre6/ExerciceBotDiagonale.py
1. """
2. Exercice du Bot qui rebondit en diagonale
3. """
4. # Importer la librairie de pygame et initialiser
5. import pygame
6. from pygame import Color
7.
8. def dessiner_bot(fenetre,r):
9. """ Dessiner un Bot.
10.
11. fenetre : la surface de dessin
12. r : rectangle englobant de type pygame.Rect
13. """
14.
15. …
16.
17. pygame.init() # Initialiser les modules de Pygame
18. LARGEUR_FENETRE = 400
19. HAUTEUR_FENETRE = 600
20. fenetre = pygame.display.set_mode((LARGEUR_FENETRE, HAUTEUR_FENETRE)) # Ouvrir la fenêtre
21. pygame.display.set_caption("Exercice du Bot qui rebondit en diagonale") # Définir le titre
dans le haut de la fenêtre
22.
23. horloge = pygame.time.Clock() # Pour contrôler la fréquence des scènes
24. (x_bot,y_bot) = (0,0) # Position initiale du Bot
25. vitesse_deplacement_x = 5 # En pixels par scène
26. vitesse_deplacement_y = 10
27. TAILLE_BOT = (50,100)
139
28.
29. # Boucle d'animation : Le Bot se déplace en diagonale
30. fin = False
31. while not fin :
32. event = pygame.event.poll() # Chercher le prochain évènement à traiter
33. if event.type == pygame.QUIT: # Utilisateur a cliqué sur la fermeture de fenêtre ?
34. fin = True # Fin de la boucle du jeu
35. else :
36. # Déplacer le Bot : Inverser la direction si le bord est atteint
37. if x_bot+vitesse_deplacement_x > LARGEUR_FENETRE-TAILLE_BOT[0] or
x_bot+vitesse_deplacement_x < 0 :
38. vitesse_deplacement_x = -vitesse_deplacement_x # Inverser la direction en x
39. x_bot = x_bot+vitesse_deplacement_x
40. if y_bot+vitesse_deplacement_y > HAUTEUR_FENETRE-TAILLE_BOT[1] or
y_bot+vitesse_deplacement_y < 0 :
41. vitesse_deplacement_y = -vitesse_deplacement_y # Inverser la direction en y
42. y_bot = y_bot+vitesse_deplacement_y
43.
44. fenetre.fill(Color('white')) # Dessiner le fond de la surface de dessin
45. dessiner_bot(fenetre,pygame.Rect((x_bot,y_bot),TAILLE_BOT)) # Dessiner le Bot
46. pygame.display.flip() # Mettre à jour la fenêtre graphique
47.
48.
49. horloge.tick(60) # Pour animer avec 60 images par seconde
50.
51. pygame.quit() # Terminer pygame
Solution. CodePython/chapitre6/ExerciceBotEtItiDiagonale.py
1. """
2. Exercice : 2 Bot et 2 Iti qui rebondissent en diagonale
3. """
4. # Importer la librairie de pygame et initialiser
5. import pygame
6. from pygame import Color
7.
8. def dessiner_bot(fenetre,r):
9. """ Dessiner un Bot.
10.
11. fenetre : la surface de dessin
12. r : rectangle englobant de type pygame.Rect
13. """
14.
15. # Dessiner le Bot relativement au rectangle englobant r
16. pygame.draw.ellipse(fenetre, Color('green'), ((r.x,r.y),(r.width, r.height/2))) #
Dessiner la tête
17. pygame.draw.rect(fenetre, Color('black'),
((r.x+r.width/4,r.y+r.height/8),(r.width/10,r.height/20))) # L'oeil gauche
18. pygame.draw.rect(fenetre, Color('black'), ((r.x+r.width*3/4-
r.width/10,r.y+r.height/8),(r.width/10,r.height/20))) # L'oeil droit
19. pygame.draw.line(fenetre, Color('black'),
(r.x+r.width/4,r.y+r.height*3/8),(r.x+r.width*3/4,r.y+r.height*3/8), 2) # La bouche
20. pygame.draw.rect(fenetre, Color('red'), ((r.x,r.y+r.height/2),(r.width,r.height/2))) #
Le corps
21.
22. def dessiner_iti(fenetre,r):
23. """ Dessiner un Iti.
24.
25. fenetre : la surface de dessin
26. r : rectangle englobant de type pygame.Rect
27. """
28.
29. # Coordonnées du milieu du rectangle englobant pour faciliter les calculs
30. milieux = r.x + r.width/2;
31. milieuy = r.y + r.height/2;
32.
33. pygame.draw.ellipse(fenetre, Color('pink'),
((r.x+r.width/3,r.y),(r.width/3,r.height/4))) # Dessiner la tête
140
34. pygame.draw.arc(fenetre, Color('black'),((milieux-
r.width/12,r.y+r.height/8),(r.width/6,r.height/14)),3.1416,0,2) # Le sourire
35. pygame.draw.ellipse(fenetre, Color('black'), ((milieux-
r.width/8,r.y+r.height/12),(r.width/12,r.height/24))) # L'oeil gauche
36. pygame.draw.ellipse(fenetre, Color('black'), ((milieux+r.width/8-
r.width/12,r.y+r.height/12),(r.width/12,r.height/24))) # L'oeil droit
37. pygame.draw.line(fenetre, Color('black'),
(milieux,r.y+r.height/4),(milieux,r.y+r.height*3/4), 2) # Le corps
38. pygame.draw.line(fenetre, Color('black'), (r.x,r.y+r.height/4),(milieux,milieuy), 2) #
Bras gauche
39. pygame.draw.line(fenetre, Color('black'),
(r.x+r.width,r.y+r.height/4),(milieux,milieuy), 2) # Bras droit
40. pygame.draw.line(fenetre, Color('black'),
(r.x,r.y+r.height),(milieux,r.y+r.height*3/4), 2) # Jambe gauche
41. pygame.draw.line(fenetre, Color('black'),
(r.x+r.width,r.y+r.height),(milieux,r.y+r.height*3/4), 2) # Jambe droite
42.
43. pygame.init() # Initialiser les modules de Pygame
44. LARGEUR_FENETRE = 400
45. HAUTEUR_FENETRE = 600
46. fenetre = pygame.display.set_mode((LARGEUR_FENETRE, HAUTEUR_FENETRE)) # Ouvrir la fenêtre
47. pygame.display.set_caption("Exercice des Bots et Itis en diagonale") # Définir le titre
dans le haut de la fenêtre
48.
49. horloge = pygame.time.Clock() # Pour contrôler la fréquence des scènes
50.
51. # Données du Bot1
52. (x_bot1,y_bot1) = (0,0) # Position initiale du Bot1 sur l'axe x
53. (vitesse_x_bot1,vitesse_y_bot1) = (5,10) # En pixels par scène
54. TAILLE_BOT1 = (20,40)
55.
56. # Données du Bot2
57. (x_bot2,y_bot2) = (100,100)
58. (vitesse_x_bot2,vitesse_y_bot2) = (10,2) # En pixels par scène
59. TAILLE_BOT2 = (30,60)
60.
61. # Données du Iti1
62. (x_iti1,y_iti1) = (200,150)
63. (vitesse_x_iti1,vitesse_y_iti1) = (10,2) # En pixels par scène
64. TAILLE_ITI1 = (40,80)
65.
66. # Données du Iti2
67. (x_iti2,y_iti2) = (300,300)
68. (vitesse_x_iti2,vitesse_y_iti2) = (5,10) # En pixels par scène
69. TAILLE_ITI2 = (50,100)
70.
71. # Boucle d'animation
72. fin = False
73. while not fin :
74. event = pygame.event.poll() # Chercher le prochain évènement à traiter
75. if event.type == pygame.QUIT: # Utilisateur a cliqué sur la fermeture de fenêtre ?
76. fin = True # Fin de la boucle du jeu
77. else :
78. # Déplacer le Bot1
79. if x_bot1+vitesse_x_bot1 > LARGEUR_FENETRE-TAILLE_BOT1[0] or x_bot1+vitesse_x_bot1
< 0 :
80. vitesse_x_bot1 = -vitesse_x_bot1 # Inverser la direction en x
81. x_bot1 = x_bot1+vitesse_x_bot1
82. if y_bot1+vitesse_y_bot1 > HAUTEUR_FENETRE-TAILLE_BOT1[1] or y_bot1+vitesse_y_bot1
< 0 :
83. vitesse_y_bot1 = -vitesse_y_bot1 # Inverser la direction en y
84. y_bot1 = y_bot1+vitesse_y_bot1
85.
86. # Déplacer le Bot2
87. if x_bot2+vitesse_x_bot2 > LARGEUR_FENETRE-TAILLE_BOT2[0] or x_bot2+vitesse_x_bot2
< 0 :
88. vitesse_x_bot2 = -vitesse_x_bot2 # Inverser la direction en x
89. x_bot2 = x_bot2+vitesse_x_bot2
90. if y_bot2+vitesse_y_bot2 > HAUTEUR_FENETRE-TAILLE_BOT2[1] or y_bot2+vitesse_y_bot2
< 0 :
91. vitesse_y_bot2 = -vitesse_y_bot2 # Inverser la direction en y
92. y_bot2 = y_bot2+vitesse_y_bot2
93.
94. # Déplacer le Iti1
141
95. if x_iti1+vitesse_x_iti1 > LARGEUR_FENETRE-TAILLE_ITI1[0] or x_iti1+vitesse_x_iti1
< 0 :
96. vitesse_x_iti1 = -vitesse_x_iti1 # Inverser la direction en x
97. x_iti1 = x_iti1+vitesse_x_iti1
98. if y_iti1+vitesse_y_iti1 > HAUTEUR_FENETRE-TAILLE_ITI1[1] or y_iti1+vitesse_y_iti1
< 0 :
99. vitesse_y_iti1 = -vitesse_y_iti1 # Inverser la direction en y
100. y_iti1 = y_iti1+vitesse_y_iti1
101.
102. # Déplacer le Iti2
103. if x_iti2+vitesse_x_iti2 > LARGEUR_FENETRE-TAILLE_ITI2[0] or x_iti2+vitesse_x_iti2
< 0 :
104. vitesse_x_iti2 = -vitesse_x_iti2 # Inverser la direction en x
105. x_iti2 = x_iti2+vitesse_x_iti2
106. if y_iti2+vitesse_y_iti2 > HAUTEUR_FENETRE-TAILLE_ITI2[1] or y_iti2+vitesse_y_iti2
< 0 :
107. vitesse_y_iti2 = -vitesse_y_iti2 # Inverser la direction en y
108. y_iti2 = y_iti2+vitesse_y_iti2
109.
110.
111. fenetre.fill(Color('white')) # Dessiner le fond de la surface de dessin
112. dessiner_bot(fenetre,pygame.Rect((x_bot1,y_bot1),TAILLE_BOT1)) # Dessiner le Bot1
113. dessiner_bot(fenetre,pygame.Rect((x_bot2,y_bot2),TAILLE_BOT2)) # Dessiner le Bot2
114. dessiner_iti(fenetre,pygame.Rect((x_iti1,y_iti1),TAILLE_ITI1)) # Dessiner le Iti1
115. dessiner_iti(fenetre,pygame.Rect((x_iti2,y_iti2),TAILLE_ITI2)) # Dessiner le Iti2
116. pygame.display.flip() # Mettre à jour la fenêtre graphique
117.
118. horloge.tick(60) # Pour animer avec 60 images par seconde
119.
120. pygame.quit() # Terminer pygame
121.
142
Zône mémoire de l’écran Zône mémoire du dessin
100 100
200 200
300 300
400 400
500 500
y y
143
Zône mémoire de l’écran Zône mémoire du dessin
100 100
200 200
300 300
400 400
500 500
y y
100 100
200 200
300 300
400 400
500 500
y y
100 100
200 200
300 300
400 400
500 500
y y
Lorsque le dessin est terminé, la zone mémoire du dessin est copiée dans la
zone de l’écran le plus rapidement possible, ce qui provoque l’affichage de
144
la nouvelle scène à l’écran. Si les opérations graphiques étaient directement
effectuées dans la zone de mémoire de l’écran, il pourrait y avoir un effet
indésirable de scintillement (flickering) à l’écran, surtout si le dessin est long à
produire.
145
Zône mémoire de l’écran Zône mémoire du dessin
100 100
200 200
Copie
300 300
400 400
500 500
y y
100 100
200 200
Effacement du dessin
300 300
400 400
500 500
y y
146
7 Développement de classes :
conception objet
La conception objet désigne le problème de conception d’un programme objet
comportant des classes et des objets. Un aspect particulièrement important
à considérer est le découpage du programme en classes. Ce chapitre
approfondit quelques notions de base de la conception objet : diviser pour
régner, minimiser la répétition, encapsulation, interface, cohésion,
couplage, et relation d’héritage. Les principes de Python touchant à
l’organisation, la compilation et l’exécution d’un programme composé de
plusieurs modules sont aussi abordés.
CodePython/chapitre7/ExempleAvecClasse.py
1. """
2. Exemple d'animation d'entités : création des classes BotAnime et ItiAnime
3. """
4. # Importer la librairie de pygame et initialiser
5. import pygame
6. from pygame import Color
7. class BotAnime :
8. """ Un objet représente un Bot qui est animé dans une fenêtre Pygame
9.
10. Le Bot est inscrit dans le rectangle englobant défini par r. Il se déplace en
diagonale selon vitesse.
11. r : pygame.Rect Le rectangle englobant
12. v : [int,int] Vitesse de déplacement selon les deux axes x et y
13. """
14.
15. def __init__(self,rectangle,vitesse):
16. self.r = rectangle
17. self.v = vitesse
18.
19. def dessiner(self,fenetre):
20. """ Dessiner un Bot.
21.
22. Le Bot est inscrit dans le rectangle englobant défini par la variable d'objet r
dans une fenetre de Pygame
23. """
24.
25. pygame.draw.ellipse(fenetre, Color('green'), ((self.r.x,self.r.y),(self.r.width,
self.r.height/2))) # Dessiner la tête
26. pygame.draw.rect(fenetre, Color('black'),
((self.r.x+self.r.width/4,self.r.y+self.r.height/8),(self.r.width/10,self.r.height/20))) #
L'oeil gauche
27. pygame.draw.rect(fenetre, Color('black'), ((self.r.x+self.r.width*3/4-
self.r.width/10,self.r.y+self.r.height/8),(self.r.width/10,self.r.height/20))) # L'oeil droit
28. pygame.draw.line(fenetre, Color('black'),
(self.r.x+self.r.width/4,self.r.y+self.r.height*3/8),(self.r.x+self.r.width*3/4,self.r.y+self.r
.height*3/8), 2) # La bouche
29. pygame.draw.rect(fenetre, Color('red'),
((self.r.x,self.r.y+self.r.height/2),(self.r.width,self.r.height/2))) # Le corps
30.
31.
32. def deplacer(self,largeur_fenetre,hauteur_fenetre):
33. """ Déplacer le Bot en diagonale en rebondissant sur les bords de la fenetre"""
34. if self.r.x+self.v[0] > largeur_fenetre-self.r.width or self.r.x+self.v[0] < 0 :
35. self.v[0] = -self.v[0] # Inverser la direction en x
36. self.r.x = self.r.x+self.v[0]
37. if self.r.y+self.v[1] > hauteur_fenetre-self.r.height or self.r.y+self.v[1] < 0 :
38. self.v[1] = -self.v[1] # Inverser la direction en y
39. self.r.y = self.r.y+self.v[1]
40.
41. class ItiAnime :
42. """ Un objet représente un Iti qui est animé dans une fenêtre Pygame
43.
44. Le Iti est inscrit dans le rectangle englobant défini par rectangle. Il se déplace en
diagonale selon vitesse.
45. r : pygame.Rect Le rectangle englobant
46. v : [int,int] Vitesse de déplacement selon les deux axes x et y
47. """
48.
49. def __init__(self,rectangle,vitesse):
50. self.r = rectangle
51. self.v = vitesse
148
52.
53. def dessiner(self,fenetre):
54. """ Dessiner un Iti.
55.
56. Le Iti est inscrit dans le rectangle englobant défini par la variable d'objet r
dans une fenetre de Pygame
57. """
58. self.milieux = self.r.x + self.r.width/2;
59. self.milieuy = self.r.y + self.r.height/2;
60.
61. pygame.draw.ellipse(fenetre, Color('pink'),
((self.r.x+self.r.width/3,self.r.y),(self.r.width/3,self.r.height/4))) # Dessiner la tête
62. pygame.draw.arc(fenetre, Color('black'),((self.milieux-
self.r.width/12,self.r.y+self.r.height/8),(self.r.width/6,self.r.height/14)),3.1416,0,2) # Le
sourire
63. pygame.draw.ellipse(fenetre, Color('black'), ((self.milieux-
self.r.width/8,self.r.y+self.r.height/12),(self.r.width/12,self.r.height/24))) # L'oeil gauche
64. pygame.draw.ellipse(fenetre, Color('black'), ((self.milieux+self.r.width/8-
self.r.width/12,self.r.y+self.r.height/12),(self.r.width/12,self.r.height/24))) # L'oeil droit
65. pygame.draw.line(fenetre, Color('black'),
(self.milieux,self.r.y+self.r.height/4),(self.milieux,self.r.y+self.r.height*3/4), 2) # Le
corps
66. pygame.draw.line(fenetre, Color('black'),
(self.r.x,self.r.y+self.r.height/4),(self.milieux,self.milieuy), 2) # Bras gauche
67. pygame.draw.line(fenetre, Color('black'),
(self.r.x+self.r.width,self.r.y+self.r.height/4),(self.milieux,self.milieuy), 2) # Bras droit
68. pygame.draw.line(fenetre, Color('black'),
(self.r.x,self.r.y+self.r.height),(self.milieux,self.r.y+self.r.height*3/4), 2) # Jambe gauche
69. pygame.draw.line(fenetre, Color('black'),
(self.r.x+self.r.width,self.r.y+self.r.height),(self.milieux,self.r.y+self.r.height*3/4), 2) #
Jambe droite
70.
71.
72. def deplacer(self,largeur_fenetre,hauteur_fenetre):
73. """ Déplacer le Iti en diagonale en rebondissant sur les bords de la fenetre"""
74. if self.r.x+self.v[0] > largeur_fenetre-self.r.width or self.r.x+self.v[0] < 0 :
75. self.v[0] = -self.v[0] # Inverser la direction en x
76. self.r.x = self.r.x+self.v[0]
77. if self.r.y+self.v[1] > hauteur_fenetre-self.r.height or self.r.y+self.v[1] < 0 :
78. self.v[1] = -self.v[1] # Inverser la direction en y
79. self.r.y = self.r.y+self.v[1]
80.
81. pygame.init() # Initialiser les modules de Pygame
82. LARGEUR_FENETRE = 400
83. HAUTEUR_FENETRE = 600
84. fenetre = pygame.display.set_mode((LARGEUR_FENETRE, HAUTEUR_FENETRE)) # Ouvrir la fenêtre
85. pygame.display.set_caption("Bot et Iti en diagonale avec classe") # Définir le titre dans
le haut de la fenêtre
86.
87. horloge = pygame.time.Clock() # Pour contrôler la fréquence des scènes
88.
89. # Création de deux BotAnime et deux ItiAnime
90. bot1 = BotAnime(pygame.Rect((0,0),(20,40)),[5,10])
91. bot2 = BotAnime(pygame.Rect((100,200),(30,60)),[0,2])
92. iti1 = ItiAnime(pygame.Rect((200,150),(40,80)),[3,3])
93. iti2 = ItiAnime(pygame.Rect((300,300),(50,100)),[5,10])
94.
95. # Boucle d'animation
96. fin = False
97. while not fin :
98. event = pygame.event.poll() # Chercher le prochain évènement à traiter
99. if event.type == pygame.QUIT: # Utilisateur a cliqué sur la fermeture de fenêtre ?
100. fin = True # Fin de la boucle du jeu
101. else :
102. bot1.deplacer(LARGEUR_FENETRE,HAUTEUR_FENETRE)
103. bot2.deplacer(LARGEUR_FENETRE,HAUTEUR_FENETRE)
104. iti1.deplacer(LARGEUR_FENETRE,HAUTEUR_FENETRE)
105. iti2.deplacer(LARGEUR_FENETRE,HAUTEUR_FENETRE)
106.
107. fenetre.fill(Color('white')) # Dessiner le fond de la surface de dessin
108. bot1.dessiner(fenetre)
109. bot2.dessiner(fenetre)
110. iti1.dessiner(fenetre)
111. iti2.dessiner(fenetre)
112.
149
113. pygame.display.flip() # Mettre à jour la fenêtre graphique
114.
115. horloge.tick(60) # Pour animer avec 60 images pas seconde
116.
117. pygame.quit() # Terminer pygame
class BotAnime :
L’entête est suivi du corps de la classe qui définit les données et les
opérations de la classe. Une méthode est une fonction spéciale qui se
distingue par le fait qu’elle est appelée sur un objet particulier d’une classe.
La fonction possède un paramètre supplémentaire qui représente l’objet sur
lequel la fonction est appelée14. Ce paramètre supplémentaire, nommé self
par convention, est le premier paramètre dans la définition de la méthode.
Les variables qui décrivent un objet Bot (rectangle et vitesse) peuvent
être initialisées dans une méthode spéciale dont le nom est __init__():
def __init__(self,rectangle,vitesse):
self.r = rectangle
self.v = vitesse
Comme pour une fonction, le code qui fait partie de la méthode doit être
indenté par rapport à l’entête. Par convention, la méthode __init()__ est
appelée automatiquement lorsqu’un objet est créé. Un objet est créé par un
appel au constructeur d’objet de la forme NomClasse(paramètres), où
les paramètres correspondent aux paramètres de la méthode __init()__,
sauf pour le premier paramètre self qui représente une référence à l’objet en
construction. Le constructeur d’objet retourne un nouvel objet de type
NomClasse. Dans notre exemple, chacun des appels ci-bas crée un objet
du type BotAnime. Les paramètres rectangle et vitesse sont utilisés
pour initialiser les variables d’objet, r et v.
bot1 = BotAnime(pygame.Rect((0,0),(20,40)),[5,10])
bot2 = BotAnime(pygame.Rect((100,200),(30,60)),[0,2])
14Habituellement, dans les langages objet, ce paramètre n’est pas déclaré car il est
implicite.
150
Méthode publique/privée, méthodes spéciales (magiques), dunder
methods
Une méthode en Python est toujours publique, c’est-à-dire qu’elle peut être
appelée de l’extérieur de la classe. Une méthode dite privée ne peut être
appelée de l’extérieur de la classe. Même si Python n’offre pas de
mécanisme de méthode privée, des conventions sont suggérées pour
désigner une méthode qui devrait être privée. Un nom de méthode qui
débute par un _ est privé par convention par opposition à un nom public.
L’encadrement d’un nom de méthode par deux caractères de soulignement
__15 (tiret bas) au début et à la fin du nom est une convention de Python
pour désigner des méthodes spéciales au-delà du fait qu’elles devraient être
privées. On utilise parfois l’expression « dunder methods » pour double
underscore afin de désigner ces méthodes, ou encore méthodes magiques (magic
methods). Plusieurs méthodes spéciales sont pré-définies par convention
telles que __str__() qui retourne une représentation en chaîne de
caractère de l’objet. Ces méthodes ne sont pas faites pour être appelées
directement mais indirectement via des opérations standards. Par exemple,
la fonction __str__() est appelée par les fonctions prédéfinies str(),
format() et print() pour produire une représentation sous forme de
chaîne de caractère d’un objet quelconque. Par défaut, __str__() appelle
la méthode __repr__() qui retourne une représentation sous forme de
chaîne plus formelle que __str__() et moins lisible dans plusieurs cas. Il
est possible de redéfinir ces méthodes au besoin pour personnaliser le
comportement par défaut.
Il est possible de créer plusieurs objets de la même classe. Pour chacun des
objets créés, des variables d’objets distinctes sont créées même si les noms des
variables sont identiques d’un objet à l’autre. Chacun des objets a un espace
de nom privé qui permet de distinguer les différentes variables d’objet.
Ainsi pour notre exemple, les deux appels au constructeur d’objet créent
deux objets de type BotAnime qui ont chacun leurs propres variables
d’objets tel qu’illustré à la figure suivante :
15Attention ! La suite des deux caractères « _ _ » peut porter à confusion avec certaines
fontes comme celle de notre texte parce que les deux caractères sont collés lorsqu’ils sont
juxtaposés (sans espace).
151
r pygame.Rect((0,20),(20,40))
bot1
v [5,10]
r pygame.Rect((100,200),(30,60))
bot2
v [10,2]
def deplacer(self,largeur_fenetre,hauteur_fenetre):
""" Déplacer le Bot en diagonale en rebondissant sur les bords de la fenetre"""
if self.r.x+self.vitesse[0] > largeur_fenetre-self.r.width or self.r.x+self.vitesse[0] < 0 :
self.vitesse[0] = -self.vitesse[0] # Inverser la direction en x
self.r.x = self.r.x+self.vitesse[0]
if self.r.y+self.vitesse[1] > hauteur_fenetre-self.r.height or self.r.y+self.vitesse[1] < 0 :
self.vitesse[1] = -self.vitesse[1] # Inverser la direction en y
self.r.y = self.r.y+self.vitesse[1]
152
Comme pour le cas de __init__(), le premier paramètre self sert à
représenter l’objet sur lequel la méthode est appliquée mais il n’est pas passé
par les arguments. Il est désigné par la syntaxe
nom_objet.nom_methode().
Le Bot est inscrit dans le rectangle englobant défini par la variable d'objet rectangle dans une
fenetre de Pygame
"""
bot1 = BotAnime(pygame.Rect((0,0),(20,40)),[5,10])
bot2 = BotAnime(pygame.Rect((100,200),(30,60)),[0,2])
iti1 = ItiAnime(pygame.Rect((200,150),(40,80)),[3,3])
iti2 = ItiAnime(pygame.Rect((300,300),(50,100)),[5,10])
Par la suite, dans la boucle d’animation les objets sont déplacés pour la
prochaine scène par appel à la méthode d’objet deplacer() :
bot1.deplacer(LARGEUR_FENETRE,HAUTEUR_FENETRE)
bot2.deplacer(LARGEUR_FENETRE,HAUTEUR_FENETRE)
iti1.deplacer(LARGEUR_FENETRE,HAUTEUR_FENETRE)
iti2.deplacer(LARGEUR_FENETRE,HAUTEUR_FENETRE)
153
paramètre self d’une méthode d’objet correspond alors à l’objet sur lequel
la méthode est appelée. Et ainsi de suite pour les trois autres objets de la
scène. Ensuite les objets sont dessinés à l’écran par appel à la méthode
dessiner() sur chacun des quatre objets de la scène.
bot1.dessiner(fenetre)
bot2.dessiner(fenetre)
iti1.dessiner(fenetre)
iti2.dessiner(fenetre)
Cette manière d’isoler les détails du fonctionnement d’une classe est une
caractéristique de la programmation objet appelée l’encapsulation. Tout ce
que l’utilisateur doit savoir, c’est comment appeler les méthodes
appropriées de la classe BotAnime (le constructeur BotAnime(),
deplacer(), dessiner()). Il n’a pas besoin de comprendre comment
cela se passe à l’intérieur des méthodes appelées. Ainsi la classe BotAnime
fournit une abstraction sous forme d’un ensemble de méthodes simples à
appeler. Dans le langage objet, cet ensemble de méthodes est appelé une
interface programmatique. On dit aussi que la classe BotAnime fournit un service
à ExempleAvecClasse qui est le client de ce service. Le client voit
l’interface programmatique mais pas l’implémentation.
154
à l’intérieur d’une classe est forte lorsque les variables et méthodes de la
classe sont fortement interreliées. Le couplage entre deux classes est faible
lorsqu’il y a peu de dépendances entre les classes. Concrètement la
dépendance entre classes est déterminée par la manière dont une classe
utilise une autre classe, en passant par des déclarations, l’utilisation des
variables, les appels de méthodes et le passage de paramètres.
CodePython/chapitre7/ExempleFenetreVariableDObjet.py
1. """
2. Exemple d'animation d'entités : création des classes BotAnime et ItiAnime
3. avec variable d'objet pour la fenetre
4. """
5. # Importer la librairie de pygame et initialiser
6. import pygame
7. from pygame import Color
8.
9. class BotAnime :
10. """ Un objet représente un Bot qui est animé dans une fenêtre Pygame
11.
12. Le Bot est inscrit dans le rectangle englobant défini par r. Il se déplace en
diagonale selon la vitesse v.
13. r : pygame.Rect Le rectangle englobant
14. v : [int,int] Vitesse de déplacement selon les deux axes x et y
15. f : pygame.Surface
16. """
17.
18. def __init__(self,rectangle,vitesse,fenetre):
19. self.r = rectangle
20. self.v = vitesse
21. self.f = fenetre
22.
23. def dessiner(self):
24. """ Dessiner un Bot.
25.
155
26. Le Bot est inscrit dans le rectangle englobant défini par la variable d'objet r
dans une fenetre de Pygame
27. """
28.
29. pygame.draw.ellipse(self.f, Color('green'), ((self.r.x,self.r.y),(self.r.width,
self.r.height/2))) # Dessiner la tête
30. pygame.draw.rect(self.f, Color('black'),
((self.r.x+self.r.width/4,self.r.y+self.r.height/8),(self.r.width/10,self.r.height/20))) #
L'oeil gauche
31. pygame.draw.rect(self.f, Color('black'), ((self.r.x+self.r.width*3/4-
self.r.width/10,self.r.y+self.r.height/8),(self.r.width/10,self.r.height/20))) # L'oeil droit
32. pygame.draw.line(self.f, Color('black'),
(self.r.x+self.r.width/4,self.r.y+self.r.height*3/8),(self.r.x+self.r.width*3/4,self.r.y+self.r
.height*3/8), 2) # La bouche
33. pygame.draw.rect(self.f, Color('red'),
((self.r.x,self.r.y+self.r.height/2),(self.r.width,self.r.height/2))) # Le corps
34.
35.
36. def deplacer(self):
37. """ Déplacer selon self.v en diagonale en rebondissant sur les bords de la
fenetre"""
38. if self.r.x+self.v[0] > self.f.get_width()-self.r.width or self.r.x+self.v[0] < 0
:
39. self.v[0] = -self.v[0] # Inverser la direction en x
40. self.r.x = self.r.x+self.v[0]
41. if self.r.y+self.v[1] > self.f.get_height()-self.r.height or self.r.y+self.v[1] <
0 :
42. self.v[1] = -self.v[1] # Inverser la direction en y
43. self.r.y = self.r.y+self.v[1]
44.
45. class ItiAnime :
46. """ Un objet représente un Bot qui est animé dans une fenêtre Pygame
47.
48. Le Iti est inscrit dans le rectangle englobant défini par r. Il se déplace en
diagonale selon la vitesse v.
49. r : pygame.Rect Le rectangle englobant
50. v : [int,int] Vitesse de déplacement selon les deux axes x et y
51. f : pygame.Surface
52. """
53.
54. def __init__(self,rectangle,vitesse,fenetre):
55. self.r = rectangle
56. self.v = vitesse
57. self.f = fenetre
58.
59. def dessiner(self):
60. """ Dessiner un Iti.
61.
62. Le Iti est inscrit dans le rectangle englobant défini par la variable d'objet r
dans une fenetre de Pygame
63. """
64. self.milieux = self.r.x + self.r.width/2;
65. self.milieuy = self.r.y + self.r.height/2;
66.
67. pygame.draw.ellipse(self.f, Color('pink'),
((self.r.x+self.r.width/3,self.r.y),(self.r.width/3,self.r.height/4))) # Dessiner la tête
68. pygame.draw.arc(self.f,Color('black'),((self.milieux-
self.r.width/12,self.r.y+self.r.height/8),(self.r.width/6,self.r.height/14)),3.1416,0,2) # Le
sourire
69. pygame.draw.ellipse(self.f, Color('black'), ((self.milieux-
self.r.width/8,self.r.y+self.r.height/12),(self.r.width/12,self.r.height/24))) # L'oeil gauche
70. pygame.draw.ellipse(self.f, Color('black'), ((self.milieux+self.r.width/8-
self.r.width/12,self.r.y+self.r.height/12),(self.r.width/12,self.r.height/24))) # L'oeil droit
71. pygame.draw.line(self.f, Color('black'),
(self.milieux,self.r.y+self.r.height/4),(self.milieux,self.r.y+self.r.height*3/4), 2) # Le
corps
72. pygame.draw.line(self.f, Color('black'),
(self.r.x,self.r.y+self.r.height/4),(self.milieux,self.milieuy), 2) # Bras gauche
73. pygame.draw.line(self.f, Color('black'),
(self.r.x+self.r.width,self.r.y+self.r.height/4),(self.milieux,self.milieuy), 2) # Bras droit
74. pygame.draw.line(self.f, Color('black'),
(self.r.x,self.r.y+self.r.height),(self.milieux,self.r.y+self.r.height*3/4), 2) # Jambe gauche
75. pygame.draw.line(self.f, Color('black'),
(self.r.x+self.r.width,self.r.y+self.r.height),(self.milieux,self.r.y+self.r.height*3/4), 2) #
Jambe droite
156
76.
77. def deplacer(self):
78. """ Déplacer selon self.v en diagonale en rebondissant sur les bords de la
fenetre"""
79. if self.r.x+self.v[0] > self.f.get_width()-self.r.width or self.r.x+self.v[0] < 0
:
80. self.v[0] = -self.v[0] # Inverser la direction en x
81. self.r.x = self.r.x+self.v[0]
82. if self.r.y+self.v[1] > self.f.get_height()-self.r.height or self.r.y+self.v[1] <
0 :
83. self.v[1] = -self.v[1] # Inverser la direction en y
84. self.r.y = self.r.y+self.v[1]
85.
86. pygame.init() # Initialiser les modules de Pygame
87. LARGEUR_FENETRE = 400
88. HAUTEUR_FENETRE = 600
89. fenetre = pygame.display.set_mode((LARGEUR_FENETRE, HAUTEUR_FENETRE)) # Ouvrir la fenêtre
90. pygame.display.set_caption("Exemple des Bots et Itis en diagonale : fenetre variable
d'objet") # Définir le titre dans le haut de la fenêtre
91.
92. horloge = pygame.time.Clock() # Pour contrôler la fréquence des scènes
93.
94. # Création de deux BotAnime et deux ItiAnime
95. bot1 = BotAnime(pygame.Rect((0,0),(20,40)),[5,10],fenetre)
96. bot2 = BotAnime(pygame.Rect((100,200),(30,60)),[0,2],fenetre)
97. iti1 = ItiAnime(pygame.Rect((200,150),(40,80)),[3,3],fenetre)
98. iti2 = ItiAnime(pygame.Rect((300,300),(50,100)),[5,10],fenetre)
99.
100. # Boucle d'animation
101. fin = False
102. while not fin :
103. event = pygame.event.poll() # Chercher le prochain évènement à traiter
104. if event.type == pygame.QUIT: # Utilisateur a cliqué sur la fermeture de fenêtre ?
105. fin = True # Fin de la boucle du jeu
106. else :
107. bot1.deplacer()
108. bot2.deplacer()
109. iti1.deplacer()
110. iti2.deplacer()
111.
112. fenetre.fill(Color('white')) # Dessiner le fond de la surface de dessin
113. bot1.dessiner()
114. bot2.dessiner()
115. iti1.dessiner()
116. iti2.dessiner()
117.
118. pygame.display.flip() # Mettre à jour la fenêtre graphique
119.
120. horloge.tick(60) # Pour animer avec 60 images pas seconde
121.
122. pygame.quit() # Terminer pygame
Solution : CodePython/chapitre7/ExerciceClasseContact.py
1. """
2. Exercice classe Contact
3. """
4.
157
5. class Contact :
6. """ Un objet représente un contact
7.
8. nom : str
9. prenom : str
10. numero_telephone : str
11. """
12.
13. def __init__(self,nom,prenom,numero_telephone):
14. self.nom = nom
15. self.prenom = prenom
16. self.numero_telephone = numero_telephone
17.
18. def __str__(self):
19. return 'Le numero de téléphone de '+self.prenom+'
'+self.nom+' est :'+self.numero_telephone
20.
21. liste_contacts = []
22. liste_contacts.append(Contact('Binette','Bob','333-333-3333'))
23. liste_contacts.append(Contact('Emerson','Keith','111-111-
1111'))
24. liste_contacts.append(Contact('Anderson','Ian','222-222-2222'))
25.
26. for un_contact in liste_contacts:
27. print(un_contact)
Résultat :
CodePython/chapitre7/ExempleVariableDobjetPublique.py
1. """
2. Exemple classe Contact
3. Variable d'objet publique
4. """
5.
6. class Contact :
7. """ Un objet représente un contact
8.
9. nom : str
10. prenom : str
11. numero_telephone : str
12. """
158
13.
14. def __init__(self,nom,prenom,numero_telephone):
15. self.nom = nom
16. self.prenom = prenom
17. self.numero_telephone = numero_telephone
18.
19. def __str__(self):
20. return 'Le numéro de téléphone de '+self.prenom+'
'+self.nom+' est :'+self.numero_telephone
21.
22. contact1 = Contact('Binette','Bob','333-333-3333')
23. print(contact1)
24. contact1.numero_telephone = '444-444-4444'
25. print(contact1)
26.
Résultat :
Pour désigner une variable d’objet qui devrait être privée, la convention vue
pour les méthodes qui consiste à précéder son nom d’un soulignement _,
peut aussi être employée.
Dans l’exemple suivant, une nouvelle variable d’objet age est ajoutée à un
contact. La documentation de la classe ne reflète pas cette possibilité.
Lorsque l’objet est imprimé, cette nouvelle variable n’apparaît pas. Ceci
peut entraîner une certaine confusion.
CodePython/chapitre7/ExempleAjoutVariableDobjet.py
1. """
2. Exemple classe Contact
3. Ajout d'une variable d'objet à un objet existant
4. """
5. class Contact :
159
6. """ Un objet représente un contact
7.
8. nom : str
9. prenom : str
10. numero_telephone : str
11. """
12.
13. def __init__(self,nom,prenom,numero_telephone):
14. self.nom = nom
15. self.prenom = prenom
16. self.numero_telephone = numero_telephone
17.
18. def __str__(self):
19. return 'Le numéro de téléphone de '+self.prenom+'
'+self.nom+' est :'+self.numero_telephone
20. print(contact1)
21. contact1.age= 52
22. print(contact1)
23.
Résultat :
CodePython/chapitre7/ExempleVariableDeClasse.py
1. """
2. Exemple d'animation d'entités : création des classes BotAnime et ItiAnime
3. avec variable de classe pour la fenetre
4. """
5. # Importer la librairie de pygame et initialiser
6. import pygame
7. from pygame import Color
8.
9. class BotAnime :
10. """ Un objet représente un Bot qui est animé dans une fenêtre Pygame
160
11.
12. Le Bot est inscrit dans le rectangle englobant défini par r. Il se déplace en
diagonale selon la vitesse v.
13. r : pygame.Rect Le rectangle englobant
14. v : [int,int] Vitesse de déplacement selon les deux axes x et y
15. """
16.
17. @staticmethod
18. def set_fenetre(fenetre):
19. """ Fixer la variable de classe f qui représente la fenetre graphique
20.
21. fenetre : pygame.Surface
22. """
23. BotAnime.f = fenetre
24.
25. def __init__(self,rectangle,vitesse):
26. self.r = rectangle
27. self.v = vitesse
28.
29. def dessiner(self):
30. """ Dessiner un Bot.
31.
32. Le Bot est inscrit dans le rectangle englobant défini par la variable d'objet r
dans une fenetre de Pygame
33. """
34.
35. pygame.draw.ellipse(BotAnime.f, Color('green'),
((self.r.x,self.r.y),(self.r.width, self.r.height/2))) # Dessiner la tête
36. pygame.draw.rect(BotAnime.f, Color('black'),
((self.r.x+self.r.width/4,self.r.y+self.r.height/8),(self.r.width/10,self.r.height/20))) #
L'oeil gauche
37. pygame.draw.rect(BotAnime.f, Color('black'), ((self.r.x+self.r.width*3/4-
self.r.width/10,self.r.y+self.r.height/8),(self.r.width/10,self.r.height/20))) # L'oeil droit
38. pygame.draw.line(BotAnime.f, Color('black'),
(self.r.x+self.r.width/4,self.r.y+self.r.height*3/8),(self.r.x+self.r.width*3/4,self.r.y+self.r
.height*3/8), 2) # La bouche
39. pygame.draw.rect(BotAnime.f, Color('red'),
((self.r.x,self.r.y+self.r.height/2),(self.r.width,self.r.height/2))) # Le corps
40.
41.
42. def deplacer(self):
43. """ Déplacer selon self.v en diagonale en rebondissant sur les bords de la
fenetre"""
44. if self.r.x+self.v[0] > BotAnime.f.get_width()-self.r.width or self.r.x+self.v[0]
< 0 :
45. self.v[0] = -self.v[0] # Inverser la direction en x
46. self.r.x = self.r.x+self.v[0]
47. if self.r.y+self.v[1] > BotAnime.f.get_height()-self.r.height or
self.r.y+self.v[1] < 0 :
48. self.v[1] = -self.v[1] # Inverser la direction en y
49. self.r.y = self.r.y+self.v[1]
50.
51. class ItiAnime :
52. """ Un objet représente un Bot qui est animé dans une fenêtre Pygame
53.
54. Le Bot est inscrit dans le rectangle englobant défini par r. Il se déplace en
diagonale selon vitesse.
55. r : pygame.Rect Le rectangle englobant
56. vitesse : [int,int] Vitesse de déplacement selon les deux axes x et y
57. fenetre : pygame.Surface
58. taille_fenetre : (int,int)
59. """
60. @staticmethod
61. def set_fenetre(fenetre):
62. """ Fixer la variable de classe f qui représente la fenetre graphique
63.
64. fenetre : pygame.Surface
65. """
66. ItiAnime.f = fenetre
67.
68. def __init__(self,rectangle,vitesse):
69. self.r = rectangle
70. self.v = vitesse
71.
72. def dessiner(self):
161
73. """ Dessiner un Iti.
74.
75. Le Iti est inscrit dans le rectangle englobant défini par la variable d'objet r
dans une fenetre de Pygame
76. """
77. self.milieux = self.r.x + self.r.width/2;
78. self.milieuy = self.r.y + self.r.height/2;
79.
80. pygame.draw.ellipse(ItiAnime.f, Color('pink'),
((self.r.x+self.r.width/3,self.r.y),(self.r.width/3,self.r.height/4))) # Dessiner la tête
81. pygame.draw.arc(ItiAnime.f,Color('black'),((self.milieux-
self.r.width/12,self.r.y+self.r.height/8),(self.r.width/6,self.r.height/14)),3.1416,0,2) # Le
sourire
82. pygame.draw.ellipse(ItiAnime.f, Color('black'), ((self.milieux-
self.r.width/8,self.r.y+self.r.height/12),(self.r.width/12,self.r.height/24))) # L'oeil gauche
83. pygame.draw.ellipse(ItiAnime.f, Color('black'), ((self.milieux+self.r.width/8-
self.r.width/12,self.r.y+self.r.height/12),(self.r.width/12,self.r.height/24))) # L'oeil droit
84. pygame.draw.line(ItiAnime.f, Color('black'),
(self.milieux,self.r.y+self.r.height/4),(self.milieux,self.r.y+self.r.height*3/4), 2) # Le
corps
85. pygame.draw.line(ItiAnime.f, Color('black'),
(self.r.x,self.r.y+self.r.height/4),(self.milieux,self.milieuy), 2) # Bras gauche
86. pygame.draw.line(ItiAnime.f, Color('black'),
(self.r.x+self.r.width,self.r.y+self.r.height/4),(self.milieux,self.milieuy), 2) # Bras droit
87. pygame.draw.line(ItiAnime.f, Color('black'),
(self.r.x,self.r.y+self.r.height),(self.milieux,self.r.y+self.r.height*3/4), 2) # Jambe gauche
88. pygame.draw.line(ItiAnime.f, Color('black'),
(self.r.x+self.r.width,self.r.y+self.r.height),(self.milieux,self.r.y+self.r.height*3/4), 2) #
Jambe droite
89.
90. def deplacer(self):
91. """ Déplacer selon self.v en diagonale en rebondissant sur les bords de la
fenetre"""
92. if self.r.x+self.v[0] > ItiAnime.f.get_width()-self.r.width or self.r.x+self.v[0]
< 0 :
93. self.v[0] = -self.v[0] # Inverser la direction en x
94. self.r.x = self.r.x+self.v[0]
95. if self.r.y+self.v[1] > ItiAnime.f.get_height()-self.r.height or
self.r.y+self.v[1] < 0 :
96. self.v[1] = -self.v[1] # Inverser la direction en y
97. self.r.y = self.r.y+self.v[1]
98.
99. pygame.init() # Initialiser les modules de Pygame
100. LARGEUR_FENETRE = 400
101. HAUTEUR_FENETRE = 600
102. fenetre = pygame.display.set_mode((LARGEUR_FENETRE, HAUTEUR_FENETRE)) # Ouvrir la fenêtre
103. BotAnime.set_fenetre(fenetre)
104. ItiAnime.set_fenetre(fenetre)
105. pygame.display.set_caption("Exemple des Bots et Itis en diagonale : fenetre variable
d'objet") # Définir le titre dans le haut de la fenêtre
106.
107. horloge = pygame.time.Clock() # Pour contrôler la fréquence des scènes
108.
109. # Création de deux BotAnime et deux ItiAnime
110. bot1 = BotAnime(pygame.Rect((0,0),(20,40)),[5,10])
111. bot2 = BotAnime(pygame.Rect((100,200),(30,60)),[0,2])
112. iti1 = ItiAnime(pygame.Rect((200,150),(40,80)),[3,3])
113. iti2 = ItiAnime(pygame.Rect((300,300),(50,100)),[5,10])
114.
115. # Boucle d'animation
116. fin = False
117. while not fin :
118. event = pygame.event.poll() # Chercher le prochain évènement à traiter
119. if event.type == pygame.QUIT: # Utilisateur a cliqué sur la fermeture de fenêtre ?
120. fin = True # Fin de la boucle du jeu
121. else :
122. bot1.deplacer()
123. bot2.deplacer()
124. iti1.deplacer()
125. iti2.deplacer()
126.
127. fenetre.fill(Color('white')) # Dessiner le fond de la surface de dessin
128. bot1.dessiner()
129. bot2.dessiner()
130. iti1.dessiner()
162
131. iti2.dessiner()
132.
133. pygame.display.flip() # Mettre à jour la fenêtre graphique
134.
135. horloge.tick(60) # Pour animer avec 60 images pas seconde
136.
137. pygame.quit() # Terminer pygame
@staticmethod
def set_fenetre(fenetre):
""" Fixer la variable de classe f qui représente la fenetre graphique
fenetre : pygame.Surface
"""
BotAnime.f = fenetre
BotAnime.f = fenetre
BotAnime.set_fenetre(fenetre)
CodePython/chapitre7/ExempleSuperClasse.py
1. """
2. Exemple d'animation d'entités : exemple de super-classe EntiteAnimee
3. """
4. import pygame
5. from pygame import Color
6.
7. class EntiteAnimee :
8. """ Un objet représente une entité qui est animée dans une fenêtre Pygame
9.
10. L'entité est inscrite dans le rectangle englobant défini par r. Il se déplace en
diagonale selon la vitesse v.
11. r : pygame.Rect Le rectangle englobant
12. v : [int,int] Vitesse de déplacement selon les deux axes x et y
13. """
14.
15. @staticmethod
16. def set_fenetre(fenetre):
17. """ Fixer la variable de classe f qui représente la fenetre graphique
18.
19. fenetre : pygame.Surface
20. """
21. EntiteAnimee.f = fenetre
22.
23. def __init__(self,rectangle,vitesse):
24. self.r = rectangle
25. self.v = vitesse
26.
27. def deplacer(self):
28. """ Déplacer selon self.v en diagonale en rebondissant sur les bords de la
fenetre"""
29. if self.r.x+self.v[0] > EntiteAnimee.f.get_width()-self.r.width or
self.r.x+self.v[0] < 0 :
30. self.v[0] = -self.v[0] # Inverser la direction en x
31. self.r.x = self.r.x+self.v[0]
32. if self.r.y+self.v[1] > EntiteAnimee.f.get_height()-self.r.height or
self.r.y+self.v[1] < 0 :
33. self.v[1] = -self.v[1] # Inverser la direction en y
34. self.r.y = self.r.y+self.v[1]
35.
36. class BotAnime(EntiteAnimee) :
37. """ Un objet représente un Bot qui est animé dans une fenêtre Pygame
38. Sous-classe de EntiteAnimee
39. """
40.
41. def dessiner(self):
42. """ Dessiner un Bot.
43.
44. Le Bot est inscrit dans le rectangle englobant défini par la variable d'objet r
dans une fenetre de Pygame
45. """
46.
47. pygame.draw.ellipse(BotAnime.f, Color('green'),
((self.r.x,self.r.y),(self.r.width, self.r.height/2))) # Dessiner la tête
48. pygame.draw.rect(BotAnime.f, Color('black'),
((self.r.x+self.r.width/4,self.r.y+self.r.height/8),(self.r.width/10,self.r.height/20))) #
L'oeil gauche
49. pygame.draw.rect(BotAnime.f, Color('black'), ((self.r.x+self.r.width*3/4-
self.r.width/10,self.r.y+self.r.height/8),(self.r.width/10,self.r.height/20))) # L'oeil droit
50. pygame.draw.line(BotAnime.f, Color('black'),
(self.r.x+self.r.width/4,self.r.y+self.r.height*3/8),(self.r.x+self.r.width*3/4,self.r.y+self.r
.height*3/8), 2) # La bouche
164
51. pygame.draw.rect(BotAnime.f, Color('red'),
((self.r.x,self.r.y+self.r.height/2),(self.r.width,self.r.height/2))) # Le corps
52.
53.
54. class ItiAnime(EntiteAnimee) :
55. """ Un objet représente un Bot qui est animé dans une fenêtre Pygame
56. Sous-classe de EntiteAnimee
57. """
58.
59. def dessiner(self):
60. """ Dessiner un Iti.
61.
62. Le Iti est inscrit dans le rectangle englobant défini par la variable d'objet r
dans une fenetre de Pygame
63. """
64. self.milieux = self.r.x + self.r.width/2;
65. self.milieuy = self.r.y + self.r.height/2;
66.
67. pygame.draw.ellipse(ItiAnime.f, Color('pink'),
((self.r.x+self.r.width/3,self.r.y),(self.r.width/3,self.r.height/4))) # Dessiner la tête
68. pygame.draw.arc(ItiAnime.f,Color('black'),((self.milieux-
self.r.width/12,self.r.y+self.r.height/8),(self.r.width/6,self.r.height/14)),3.1416,0,2) # Le
sourire
69. pygame.draw.ellipse(ItiAnime.f, Color('black'), ((self.milieux-
self.r.width/8,self.r.y+self.r.height/12),(self.r.width/12,self.r.height/24))) # L'oeil gauche
70. pygame.draw.ellipse(ItiAnime.f, Color('black'), ((self.milieux+self.r.width/8-
self.r.width/12,self.r.y+self.r.height/12),(self.r.width/12,self.r.height/24))) # L'oeil droit
71. pygame.draw.line(ItiAnime.f, Color('black'),
(self.milieux,self.r.y+self.r.height/4),(self.milieux,self.r.y+self.r.height*3/4), 2) # Le
corps
72. pygame.draw.line(ItiAnime.f, Color('black'),
(self.r.x,self.r.y+self.r.height/4),(self.milieux,self.milieuy), 2) # Bras gauche
73. pygame.draw.line(ItiAnime.f, Color('black'),
(self.r.x+self.r.width,self.r.y+self.r.height/4),(self.milieux,self.milieuy), 2) # Bras droit
74. pygame.draw.line(ItiAnime.f, Color('black'),
(self.r.x,self.r.y+self.r.height),(self.milieux,self.r.y+self.r.height*3/4), 2) # Jambe gauche
75. pygame.draw.line(ItiAnime.f, Color('black'),
(self.r.x+self.r.width,self.r.y+self.r.height),(self.milieux,self.r.y+self.r.height*3/4), 2) #
Jambe droite
76.
77. pygame.init() # Initialiser les modules de Pygame
78. LARGEUR_FENETRE = 400
79. HAUTEUR_FENETRE = 600
80. fenetre = pygame.display.set_mode((LARGEUR_FENETRE, HAUTEUR_FENETRE)) # Ouvrir la fenêtre
81. EntiteAnimee.set_fenetre(fenetre)
82. pygame.display.set_caption("Exemple des Bots et Itis animés en diagonale avec super-classe
EntiteAnimee") # Définir le titre dans le haut de la fenêtre
83.
84. horloge = pygame.time.Clock() # Pour contrôler la fréquence des scènes
85.
86. # Création de deux BotAnime et deux ItiAnime
87. bot1 = BotAnime(pygame.Rect((0,0),(20,40)),[5,10])
88. bot2 = BotAnime(pygame.Rect((100,200),(30,60)),[0,2])
89. iti1 = ItiAnime(pygame.Rect((200,150),(40,80)),[3,3])
90. iti2 = ItiAnime(pygame.Rect((300,300),(50,100)),[5,10])
91.
92. # Boucle d'animation
93. fin = False
94. while not fin :
95. event = pygame.event.poll() # Chercher le prochain évènement à traiter
96. if event.type == pygame.QUIT: # Utilisateur a cliqué sur la fermeture de fenêtre ?
97. fin = True # Fin de la boucle du jeu
98. else :
99. bot1.deplacer()
100. bot2.deplacer()
101. iti1.deplacer()
102. iti2.deplacer()
103.
104. fenetre.fill(Color('white')) # Dessiner le fond de la surface de dessin
105. bot1.dessiner()
106. bot2.dessiner()
107. iti1.dessiner()
108. iti2.dessiner()
109.
110. pygame.display.flip() # Mettre à jour la fenêtre graphique
165
111.
112. horloge.tick(60) # Pour animer avec 60 images pas seconde
113.
114. pygame.quit() # Terminer pygame
115.
class BotAnime(EntiteAnimee) :
EntiteAnimee
BotAnime ItiAnime
Lorsqu’une nouvelle classe est définie sans spécifier de superclasse, elle est
automatiquement une sous-classe de la classe prédéfinie Object. Cette
classe définit plusieurs opérations standards pour toutes les classes Python
dont plusieurs méthodes spéciales telles que __str__(), __repr__(),
__hash__(), etc. Ces méthodes définissent certains comportements de
base pour tous les objets.
class EntiteAnimee :
class EntiteAnimee(Object) :
bot1 = BotAnime(pygame.Rect((0,0),(20,40)),[5,10])
bot1.deplacer()
pygame.draw.ellipse(BotAnime.f,Color('green'),
((self.r.x,self.r.y),(self.r.width, self.r.height/2))) # Dessiner la tête
167
une approche typique et très puissante de la programmation objet. Dans
notre exemple, ceci nous permet de créer des entités animées spécialisées
qui héritent des caractéristiques plus générales de la classe EntiteAnimee.
Une classe dite abstraite ne peut être utilisée directement pour la création
d’objet. Elle n’est utile que comme super-classe d’autres classes non
abstraites dites concrètes qui, elles, sont utilisées pour créer des objets. Il peut
être utile de définir des méthodes de classes abstraites qui n’ont pas de
corps pour obliger une sous-classe à implémenter la méthode dite abstraite
de la super-classe. Il est possible de spécifier qu’une classe est abstraite en
Python avec le module ABC (voir PEP 3119 -- Introducing Abstract Base
Classes | Python.org).
CodePython/chapitre7/ExempleSurchargeDynamique.py
1. """
2. Exemple d'animation d'entités : création d'une super classe EntiteAnime et
3. appel des méthodes d'animation des entités par itération et surcharge dynamique
4. """
5. import pygame
6. from pygame import Color
7.
8. class EntiteAnimee :
9. """ Un objet représente une entité qui est animée dans une fenêtre Pygame
10.
11. L'entité est inscrite dans le rectangle englobant défini par r. Il se déplace en
diagonale selon la vitesse v.
12. r : pygame.Rect Le rectangle englobant
168
13. v : [int,int] Vitesse de déplacement selon les deux axes x et y
14. """
15.
16. @staticmethod
17. def set_fenetre(fenetre):
18. """ Fixer la variable de classe f qui représente la fenetre graphique
19.
20. fenetre : pygame.Surface
21. """
22. EntiteAnimee.f = fenetre
23.
24. def __init__(self,rectangle,vitesse):
25. self.r = rectangle
26. self.v = vitesse
27.
28. def deplacer(self):
29. """ Déplacer selon self.v en diagonale en rebondissant sur les bords de la
fenetre"""
30. if self.r.x+self.v[0] > EntiteAnimee.f.get_width()-self.r.width or
self.r.x+self.v[0] < 0 :
31. self.v[0] = -self.v[0] # Inverser la direction en x
32. self.r.x = self.r.x+self.v[0]
33. if self.r.y+self.v[1] > EntiteAnimee.f.get_height()-self.r.height or
self.r.y+self.v[1] < 0 :
34. self.v[1] = -self.v[1] # Inverser la direction en y
35. self.r.y = self.r.y+self.v[1]
36.
37. class BotAnime(EntiteAnimee) :
38. """ Un objet représente un Bot qui est animé dans une fenêtre Pygame
39. Sous-classe de EntiteAnimee
40. """
41.
42. def dessiner(self):
43. """ Dessiner un Bot.
44.
45. Le Bot est inscrit dans le rectangle englobant défini par la variable d'objet r
dans une fenetre de Pygame
46. """
47.
48. pygame.draw.ellipse(BotAnime.f, Color('green'),
((self.r.x,self.r.y),(self.r.width, self.r.height/2))) # Dessiner la tête
49. pygame.draw.rect(BotAnime.f, Color('black'),
((self.r.x+self.r.width/4,self.r.y+self.r.height/8),(self.r.width/10,self.r.height/20))) #
L'oeil gauche
50. pygame.draw.rect(BotAnime.f, Color('black'), ((self.r.x+self.r.width*3/4-
self.r.width/10,self.r.y+self.r.height/8),(self.r.width/10,self.r.height/20))) # L'oeil droit
51. pygame.draw.line(BotAnime.f, Color('black'),
(self.r.x+self.r.width/4,self.r.y+self.r.height*3/8),(self.r.x+self.r.width*3/4,self.r.y+self.r
.height*3/8), 2) # La bouche
52. pygame.draw.rect(BotAnime.f, Color('red'),
((self.r.x,self.r.y+self.r.height/2),(self.r.width,self.r.height/2))) # Le corps
53.
54.
55. class ItiAnime(EntiteAnimee) :
56. """ Un objet représente un Bot qui est animé dans une fenêtre Pygame
57. Sous-classe de EntiteAnimee
58. """
59.
60. def dessiner(self):
61. """ Dessiner un Iti.
62.
63. Le Iti est inscrit dans le rectangle englobant défini par la variable d'objet r
dans une fenetre de Pygame
64. """
65. self.milieux = self.r.x + self.r.width/2;
66. self.milieuy = self.r.y + self.r.height/2;
67.
68. pygame.draw.ellipse(ItiAnime.f, Color('pink'),
((self.r.x+self.r.width/3,self.r.y),(self.r.width/3,self.r.height/4))) # Dessiner la tête
69. pygame.draw.arc(ItiAnime.f,Color('black'),((self.milieux-
self.r.width/12,self.r.y+self.r.height/8),(self.r.width/6,self.r.height/14)),3.1416,0,2) # Le
sourire
70. pygame.draw.ellipse(ItiAnime.f, Color('black'), ((self.milieux-
self.r.width/8,self.r.y+self.r.height/12),(self.r.width/12,self.r.height/24))) # L'oeil gauche
169
71. pygame.draw.ellipse(ItiAnime.f, Color('black'), ((self.milieux+self.r.width/8-
self.r.width/12,self.r.y+self.r.height/12),(self.r.width/12,self.r.height/24))) # L'oeil droit
72. pygame.draw.line(ItiAnime.f, Color('black'),
(self.milieux,self.r.y+self.r.height/4),(self.milieux,self.r.y+self.r.height*3/4), 2) # Le
corps
73. pygame.draw.line(ItiAnime.f, Color('black'),
(self.r.x,self.r.y+self.r.height/4),(self.milieux,self.milieuy), 2) # Bras gauche
74. pygame.draw.line(ItiAnime.f, Color('black'),
(self.r.x+self.r.width,self.r.y+self.r.height/4),(self.milieux,self.milieuy), 2) # Bras droit
75. pygame.draw.line(ItiAnime.f, Color('black'),
(self.r.x,self.r.y+self.r.height),(self.milieux,self.r.y+self.r.height*3/4), 2) # Jambe gauche
76. pygame.draw.line(ItiAnime.f, Color('black'),
(self.r.x+self.r.width,self.r.y+self.r.height),(self.milieux,self.r.y+self.r.height*3/4), 2) #
Jambe droite
77.
78. pygame.init() # Initialiser les modules de Pygame
79. LARGEUR_FENETRE = 400
80. HAUTEUR_FENETRE = 600
81. fenetre = pygame.display.set_mode((LARGEUR_FENETRE, HAUTEUR_FENETRE)) # Ouvrir la fenêtre
82. EntiteAnimee.set_fenetre(fenetre)
83. pygame.display.set_caption("Exemple des Bots et Itis animés en diagonale avec super-classe
EntiteAnimee") # Définir le titre dans le haut de la fenêtre
84.
85. horloge = pygame.time.Clock() # Pour contrôler la fréquence des scènes
86.
87. # Placer deux BotAnime et deux ItiAnime dans la liste des entités
88. liste_entite = []
89. liste_entite.append(BotAnime(pygame.Rect((0,0),(20,40)),[5,10]))
90. liste_entite.append(BotAnime(pygame.Rect((100,200),(30,60)),[0,2]))
91. liste_entite.append(ItiAnime(pygame.Rect((200,150),(40,80)),[3,3]))
92. liste_entite.append(ItiAnime(pygame.Rect((300,300),(50,100)),[5,10]))
93.
94. # Boucle d'animation
95. fin = False
96. while not fin :
97. event = pygame.event.poll() # Chercher le prochain évènement à traiter
98. if event.type == pygame.QUIT: # Utilisateur a cliqué sur la fermeture de fenêtre ?
99. fin = True # Fin de la boucle du jeu
100. else :
101. for une_entite in liste_entite :
102. une_entite.deplacer()
103.
104. fenetre.fill(Color('white')) # Dessiner le fond de la surface de dessin
105. for une_entite in liste_entite :
106. une_entite.dessiner()
107.
108. pygame.display.flip() # Mettre à jour la fenêtre graphique
109.
110. horloge.tick(60) # Pour animer avec 60 images pas seconde
111.
112. pygame.quit() # Terminer pygame
A noter que les appels aux méthodes deplacer() et dessiner() sont des
exemples de la surcharge dynamique. La bonne version de la méthode à appeler
est déterminée automatiquement à l’exécution du programme en fonction
du type de l’objet.
170
7.4 Création d’un module
Au-delà des fonctions et des classes, les modules offrent un moyen
supplémentaire de gérer la complexité d’un logiciel en les découpant en
plusieurs modules. Il est possible de regrouper des fonctions, classes,
variables et du code exécutable dans un module. Par la suite, le module peut
être importé et utilisé dans un script ou un autre module. Concrètement un
module nommé nomModule correspond à un fichier appelé nomModule.py
qui contient le code des éléments du module.
CodePython/chapitre7/Entite.py
1. """
2. Module qui contient la hiérarchie des classes EntiteAnimee
3. """
4.
5. import pygame
6. from pygame import Color
7.
8. class EntiteAnimee :
9. """ Un objet représente une entité qui est animée dans une fenêtre Pygame
10.
11. L'entité est inscrite dans le rectangle englobant défini par r. Il se déplace en
diagonale selon la vitesse v.
12. r : pygame.Rect Le rectangle englobant
13. v : [int,int] Vitesse de déplacement selon les deux axes x et y
14. """
15.
16. @staticmethod
17. def set_fenetre(fenetre):
18. """ Fixer la variable de classe f qui représente la fenetre graphique
19.
20. fenetre : pygame.Surface
21. """
22. EntiteAnimee.f = fenetre
23.
24. def __init__(self,rectangle,vitesse):
25. self.r = rectangle
26. self.v = vitesse
27.
28. def deplacer(self):
29. """ Déplacer selon self.v en diagonale en rebondissant sur les bords de la
fenetre"""
30. if self.r.x+self.v[0] > EntiteAnimee.f.get_width()-self.r.width or
self.r.x+self.v[0] < 0 :
31. self.v[0] = -self.v[0] # Inverser la direction en x
32. self.r.x = self.r.x+self.v[0]
33. if self.r.y+self.v[1] > EntiteAnimee.f.get_height()-self.r.height or
self.r.y+self.v[1] < 0 :
34. self.v[1] = -self.v[1] # Inverser la direction en y
35. self.r.y = self.r.y+self.v[1]
36.
37. class BotAnime(EntiteAnimee) :
38. """ Un objet représente un Bot qui est animé dans une fenêtre Pygame
39. Sous-classe de EntiteAnimee
171
40. """
41.
42. def dessiner(self):
43. """ Dessiner un Bot.
44.
45. Le Bot est inscrit dans le rectangle englobant défini par la variable d'objet r
dans une fenetre de Pygame
46. """
47.
48. pygame.draw.ellipse(BotAnime.f, Color('green'), ((self.r.x,self.r.y),(self.r.width,
self.r.height/2))) # Dessiner la tête
49. pygame.draw.rect(BotAnime.f, Color('black'),
((self.r.x+self.r.width/4,self.r.y+self.r.height/8),(self.r.width/10,self.r.height/20))) #
L'oeil gauche
50. pygame.draw.rect(BotAnime.f, Color('black'), ((self.r.x+self.r.width*3/4-
self.r.width/10,self.r.y+self.r.height/8),(self.r.width/10,self.r.height/20))) # L'oeil droit
51. pygame.draw.line(BotAnime.f, Color('black'),
(self.r.x+self.r.width/4,self.r.y+self.r.height*3/8),(self.r.x+self.r.width*3/4,self.r.y+self.r
.height*3/8), 2) # La bouche
52. pygame.draw.rect(BotAnime.f, Color('red'),
((self.r.x,self.r.y+self.r.height/2),(self.r.width,self.r.height/2))) # Le corps
53.
54.
55. class ItiAnime(EntiteAnimee) :
56. """ Un objet représente un Bot qui est animé dans une fenêtre Pygame
57. Sous-classe de EntiteAnimee
58. """
59.
60. def dessiner(self):
61. """ Dessiner un Iti.
62.
63. Le Iti est inscrit dans le rectangle englobant défini par la variable d'objet r
dans une fenetre de Pygame
64. """
65. self.milieux = self.r.x + self.r.width/2;
66. self.milieuy = self.r.y + self.r.height/2;
67.
68. pygame.draw.ellipse(ItiAnime.f, Color('pink'),
((self.r.x+self.r.width/3,self.r.y),(self.r.width/3,self.r.height/4))) # Dessiner la tête
69. pygame.draw.arc(ItiAnime.f, Color('black'),((self.milieux-
self.r.width/12,self.r.y+self.r.height/8),(self.r.width/6,self.r.height/14)),3.1416,0,2) # Le
sourire
70. pygame.draw.ellipse(ItiAnime.f, Color('black'), ((self.milieux-
self.r.width/8,self.r.y+self.r.height/12),(self.r.width/12,self.r.height/24))) # L'oeil gauche
71. pygame.draw.ellipse(ItiAnime.f, Color('black'), ((self.milieux+self.r.width/8-
self.r.width/12,self.r.y+self.r.height/12),(self.r.width/12,self.r.height/24))) # L'oeil droit
72. pygame.draw.line(ItiAnime.f, Color('black'),
(self.milieux,self.r.y+self.r.height/4),(self.milieux,self.r.y+self.r.height*3/4), 2) # Le
corps
73. pygame.draw.line(ItiAnime.f, Color('black'),
(self.r.x,self.r.y+self.r.height/4),(self.milieux,self.milieuy), 2) # Bras gauche
74. pygame.draw.line(ItiAnime.f, Color('black'),
(self.r.x+self.r.width,self.r.y+self.r.height/4),(self.milieux,self.milieuy), 2) # Bras droit
75. pygame.draw.line(ItiAnime.f, Color('black'),
(self.r.x,self.r.y+self.r.height),(self.milieux,self.r.y+self.r.height*3/4), 2) # Jambe gauche
76. pygame.draw.line(ItiAnime.f, Color('black'),
(self.r.x+self.r.width,self.r.y+self.r.height),(self.milieux,self.r.y+self.r.height*3/4), 2) #
Jambe droite
CodePython/chapitre7/ExempleModuleImportEntite.py
1. """
2. Exemple d'animation d'entités avec module Entite
3. """
4. import Entite
5. import pygame
6. from pygame import Color
7.
8. pygame.init() # Initialiser les modules de Pygame
9. LARGEUR_FENETRE = 400
10. HAUTEUR_FENETRE = 600
11. fenetre = pygame.display.set_mode((LARGEUR_FENETRE, HAUTEUR_FENETRE)) # Ouvrir la fenêtre
12. Entite.EntiteAnimee.set_fenetre(fenetre)
172
13. pygame.display.set_caption("Exemple des Bots et Itis animés en diagonale avec super-classe
EntiteAnimee") # Définir le titre dans le haut de la fenêtre
14.
15. horloge = pygame.time.Clock() # Pour contrôler la fréquence des scènes
16.
17. # Placer deux BotAnime et deux ItiAnime dans la liste des entités
18. liste_entite = []
19. liste_entite.append(Entite.BotAnime(pygame.Rect((0,0),(20,40)),[5,10]))
20. liste_entite.append(Entite.BotAnime(pygame.Rect((100,200),(30,60)),[0,2]))
21. liste_entite.append(Entite.ItiAnime(pygame.Rect((200,150),(40,80)),[3,3]))
22. liste_entite.append(Entite.ItiAnime(pygame.Rect((300,300),(50,100)),[5,10]))
23.
24. # Boucle d'animation
25. fin = False
26. while not fin :
27. event = pygame.event.poll() # Chercher le prochain évènement à traiter
28. if event.type == pygame.QUIT: # Utilisateur a cliqué sur la fermeture de fenêtre ?
29. fin = True # Fin de la boucle du jeu
30. else :
31. for une_entite in liste_entite :
32. une_entite.deplacer()
33.
34. fenetre.fill(Color('white')) # Dessiner le fond de la surface de dessin
35. for une_entite in liste_entite :
36. une_entite.dessiner()
37. pygame.display.flip() # Mettre à jour la fenêtre graphique
38.
39. horloge.tick(60) # Pour animer avec 60 images pas seconde
40.
41. pygame.quit() # Terminer pygame
import Entite
liste_entite.append(Entite.BotAnime(pygame.Rect((0,0),(20,40)),[5,10]))
173
Il y a d’autres possibilités pour importer un module. Il est possible
d’assigner un nom différent au module avec la clause as. La ligne suivante
renomme le module Entite avec l’alias En :
import Entite as En
liste_entite.append(En.BotAnime(pygame.Rect((0,0),(20,40)),[5,10]))
Les séquences Python telles que list, tuple et str sont des itérables. Un
objet itérable doit implémenter la méthode spéciale __iter__() qui
retourne un objet itérateur permettant d’itérer sur les éléments de l’itérable.
Pour obtenir un objet itérateur, il faut appeler la fonction iter() qui
appelle la méthode spéciale __iter__(). Un objet itérateur doit implémenter
une méthode spéciale __next__(). La fonction next() retourne le
prochain objet à traiter en appelant la méthode __next__() sur l’objet
itérateur. S’il n’y en a plus, la méthode retourne l’exception
StopIteration.
1. une_liste = [3,1,2]
2. un_iterateur = iter(une_liste)
3. print(un_iterateur)
4.
5. print(next(un_iterateur))
6. print(next(un_iterateur))
7. print(next(un_iterateur))
174
8. print(next(un_iterateur))
Résultat :
File
"C:\Users\vango\OneDrive\Documents\GitHub\CodePython\ExempleIterNext.py",
line 10, in <module>
print(next(un_iterateur))
StopIteration
Il est possible d’appeler les méthodes spéciales directement mais ceci n’est
pas recommandé :
1. une_liste = [3,1,2]
2. un_iterateur = une_liste.__iter__()
3. print(un_iterateur)
4.
5. print(un_iterateur.__next__())
6. print(un_iterateur.__next__())
7. print(un_iterateur.__next__())
8. print(un_iterateur.__next__())
Résultat :
File
"C:\Users\vango\OneDrive\Documents\GitHub\CodePython\ExempleMethodesSpeciale
sIterNext.py", line 10, in <module>
print(un_iterateur.__next__())
StopIteration
L’exemple suivant utilise une boucle qui appelle explicitement les fonctions
iter() et next() pour parcourir la liste :
1. une_liste = [3,1,2]
2. un_iterateur = iter(une_liste)
3. while True:
4. try:
5. suivant = next(un_iterateur)
6. print(suivant)
7. except StopIteration:
175
8. print("Fin de l'itérable")
9. break
Résultat :
3
1
2
Fin de l'itérable
Le même effet est produit par le for d’une manière beaucoup plus
succincte :
CodePython/chapitre7/ExempleIterator.py
1. class SequenceSup0:
2. """Représente la séquence des éléments supérieurs à 0"""
3. def __init__(self, seq):
4. self.seq = seq
5. self.taille = len(seq)
6. self.indice = 0
7.
8. def __iter__(self):
9. return self
10.
11. def __next__(self):
12. while (self.indice < self.taille):
13. if (self.seq[self.indice] > 0) :
14. self.indice = self.indice + 1
15. return self.seq[self.indice-1]
16. self.indice = self.indice + 1
17. raise StopIteration
18.
19. for element in SequenceSup0([3,-1,4,6,0,8]):
20. print(element)
21.
Résultat :
3
4
176
6
8
Exercice. Codez une classe itérable qui retourne les éléments pairs d’une
liste d’entiers.
def filtreSup0(seq) :
for element in seq :
if element > 0 :
yield element
Expression génératrice
177
>>> liste_sup0 = [element for element in [3,-1,4,6,0,8] if element > 0]
>>> liste_sup0
[3, 4, 6, 8]
Exercice. Produire une liste formée de la somme des éléments deux à deux
des deux listes [1,2,3] et [5,3,4].
Solution.
>>> [i+j for i,j in zip([1,2,3],[5,3,4])]
[6, 5, 7]
À l’inverse, pour produire deux listes à partir des éléments d’une liste de
couples, il est possible de faire appel à l’opérateur * de décompaction
d’argument en combinaison avec la fonction zip(). Dans l’exemple
suivant, en passant *liste_triplets à la fonction zip(), la liste de
couples est décompactée en trois arguments, chacun des arguments étant
un couple. L’itérateur produit par la fonction zip() construit un premier
triplet formé des premiers éléments des trois couples, et un second triplet
formé des seconds éléments des trois couples.
178
[5, 3, 4]
Exercice. Former deux listes, la liste des noms et la liste des numéros de
téléphones, à partir de la liste des couples suivante :
A noter qu’il est possible de produire le même effet avec une liste en
compréhension :
L’exemple suivant calcule les carrés d’une liste d’entiers. À cet effet, la
fonction carres() est définie et employée comme argument du map().
179
...
>>> list(map(carres, [5,2,10]))
[25, 4, 100]
Dans le cas d’un map(), l’expression doit être définie par une fonction.
Néanmoins, il est possible d’employer une expression lambda pour définir
la fonction directement dans le map().
Une fonction lambda est utile lorsqu’il faut définir une fonction mais qu’il
n’est pas nécessaire de lui donner un nom. Par exemple, elle peut être
définie directement comme argument d’une fonction qui prend une autre
fonction en paramètre. Ici, l’exemple des carrés est repris avec une fonction
lambda passée en argument au map().
180
[9, 100, 25, 4]
Ainsi, le reduce() peut être combiné à l’exemple des carrés des entiers
pour calculer la somme des carrés :
A noter que le même effet peut être produit avec un générateur ou une liste
en compréhension incluant une clause if:
Solution.
>>> from functools import reduce
>>> reduce(lambda x,y: x+y, map (lambda x,y: x*y, [5,2,10],[4,10,5]))
90
>>> sum(x * y for x, y in zip([5, 2, 10], [4, 10, 5]))
90
182
8 Animation 2D et développement
d’un jeu simple
Ce chapitre présente le développement d’un jeu simple qui combine
l’animation 2D et une interactivité de base au moyen de la souris. Les
acteurs sont raffinés par rapport au chapitre précédent en ajoutant des sons
et des mouvements. Le jeu démarre en animant une série d’entités. Le but
de l’utilisateur est de détruire ces entités en cliquant dessus avec la souris.
Lorsque l’entité est touchée, elle disparaît en poussant un cri de désarroi.
• son : cette variable d’objet est un clip sonore qui est employé pour
que l’entité émette un son lorsqu’elle est éliminée
Les classes des entités du jeu sont placées dans le module EntiteDuJeu.
CodePython/chapitre8/EntiteDuJeu.py
1. """
2. Module qui contient la hiérarchie des classes EntiteAnimee
3. """
4. # Importer la librairie de pygame et initialiser
5. import pygame
6. from pygame import Color
7.
8. class EntiteAnimeeAvecSon :
9. """ Un objet représente une entité qui est animée dans une fenêtre Pygame.
10.
11. L'entité est inscrite dans le rectangle englobant r. Il se déplace en diagonale selon
la vitesse v.
12. Elle émet un son lorsque supprimée.
13. r : pygame.Rect Le rectangle englobant
14. v : [int,int] Vitesse de déplacement selon les deux axes x et y
15. son : pygame.mixer.Sound
16. """
17.
18. @staticmethod
19. def set_fenetre(fenetre):
20. """ Fixer la variable de classe f qui représente la fenetre graphique
21.
22. fenetre : pygame.Surface
23. """
24. EntiteAnimeeAvecSon.f = fenetre
25.
26. def __init__(self,rectangle,vitesse,fichier_son):
27. self.r = rectangle
28. self.v = vitesse
183
29. self.son = pygame.mixer.Sound(fichier_son)
30.
31. def prochaine_scene(self):
32. """ Déplacer selon self.v en diagonale en rebondissant sur les bords de la
fenetre"""
33. if self.r.x+self.v[0] > EntiteAnimeeAvecSon.f.get_width()-self.r.width or
self.r.x+self.v[0] < 0 :
34. self.v[0] = -self.v[0] # Inverser la direction en x
35. self.r.x = self.r.x+self.v[0]
36. if self.r.y+self.v[1] > EntiteAnimeeAvecSon.f.get_height()-self.r.height or
self.r.y+self.v[1] < 0 :
37. self.v[1] = -self.v[1] # Inverser la direction en y
38. self.r.y = self.r.y+self.v[1]
39.
40. def touche(self,x,y):
41. return ((x >= self.r.x) and (x <= self.r.x + self.r.width) and (y >= self.r.y) and
(y <= self.r.y + self.r.height))
42.
43. def emettre_son(self):
44. self.son.play()
45.
46. class EntiteAvecEtat(EntiteAnimeeAvecSon):
47. def __init__(self,rectangle,vitesse,fichier_son,nombre_etats):
48. super().__init__(rectangle,vitesse,fichier_son)
49. self.nombre_etats = nombre_etats
50. self.etat_courant = 0
51.
52. def prochaine_scene(self):
53. self.etat_courant=(self.etat_courant+1)%self.nombre_etats
54. super().prochaine_scene()
55.
56.
57. class BotAnime(EntiteAnimeeAvecSon) :
58. """ Un objet représente un Bot qui est animé dans une fenêtre Pygame
59. Sous-classe de EntiteAnimee
60. """
61.
62. def dessiner(self):
63. """ Dessiner un Bot.
64.
65. Le Bot est inscrit dans le rectangle englobant défini par la variable d'objet r
dans une fenetre de Pygame
66. """
67.
68. pygame.draw.ellipse(BotAnime.f, Color('green'),
((self.r.x,self.r.y),(self.r.width, self.r.height/2))) # Dessiner la tête
69. pygame.draw.rect(BotAnime.f, Color('black'),
((self.r.x+self.r.width/4,self.r.y+self.r.height/8),(self.r.width/10,self.r.height/20))) #
L'oeil gauche
70. pygame.draw.rect(BotAnime.f, Color('black'), ((self.r.x+self.r.width*3/4-
self.r.width/10,self.r.y+self.r.height/8),(self.r.width/10,self.r.height/20))) # L'oeil droit
71. pygame.draw.line(BotAnime.f, Color('black'),
(self.r.x+self.r.width/4,self.r.y+self.r.height*3/8),(self.r.x+self.r.width*3/4,self.r.y+self.r
.height*3/8), 2) # La bouche
72. pygame.draw.rect(BotAnime.f, Color('red'),
((self.r.x,self.r.y+self.r.height/2),(self.r.width,self.r.height/2))) # Le corps
73.
74.
75. class ItiAnimeVolant(EntiteAvecEtat) :
76. """ Un objet représente un Iti qui est animé dans une fenêtre Pygame
77. Sous-classe de EntiteAnimee
78. """
79.
80. def dessiner(self):
81. """ Dessiner un Iti.
82.
83. Le Iti est inscrit dans le rectangle englobant défini par la variable d'objet r
dans une fenetre de Pygame
84. L'etat courant détermine la hauteur des bras.
85. """
86. self.milieux = self.r.x + self.r.width/2;
87. self.milieuy = self.r.y + self.r.height/2;
88.
89. pygame.draw.ellipse(ItiAnimeVolant.f, Color('pink'),
((self.r.x+self.r.width/3,self.r.y),(self.r.width/3,self.r.height/4))) # Dessiner la tête
184
90. pygame.draw.arc(ItiAnimeVolant.f,Color('black'),((self.milieux-
self.r.width/12,self.r.y+self.r.height/8),(self.r.width/6,self.r.height/14)),3.1416,0,2) # Le
sourire
91. pygame.draw.ellipse(ItiAnimeVolant.f, Color('black'), ((self.milieux-
self.r.width/8,self.r.y+self.r.height/12),(self.r.width/12,self.r.height/24))) # L'oeil gauche
92. pygame.draw.ellipse(ItiAnimeVolant.f, Color('black'),
((self.milieux+self.r.width/8-
self.r.width/12,self.r.y+self.r.height/12),(self.r.width/12,self.r.height/24))) # L'oeil droit
93. pygame.draw.line(ItiAnimeVolant.f, Color('black'),
(self.milieux,self.r.y+self.r.height/4),(self.milieux,self.r.y+self.r.height*3/4), 2) # Le
corps
94. pygame.draw.line(ItiAnimeVolant.f, Color('black'),
(self.r.x,self.r.y+self.r.height/4+(self.r.height/4)*self.etat_courant),(self.milieux,self.mili
euy), 2) # Bras gauche
95. pygame.draw.line(ItiAnimeVolant.f, Color('black'),
(self.r.x+self.r.width,self.r.y+self.r.height/4+(self.r.height/4)*self.etat_courant),(self.mili
eux,self.milieuy), 2) # Bras droit
96. pygame.draw.line(ItiAnimeVolant.f, Color('black'),
(self.r.x,self.r.y+self.r.height),(self.milieux,self.r.y+self.r.height*3/4), 2) # Jambe gauche
97. pygame.draw.line(ItiAnimeVolant.f, Color('black'),
(self.r.x+self.r.width,self.r.y+self.r.height),(self.milieux,self.r.y+self.r.height*3/4), 2) #
Jambe droite
98.
99.
100. class EntiteAnimeeParImages(EntiteAvecEtat):
101. def __init__(self,rectangle,vitesse,fichier_son,nombre_etats,nom_dossier):
102. super().__init__(rectangle,vitesse,fichier_son,nombre_etats)
103. self.image_animation = []
104. for i in range(nombre_etats):
105.
self.image_animation.append(pygame.transform.scale(pygame.image.load(nom_dossier+"/"+nom_dossie
r+str(i+1)+".gif"),(self.r.width,self.r.height)))
106. def dessiner(self):
107.
EntiteAnimeeParImages.f.blit(self.image_animation[self.etat_courant],[self.r.x,self.r.y])
108.
EntiteAnimeeAvecSon
BotAnime EntiteAvecEtat
ItitAnimeVolant EntiteAnimeeParImages
185
self.son = pygame.mixer.Sound(fichier_son)
Lorsqu’une entité est touchée par le clic de la souris, elle émet le son avec
la méthode emettre_son() qui ne fait qu’appeler la méthode play() sur
l’objet son :
def emettre_son(self):
self.son.play()
186
dynamiquement quelle méthode __init__() appeler en fonction de la
classe de l’objet visé. Ce principe est appelé surcharge dynamique (ou encore
polymorphisme dynamique) d’un nom de méthode. Si la méthode est appelée pour
un objet de la sous-classe, c’est la méthode de la sous-classe qui est
employée. Dans la méthode __init__() de la sous-classe, il est aussi
possible d’appeler la méthode __init__() de la super-classe, comme c’est
le cas de notre exemple. Afin d’éviter toute ambigüité, dans la sous-classe,
on emploie la syntaxe super().__init__() afin de préciser que l’appel
se fait sur la méthode de la super-classe. Ainsi dans notre exemple, le
__init__() de la sous-classe redéfinit le __init__() de la super-classe
en lui ajoutant un comportement qui consiste à initialiser les variables
d’objets nombre_etats et etat_courant.
def __init__(self,rectangle,vitesse,fichier_son,nombre_etats):
super().__init__(rectangle,vitesse,fichier_son)
self.nombre_etats = nombre_etats
self.etat_courant = 0
def prochaine_scene(self):
self.etat_courant=(self.etat_courant+1)%self.nombre_etats
super().prochaine_scene()
pygame.draw.line(ItiAnimeVolant.f,NOIR,(self.r.x+self.r.width,self.r.y+self.r.height/4+(self.r.height/4
)*self.etat_courant),
(self.milieux,self.milieuy), 2)
187
• Animation bitmap de mouvements
pygame.transform.scale(pygame.image.load(nom_dossier+"/"+nom_dossier+str(i+1)+".gif"),
(self.r.width,self.r.height)))
def dessiner(self):
EntiteAnimeeParImages.f.blit(self.image_animation[self.etat_courant],[self.r.x,self.r.y])
Figure 14. Série d’images pour l’animation du coq qui bât des ailes pour voler
approximativement.
L’argument
nom_dossier+"/"+nom_dossier+str(i+1)+".gif"
188
est le chemin du fichier. L’argument nom_dossier passé à __init__()
est par convention le nom d’un dossier qui contient les fichiers d’images
qui sont nommés selon la convention nom_dossieri où i est un entier de
1 à n, n étant le nombre d’images de la séquence d’animation. L’entier
désigne l’ordre de l’image dans la séquence d’animation. Dans notre
exemple de coq, les fichiers sont nommés coqi.gif et ils sont dans un
dossier nommé coq du répertoire du programme Python.
def dessiner(self):
EntiteAnimeeParImages.f.blit(self.image_animation[self.etat_courant],[self.r
.x,self.r.y])
Modes de transparence
Lorsqu’une image est affichée, chacun des pixels peut être affiché selon
trois modes de transparence :
Transparent. Le pixel n’est pas affiché. On voit donc ce qui est déjà affiché.
Dans notre exemple de coq, le gris foncé qui entoure le coq correspond à
des pixels transparents.
189
Le programme principal pour notre exemple de jeu est semblable à
l’exemple d’animation du chapitre précédent. Il ajoute une interaction avec
la souris semblable à celle vue au chapitre 5.
CodePython/chapitre8/ExempleJeuSimple.py
1. """
2. Exemple de jeu : programme principal
3. """
4. import EntiteDuJeu
5. import pygame
6. from pygame import Color
7.
8. pygame.init() # Initialiser les modules de Pygame
9. LARGEUR_FENETRE = 400
10. HAUTEUR_FENETRE = 600
11. fenetre = pygame.display.set_mode((LARGEUR_FENETRE, HAUTEUR_FENETRE)) # Ouvrir la fenêtre
12. EntiteDuJeu.EntiteAnimeeAvecSon.set_fenetre(fenetre)
13. pygame.display.set_caption("Exemple de jeu avec module EntiteDuJeu")
14.
15. horloge = pygame.time.Clock() # Pour contrôler la fréquence des scènes
16.
17. # Création de la liste des entités du jeu
18. liste_entite = []
19. liste_entite.append(EntiteDuJeu.BotAnime(pygame.Rect((10,100),(40,80)),[3,3],"Son2.wav"))
20. liste_entite.append(EntiteDuJeu.BotAnime(pygame.Rect((200,200),(50,100)),[0,2],"Son2.wav"))
21.
liste_entite.append(EntiteDuJeu.ItiAnimeVolant(pygame.Rect((200,50),(50,100)),[3,0],"Son3.wav",
3))
22.
liste_entite.append(EntiteDuJeu.EntiteAnimeeParImages(pygame.Rect((50,100),(100,100)),[5,5],"So
n4.wav",9,"coq"))
23.
24. # Boucle d'animation
25. fin = False
26. while not fin :
27. event = pygame.event.poll() # Chercher le prochain évènement à traiter
28. if event.type == pygame.QUIT: # Utilisateur a cliqué sur la fermeture de fenêtre ?
29. fin = True # Fin de la boucle du jeu
30. else :
31. if event.type == pygame.MOUSEBUTTONUP : # Utilisateur a cliqué dans la fenêtre ?
32. x=event.pos[0] # Position x de la souris
33. y=event.pos[1] # Position y de la souris
34. for une_entite in liste_entite :
35. if une_entite.touche(x,y):
36. une_entite.emettre_son()
37. liste_entite.remove(une_entite)
38.
39. for une_entite in liste_entite :
40. une_entite.prochaine_scene()
41.
42. fenetre.fill(Color('white')) # Dessiner le fond de la surface de dessin
43. for une_entite in liste_entite :
44. une_entite.dessiner()
45.
46. pygame.display.flip() # Mettre à jour la fenêtre graphique
47. horloge.tick(60) # Pour animer avec 60 images pas seconde
48.
49. pygame.quit() # Terminer pygame
1. liste_entite = []
2. liste_entite.append(EntiteDuJeu.BotAnime(pygame.Rect((10,100),(40,80)),[3,3],"Son2.wav"))
3. liste_entite.append(EntiteDuJeu.BotAnime(pygame.Rect((200,200),(50,100)),[0,2],"Son2.wav"))
4.
liste_entite.append(EntiteDuJeu.ItiAnimeVolant(pygame.Rect((200,50),(50,100)),[3,0],"Son3.wav",
3))
5. liste_entite.append(EntiteDuJeu.EntiteAnimeeParImages(pygame.Rect((50,100),(100,100)),[5,5],
190
6. "Son4.wav",9,"coq"))
191
9 Exceptions
Les exceptions ont été introduites à la section 2.3. Ce chapitre montre
comment traiter les exceptions par programmation. Lorsqu’une condition
anormale se produit, un programme Python peut lever une exception avec
l’énoncé raise. Plusieurs exceptions prédéfinies existent. Une exception
prédéfinie est un objet d’une classe qui doit être une sous-classe de la classe
BaseException. Lorsqu’une exception est levée, le résultat est une
interruption du programme et l’affichage d’un message explicatif. D’autre
part, il est aussi possible de traiter une exception dans le programme lui-
même sans provoquer son interruption. L’énoncé try est prévu à cet effet.
CodePython/chapitre9/ExempleTry.py
1. """
2. Lire une suite d'entiers jusqu'à ce que l'entier 0 soit entré et afficher la somme
3. des entiers lus. Ignorer les données erronées. Exemple du try.
4. """
5.
6. somme=0
7. while True:
8. chaine = input("Entrez un nombre entier, 0 pour terminer :")
9. if chaine != '0' :
10. try :
11. somme = somme + int(chaine)
12. except ValueError :
13. print("La chaîne '" + chaine + "' n'est pas un nombre et sera exclue")
14. else:
15. break
16. print("La somme des entiers est:",somme)
L’entête try est suivi d’un bloc d’énoncés dont l’exécution est tentée en
anticipant la possibilité qu’une exception soit levée. Dans notre exemple, il
y a un seul énoncé dans le bloc :
try :
somme = somme + int(chaine)
Si une exception est levée dans le bloc, elle peut être attrapée par une clause
except. Dans notre exemple, il y a une seule clause except :
except ValueError :
192
print("La chaîne '" + chaine + "' n'est pas un nombre et sera
exclue")
La clause except spécifie le type d’exception qui est visé. Elle est suivie
d’un bloc d’énoncés qui sont exécutés dans le cas où l’exception du type
visé est levée dans le try. Dans notre exemple, l’exception ValueError
peut être levée si la chaine passée à int() ne représente pas un nombre
entier. L’exception est alors attrapée par la clause except ValueError.
Ceci conduit à l’affichage du message avec print(). L’exécution du
programme se poursuit en passant à l’itération suivante du while. Dans la
version précédente de cet exemple, le programme aurait été interrompu par
l’exception.
CodePython/chapitre9/EntiteDuJeuAvecException.py
1. """
2. Module qui contient la hiérarchie des classes EntiteAnimee : exemples d'exceptions
3. """
4. import pygame
5. from pygame import Color
6.
7. class Erreur(Exception):
8. """ Classe de base pour les exceptions de ce module
9. """
10. pass
11.
12. class CoordonneesEntiteErreur(Erreur):
13. """ Les coordonnées dépassent la fenetre d'animation
14. """
15. def __init__(self,position):
16. self.position = position
17.
18. class TailleExcessiveErreur(Erreur):
19. """ La taille de l'entité est excessive par rapport à la fenetre d'animation
20. """
21. def __init__(self, taille):
22. self.taille=taille
23.
24. class EntiteAnimeeAvecSon :
25. """ Un objet représente une entité qui est animée dans une fenêtre Pygame.
26.
27. L'entité est inscrite dans le rectangle englobant r. Il se déplace en diagonale selon
la vitesse v.
28. Elle émet un son lorsque supprimée.
29. r : pygame.Rect Le rectangle englobant
30. v : [int,int] Vitesse de déplacement selon les deux axes x et y
31. son : pygame.mixer.Sound
32. """
33.
34. @staticmethod
35. def set_fenetre(fenetre):
36. """ Fixer la variable de classe f qui représente la fenetre graphique
37.
38. fenetre : pygame.Surface
39. """
193
40. EntiteAnimeeAvecSon.f = fenetre
41.
42. def __init__(self,rectangle,vitesse,fichier_son):
43. if rectangle.x < 0 or rectangle.y < 0 or rectangle.x >
EntiteAnimeeAvecSon.f.get_width() or rectangle.y > EntiteAnimeeAvecSon.f.get_height() :
44. raise CoordonneesEntiteErreur((rectangle.x,rectangle.y))
45. elif rectangle.width > EntiteAnimeeAvecSon.f.get_width() or rectangle.height >
EntiteAnimeeAvecSon.f.get_height() :
46. raise TailleExcessiveErreur(rectangle.size)
47. else:
48. self.r = rectangle
49. self.v = vitesse
50. self.son = pygame.mixer.Sound(fichier_son)
51.
52. def prochaine_scene(self):
53. """ Déplacer selon self.v en diagonale en rebondissant sur les bords de la
fenetre"""
54. if self.r.x+self.v[0] > EntiteAnimeeAvecSon.f.get_width()-self.r.width or
self.r.x+self.v[0] < 0 :
55. self.v[0] = -self.v[0] # Inverser la direction en x
56. self.r.x = self.r.x+self.v[0]
57. if self.r.y+self.v[1] > EntiteAnimeeAvecSon.f.get_height()-self.r.height or
self.r.y+self.v[1] < 0 :
58. self.v[1] = -self.v[1] # Inverser la direction en y
59. self.r.y = self.r.y+self.v[1]
60.
61. def touche(self,x,y):
62. return ((x >= self.r.x) and (x <= self.r.x + self.r.width) and (y >= self.r.y) and
(y <= self.r.y + self.r.height))
63.
64. def emettre_son(self):
65. self.son.play()
66.
67. class EntiteAvecEtat(EntiteAnimeeAvecSon):
68. def __init__(self,rectangle,vitesse,fichier_son,nombre_etats):
69. super().__init__(rectangle,vitesse,fichier_son)
70. self.nombre_etats = nombre_etats
71. self.etat_courant = 0
72.
73. def prochaine_scene(self):
74. self.etat_courant=(self.etat_courant+1)%self.nombre_etats
75. super().prochaine_scene()
76.
77. class BotAnime(EntiteAnimeeAvecSon) :
78. """ Un objet représente un Bot qui est animé dans une fenêtre Pygame
79. Sous-classe de EntiteAnimee
80. """
81.
82. def dessiner(self):
83. """ Dessiner un Bot.
84.
85. Le Bot est inscrit dans le rectangle englobant défini par la variable d'objet r
dans une fenetre de Pygame
86. """
87.
88. pygame.draw.ellipse(BotAnime.f, Color('green'),
((self.r.x,self.r.y),(self.r.width, self.r.height/2))) # Dessiner la tête
89. pygame.draw.rect(BotAnime.f, Color('black'),
((self.r.x+self.r.width/4,self.r.y+self.r.height/8),(self.r.width/10,self.r.height/20))) #
L'oeil gauche
90. pygame.draw.rect(BotAnime.f, Color('black'), ((self.r.x+self.r.width*3/4-
self.r.width/10,self.r.y+self.r.height/8),(self.r.width/10,self.r.height/20))) # L'oeil droit
91. pygame.draw.line(BotAnime.f, Color('black'),
(self.r.x+self.r.width/4,self.r.y+self.r.height*3/8),(self.r.x+self.r.width*3/4,self.r.y+self.r
.height*3/8), 2) # La bouche
92. pygame.draw.rect(BotAnime.f, Color('red'),
((self.r.x,self.r.y+self.r.height/2),(self.r.width,self.r.height/2))) # Le corps
93.
94.
95. class ItiAnimeVolant(EntiteAvecEtat) :
96. """ Un objet représente un Iti qui est animé dans une fenêtre Pygame
97. Sous-classe de EntiteAnimee
98. """
99.
100. def dessiner(self):
194
101. """ Dessiner un Iti.
102.
103. Le Iti est inscrit dans le rectangle englobant défini par la variable d'objet r
dans une fenetre de Pygame
104. L'etat courant détermine la hauteur des bras.
105. """
106. self.milieux = self.r.x + self.r.width/2;
107. self.milieuy = self.r.y + self.r.height/2;
108.
109. pygame.draw.ellipse(ItiAnimeVolant.f, Color('pink'),
((self.r.x+self.r.width/3,self.r.y),(self.r.width/3,self.r.height/4))) # Dessiner la tête
110. pygame.draw.arc(ItiAnimeVolant.f,Color('black'),((self.milieux-
self.r.width/12,self.r.y+self.r.height/8),(self.r.width/6,self.r.height/14)),3.1416,0,2) # Le
sourire
111. pygame.draw.ellipse(ItiAnimeVolant.f, Color('black'), ((self.milieux-
self.r.width/8,self.r.y+self.r.height/12),(self.r.width/12,self.r.height/24))) # L'oeil gauche
112. pygame.draw.ellipse(ItiAnimeVolant.f, Color('black'),
((self.milieux+self.r.width/8-
self.r.width/12,self.r.y+self.r.height/12),(self.r.width/12,self.r.height/24))) # L'oeil droit
113. pygame.draw.line(ItiAnimeVolant.f, Color('black'),
(self.milieux,self.r.y+self.r.height/4),(self.milieux,self.r.y+self.r.height*3/4), 2) # Le
corps
114. pygame.draw.line(ItiAnimeVolant.f, Color('black'),
(self.r.x,self.r.y+self.r.height/4+(self.r.height/4)*self.etat_courant),(self.milieux,self.mili
euy), 2) # Bras gauche
115. pygame.draw.line(ItiAnimeVolant.f, Color('black'),
(self.r.x+self.r.width,self.r.y+self.r.height/4+(self.r.height/4)*self.etat_courant),(self.mili
eux,self.milieuy), 2) # Bras droit
116. pygame.draw.line(ItiAnimeVolant.f, Color('black'),
(self.r.x,self.r.y+self.r.height),(self.milieux,self.r.y+self.r.height*3/4), 2) # Jambe gauche
117. pygame.draw.line(ItiAnimeVolant.f, Color('black'),
(self.r.x+self.r.width,self.r.y+self.r.height),(self.milieux,self.r.y+self.r.height*3/4), 2) #
Jambe droite
118.
119.
120. class EntiteAnimeeParImages(EntiteAvecEtat):
121. def __init__(self,rectangle,vitesse,fichier_son,nombre_etats,nom_dossier):
122. super().__init__(rectangle,vitesse,fichier_son,nombre_etats)
123. self.image_animation = []
124. for i in range(nombre_etats):
125.
self.image_animation.append(pygame.transform.scale(pygame.image.load(nom_dossier+"/"+nom_dossie
r+str(i+1)+".gif"),(self.r.width,self.r.height)))
126. def dessiner(self):
127.
EntiteAnimeeParImages.f.blit(self.image_animation[self.etat_courant],[self.r.x,self.r.y])
128. …
129.
class Erreur(Exception):
""" Classe de base pour les exceptions de ce module
"""
pass
195
class CoordonneesEntiteErreur(Erreur):
""" Les coordonnées dépassent la fenetre d'animation
"""
def __init__(self,position):
self.position = position
class TailleExcessiveErreur(Erreur):
""" La taille de l'entité est excessive par rapport à la fenetre
d'animation
"""
def __init__(self, largeur, hauteur):
self.largeur = largeur
self.hauteur = hauteur
CodePython/chapitre9/ExempleJeuSimpleAvecException.py
1. """
2. Exemple de jeu avec exceptions: programme principal
3. """
4. import EntiteDuJeuAvecException
5. import pygame
6. from pygame import Color
7. pygame.init() # Initialiser les modules de Pygame
8. LARGEUR_FENETRE = 400
9. HAUTEUR_FENETRE = 600
10. fenetre = pygame.display.set_mode((LARGEUR_FENETRE, HAUTEUR_FENETRE)) # Ouvrir la fenêtre
11. EntiteDuJeuAvecException.EntiteAnimeeAvecSon.set_fenetre(fenetre)
12. pygame.display.set_caption("Exemple de jeu avec module EntiteDuJeu")
13. horloge = pygame.time.Clock() # Pour contrôler la fréquence des scènes
14.
15. # Création de la liste des entités du jeu
16. liste_entite = []
17. try :
196
18.
liste_entite.append(EntiteDuJeuAvecException.BotAnime(pygame.Rect((10,100),(40,80)),[3,3],"Son2
.wav"))
19.
liste_entite.append(EntiteDuJeuAvecException.BotAnime(pygame.Rect((200,200),(50,100)),[0,2],"So
n2.wav"))
20.
liste_entite.append(EntiteDuJeuAvecException.ItiAnimeVolant(pygame.Rect((200,50),(50,100)),[3,0
],"Son3.wav",3))
21.
liste_entite.append(EntiteDuJeuAvecException.EntiteAnimeeParImages(pygame.Rect((50,100),(100,10
0)),[5,5],"Son4.wav",9,"coq"))
22. except EntiteDuJeuAvecException.CoordonneesEntiteErreur as e :
23. print("Les coordonnées de l'entité dépassent la taille de la fenetre",e)
24. except EntiteDuJeuAvecException.TailleExcessiveErreur as e :
25. print("L'entité a une taille excessive par rapport à la taille de la fenetre",e)
26. except :
27. print("Une exception a été levée lors de la création des entités du jeu")
28. raise
29. else :
30. # Boucle d'animation
31. fin = False
32. while not fin :
33. event = pygame.event.poll() # Chercher le prochain évènement à traiter
34. if event.type == pygame.QUIT: # Utilisateur a cliqué sur la fermeture de fenêtre ?
35. fin = True # Fin de la boucle du jeu
36. else :
37. if event.type == pygame.MOUSEBUTTONUP : # Utilisateur a cliqué dans la fenêtre
?
38. x=event.pos[0] # Position x de la souris
39. y=event.pos[1] # Position y de la souris
40. for une_entite in liste_entite :
41. if une_entite.touche(x,y):
42. une_entite.emettre_son()
43. liste_entite.remove(une_entite)
44.
45. for une_entite in liste_entite :
46. une_entite.prochaine_scene()
47.
48. fenetre.fill(Color('white')) # Dessiner le fond de la surface de dessin
49. for une_entite in liste_entite :
50. une_entite.dessiner()
51.
52. pygame.display.flip() # Mettre à jour la fenêtre graphique
53. horloge.tick(60) # Pour animer avec 60 images pas seconde
54. finally:
55. pygame.quit() # Terminer pygame
56.
Le try peut contenir plusieurs clauses except les unes à la suite des autres
pour attraper divers types d’exceptions comme c’est le cas du try de
l’exemple. Une clause except sans type peut terminer la liste pour attraper
n’importe quel type d’exception qui n’a pas été attrapée dans les clauses
précédentes :
except :
print("Une exception a été levée lors de la création des entités du jeu")
raise
Dans cet exemple, un message est affiché et l’exception qui a été attrapée
est relancée à nouveau par l’énoncé raise. Ceci provoquera un arrêt du
programme et l’affichage de la trace de l’exception.
Une clause else peut suivre le dernier except et elle est exécutée si aucune
exception n’est attrapée. Dans notre exemple, la boucle d’animation est
alors démarrée.
197
Enfin une clause finally peut être ajoutée. Elle sera exécutée qu’une
exception ne soit attrapée ou pas. Dans notre exemple, le module Pygame
est alors terminé, ce qui provoque la fermeture de la fenêtre dans tous les
cas :
finally :
pygame.quit() # Terminer pygame
Exercice. Créez une nouvelle classe pour une exception qui représente le
cas où la vitesse de l’entité est excessive, c’est-à-dire qu’elle dépasse 20% de
la taille de la fenêtre. Soulevez l’exception dans le constructeur de la classe
EntiteAnimeeAvecSon. Attrapez l’exception dans le programme et
affichez un message à cet effet comme pour les autres cas d’exceptions de
l’exemple.
L’exemple suivant lit deux entiers pour les diviser et vérifie que le diviseur
est non nul par un assert. Le premier paramètre du assert est la
condition à vérifier et le second, un message d’erreur. Si la condition est
fausse, une exception est levée et le message d’erreur est affiché.
CodePython/chapitre9/ExempleAssert.py
1. '''
2. Ce programme saisit deux entiers et en affiche le quotient.
Exemple de assert.
3. '''
4.
5. chaine1 = input("Entrez le dividende :")
6. chaine2 = input("Entrez le diviseur :")
7.
198
8. dividende = int(chaine1)
9. diviseur = int(chaine2)
10.
11. assert diviseur != 0, "Le diviseur ne peut être 0"
12.
13. quotient = dividende / diviseur
14. print("Le quotient est ",quotient)
15.
L’exécution avec un diviseur nul lève l’exception et affiche le message
suivant :
CodePython/chapitre9/EntiteDuJeuAssert.py
1. """
2. Module qui contient la hiérarchie des classes EntiteAnimee : exemple de assert
3. """
4. import pygame
5. from pygame import Color
6.
7. class Erreur(Exception):
8. """ Classe de base pour les exceptions de ce module
9. """
10. pass
11.
12. class CoordonneesEntiteErreur(Erreur):
13. """ Les coordonnées dépassent la fenetre d'animation
14. """
15. def __init__(self,position):
16. self.position = position
17.
18. class TailleExcessiveErreur(Erreur):
19. """ La taille de l'entité est excessive par rapport à la fenetre d'animation
20. """
21. def __init__(self, taille):
22. self.taille=taille
23.
24. class EntiteAnimeeAvecSon :
25. """ Un objet représente une entité qui est animée dans une fenêtre Pygame.
26.
27. L'entité est inscrite dans le rectangle englobant r. Il se déplace en diagonale selon
la vitesse v.
28. Elle émet un son lorsque supprimée.
29. r : pygame.Rect Le rectangle englobant
30. v : [int,int] Vitesse de déplacement selon les deux axes x et y
31. son : pygame.mixer.Sound
32. """
33.
34. @staticmethod
35. def set_fenetre(fenetre):
36. """ Fixer la variable de classe f qui représente la fenetre graphique
199
37.
38. fenetre : pygame.Surface
39. """
40. EntiteAnimeeAvecSon.f = fenetre
41.
42. def __init__(self,rectangle,vitesse,fichier_son):
43. if rectangle.x < 0 or rectangle.y < 0 or rectangle.x >
EntiteAnimeeAvecSon.f.get_width() or rectangle.y > EntiteAnimeeAvecSon.f.get_height() :
44. raise CoordonneesEntiteErreur((rectangle.x,rectangle.y))
45. elif rectangle.width > EntiteAnimeeAvecSon.f.get_width() or rectangle.height >
EntiteAnimeeAvecSon.f.get_height() :
46. raise TailleExcessiveErreur(rectangle.size)
47. else:
48. self.r = rectangle
49. self.v = vitesse
50. self.son = pygame.mixer.Sound(fichier_son)
51.
52. def prochaine_scene(self):
53. """ Déplacer selon self.v en diagonale en rebondissant sur les bords de la
fenetre"""
54. if self.r.x+self.v[0] > EntiteAnimeeAvecSon.f.get_width()-self.r.width or
self.r.x+self.v[0] < 0 :
55. self.v[0] = +self.v[0] # Inverser la direction en x (bug introduit pour
l'exemple !)
56. self.r.x = self.r.x+self.v[0]
57. if self.r.y+self.v[1] > EntiteAnimeeAvecSon.f.get_height()-self.r.height or
self.r.y+self.v[1] < 0 :
58. self.v[1] = -self.v[1] # Inverser la direction en y
59. self.r.y = self.r.y+self.v[1]
60. assert self.r.x >= 0 and self.r.x <= EntiteAnimeeAvecSon.f.get_width()-
self.r.width, "La position x dépasse du cadre du jeu"
61. assert self.r.y >= 0 and self.r.y <= EntiteAnimeeAvecSon.f.get_height()-
self.r.height, "La position y dépasse du cadre du jeu"
62.
63. def touche(self,x,y):
64. return ((x >= self.r.x) and (x <= self.r.x + self.r.width) and (y >= self.r.y) and
(y <= self.r.y + self.r.height))
65.
66. …
67.
CodePython/chapitre9/ExempleDuJeuAvecAssert.py
1. """
2. Exemple de jeu avec assert: programme principal
3. """
4. import EntiteDuJeuAssert
5. import pygame
6. from pygame import Color
7.
8. pygame.init() # Initialiser les modules de Pygame
9. LARGEUR_FENETRE = 400
10. HAUTEUR_FENETRE = 600
11. …
12.
L’exécution avec l’erreur de codage lève l’exception et affiche :
200
9.3 Développement de tests avec unittest
Plutôt que d’introduire des vérifications dans le code, l’emploi de tests
pendant le développement d’un logiciel permet de détecter des anomalies
avant de déployer celui-ci dans son environnement d’exécution sans
encombrer le code lui-même. Des logiciels permettent de faciliter le
processus de vérification par l’utilisation systématique de tests. Le module
unittest de Python permet le développement de tests unitaires.
Un test unitaire permet de tester une partie d’un logiciel, comme une
fonction, une méthode, une classe ou un module.
CodePython/chapitre9/ExempleFonctionSurface.py
1. """
2. Exemple de fonction surface qui retourne une valeur
3. """
4. def surface(largeur, hauteur):
5. return largeur*hauteur
6. s = surface(3,5)
7. print("La surface est :",s)
8.
Pour vérifier le bon fonctionnement de la fonction surface(), on peut
coder un ensemble de tests en exploitant le module unittest :
CodePython/chapitre9/TestExempleFonctionSurface.py
1. """
2. Exemple d'utilisation de unittest
3. """
4.
5. import ExempleFonctionSurface
6. import unittest
7.
8. class TestFonctionSurface(unittest.TestCase):
9.
10. def test_1(self):
11. self.assertEqual(ExempleFonctionSurface.surface(3,5),
15)
12.
13. def test_2(self):
201
14. self.assertEqual(ExempleFonctionSurface.surface(2.5,4),
10)
15.
16. def test_negatif(self):
17. with self.assertRaises(Exception):
18. ExempleFonctionSurface.surface(-3,4)
19.
20. unittest.main()
21.
Résultat :
======================================================================
FAIL: test_negatif (__main__.TestFonctionSurface)
----------------------------------------------------------------------
Traceback (most recent call last):
File "C:\Users\Robert\CodePython\TestExempleFnctionSurface.py", line 19, in
test_negatif
ExempleFonctionSurface.surface(-3,4)
AssertionError: Exception not raised
----------------------------------------------------------------------
Ran 3 tests in 0.002s
FAILED (failures=1)
self.assertEqual(ExempleFonctionSurface.surface(3,5), 15)
Dans la ligne suivante, la fonction vérifie si une exception est levée par
l’appel à surface(-3,4), ce qui devrait être le cas pour une valeur
négative.
with self.assertRaises(Exception):
ExempleFonctionSurface.surface(-3,4)
202
exécutés avec succès mais le troisième fait ressortir un problème parce que
la fonction ne lève pas d’exception avec un argument négatif.
Solution minimaliste :
CodePython/chapitre9/ExempleFonctionSurfaceExceptionNeg.py
1. """
2. Exemple de fonction surface qui retourne une valeur
3. Exception si un argument est négatif
4. """
5. def surface(largeur, hauteur):
6. if largeur < 0 or hauteur < 0 :
7. raise(Exception)
8. return largeur*hauteur
9.
En retestant le code modifié, le résultat suivant devrait montrer son bon
fonctionnement :
----------------------------------------------------------------------
Ran 3 tests in 0.002s
OK
203
10 Formatage et analyse de
chaînes de caractères
Ce chapitre approfondie la manipulation des chaînes de caractères. La
première section traite du formatage des chaînes et la seconde de l’analyse
avec les expressions régulières.
204
Il est aussi possible de faire référence aux arguments de format() en
désignant leur position par un indice. Ceci permet de changer l’ordre des
valeurs à substituer.
Il est aussi possible de faire référence aux noms des paramètres dans le cas
de paramètres nommés.
f-string
Depuis la version 3.6, Python a introduit le formatage avec les f-strings qui
offre une syntaxe simplifiée par rapport à format() et qui est maintenant la
manière suggérée. Un f-string débute par f ou F et contient des champs de
remplacement comme pour format(). Par opposition à format(), une
valeur est exprimée directement par une expression à évaluer entre les accolades:
>>> f'La somme de {2} et {3} est {2+3}'
'La somme de 2 et 3 est 5'
>>> f"{'Gingras':>15}|{'Guy':>15}|{'514-333-4444':12}|"
' Gingras| Guy|514-333-4444|'
205
et L une lettre majuscule. Le module re17 permet d’effectuer des
appariements de chaînes sophistiqués basés sur la notion d’expression
régulière (regular expression) aussi appelée expression rationnelle.
>>> import re
>>> code_postal_re = re.compile(r'[A-Z][0-9][A-Z] [0-9][A-Z][0-9]')
>>> code_postal_re.match('J2S 4H8')
<_sre.SRE_Match object; span=(0, 7), match='J2S 4H8'>
>>> code_postal_re.match('JDS 4H8')
r'[A-Z][0-9][A-Z] [0-9][A-Z][0-9]'
>>> re.match(r'd','d')
<_sre.SRE_Match object; span=(0, 1), match='d'>
>>> re.match(r'd','dtaj')
<_sre.SRE_Match object; span=(0, 1), match='d'>
>>> re.match(r'd','atdk')
Dans le dernier exemple, l’appariement n’a pas fonctionné parce que le avec
match() effectue celui-ci à partir du début de la chaîne. Le search()
permet la recherche à n’importe quelle position.
>>> re.search(r'd','atdk')
<_sre.SRE_Match object; span=(2, 3), match='d'>
>>> re.match(r'z','zorro')
<re.Match object; span=(0, 1), match='z'>
>>> re.match(r'z','torro')
>>>
>>> re.search(r'z','raz')
<re.Match object; span=(2, 3), match='z'>
Suite de motifs : m1 m2 … mn
Un motif peut être composé d’une suite de motifs m1 m2 … mn. Ainsi une
suite de caractères est un motif.
>>> re.match(r'babfc','babfc')
<_sre.SRE_Match object; span=(0, 5), match='babfc'>
Caractère spécial .
>>> re.match(r'a..c.d','agsced')
<_sre.SRE_Match object; span=(0, 6), match='agsced'>
>>> re.match(r'a..c.d','afced')
>>>
207
Exercice. Vérifiez qu’une chaîne débute par x suivi de deux caractères
quelconques suivi de y.
>>> re.match(r'x..y','xefyab')
<re.Match object; span=(0, 4), match='xefy'>
>>> re.match(r'x..y','xeyab')
>>>
>>> re.match(r':$',':')
<re.Match object; span=(0, 1), match=':'>
>>> re.match(r':$',':a')
>>>
>>> re.match(r'123$','123')
<re.Match object; span=(0, 3), match='123'>
>>> re.match(r'123$','1234')
>>>
>>> re.search(r'123$','abc12123')
<re.Match object; span=(5, 8), match='123'>
>>> re.search(r'123$','abc121234')
>>>
Ensemble de caractères : []
>>> re.match(r'[A-Z]','Abcd')
<re.Match object; span=(0, 1), match='A'>
208
Répétition de motif : m*
>>> re.match(r'a*','aaaa')
<_sre.SRE_Match object; span=(0, 4), match='aaaa'>
>>> re.match(r'a*','')
<_sre.SRE_Match object; span=(0, 0), match=''>
>>> re.match(r'[0-9]*','123xyz')
<re.Match object; span=(0, 3), match='123'>
>>> re.match(r'a+','aaaa')
<_sre.SRE_Match object; span=(0, 4), match='aaaa'>
>>> re.match(r'a+','')
>>> re.match(r'fc+','fccc')
<_sre.SRE_Match object; span=(0, 4), match='fccc'>
>>> re.match(r'fc+','fcfc')
<_sre.SRE_Match object; span=(0, 2), match='fc'>
Exercice. Vérifiez qu’une chaîne débute par une suite d’une majuscule ou
plus.
>>> re.match(r'[A-Z]+','Allo')
<re.Match object; span=(0, 1), match='A'>
>>> re.match(r'[A-Z]+','ABCDabcd')
<re.Match object; span=(0, 4), match='ABCD'>
0 ou 1 fois : m?
>>> re.match(r'[A-Z]?','Allo')
<re.Match object; span=(0, 1), match='A'>
>>> re.match(r'[A-Z]?','allo')
<re.Match object; span=(0, 0), match=''>
>>> re.match(r'[A-Z]?','ABCDabcd')
<re.Match object; span=(0, 1), match='A'>
209
Appariement non vorace : m*?, m+? et m??
L’appariement avec *,+ et ? est vorace en ce sens qu’il apparie le plus grand
nombre de caractères possibles. Pour limiter l’appariement au nombre
minimal de caractères, on emploie m*?, m+? et m??.
>>> re.match(r'a.+b','addebsbsb')
<_sre.SRE_Match object; span=(0, 9), match='addebsbsb'>
>>> re.match(r'a.+?b','addebesbsb')
<_sre.SRE_Match object; span=(0, 5), match='added'>
Dans le premier cas, toute la chaîne est appariée, alors que dans le
deuxième, le nombre minimal de caractères qui correspond au motif est
apparié.
>>> re.match(r'a{4}','aaaa')
<_sre.SRE_Match object; span=(0, 4), match='aaaa'>
>>> re.match(r'a{4}','aaa')
>>>
Exercice. Vérifiez qu’une chaîne débute par une séquence de trois lettres.
>>> re.match(r'[A-Z]{3}','ABC123')
<re.Match object; span=(0, 3), match='ABC'>
>>> re.match(r'[A-Z]{3}','AB123')
>>>
>>> re.match(r'a{2,4}','aa')
<_sre.SRE_Match object; span=(0, 2), match='aa'>
>>> re.match(r'a{2,4}','aaaa')
<_sre.SRE_Match object; span=(0, 4), match='aaaa'>
>>> re.match(r'a{2,4}','aaaaa')
<_sre.SRE_Match object; span=(0, 4), match='aaaa'>
>>> re.match(r'a\*\*\?b','a**?b')
210
<_sre.SRE_Match object; span=(0, 5), match='a**?b'>
>>> re.search(r'\.','abc.com')
<re.Match object; span=(3, 4), match='.'>
>>> re.search(r'\.[a-zA-Z]{2,4}$','abc.com')
<re.Match object; span=(3, 7), match='.com'>
Groupage : (m)
>>> re.match(r'(fc)+','fcfc')
<_sre.SRE_Match object; span=(0, 4), match='fcfc'>
>>> re.match(r'k(ed)+b','kedededb')
<_sre.SRE_Match object; span=(0, 8), match='kedededb'>
>>> re.match(r'([0-9][A-Z]){3}$','1A2C6B')
<re.Match object; span=(0, 6), match='1A2C6B'>
>>> re.match(r'([0-9][A-Z]){3}$','1A2C6B4S')
>>> re.match(r'([0-9][A-Z]){3}$','1AEC6B')
>>> re.match(r'([0-3][0-9])-([0-1][0-9])-([0-9]{4})','15-10-2015').group()
'15-10-2015'
>>> re.match(r'([0-3][0-9])-([0-1][0-9])-([0-9]{4})','15-10-2015').group(2)
'10'
211
>>>re.match(r'([0-3][0-9])-([0-1][0-9])-([0-9]{4})','15-10-
2015').group(1,2,3)
('15', '10', '2015')
>>>re.match(r'([A-Z]{4})([0-3][0-9])([0-1][0-9])([0-9]{2})([0-
9]{2})','BEDA10069001').group(1,2,3,4,5)
('BEDA', '10', '06', '90', '01')
Alternatives : m1|m2
>>> re.match(r'[A-Z][0-9][A-Z]|[0-9][0-9][0-9]','B6R')
<re.Match object; span=(0, 3), match='B6R'>
>>> re.match(r'[A-Z][0-9][A-Z]|[0-9][0-9][0-9]','234')
<re.Match object; span=(0, 3), match='234'>
>>> re.match(r'[A-Z][0-9][A-Z]|[0-9][0-9][0-9]','2A4')
212
11 Traitement de fichiers
Pour conserver des données de manière persistante au-delà de l’exécution
des programmes, il faut employer les mémoires secondaires. Les langages
de programmation fournissent des interfaces programmatiques pour la
manipulation des fichiers en mémoire secondaire. Ces interfaces réalisent
des abstractions simples qui isolent le programme de plusieurs des détails
de bas niveau des mécanismes des mémoires secondaires. La simplicité de
ces interfaces est pertinente pour le développement d’applications
nécessitant des services de base pour la persistance des données.
Cependant, ces interfaces sont souvent trop limitées pour des applications
complexes nécessitant des services plus sophistiqués. Les systèmes de
gestion de bases de données sont prévus à cet effet. Ce chapitre présente
les concepts de base pour le traitement de fichier en Python.
CodePython/chapitre11/ExempleLectureFichier.py
1. """
2. Exemple de lecture d'un fichier en mode texte
3. """
4. fichier = open("Fichier1.txt", "rt")
5. for ligne in fichier:
6. print(ligne,end="")
7. fichier.close()
8.
La fonction open() ouvre le fichier et retourne un objet itérable qui
représente le fichier ouvert.
213
Le deuxième paramètre représente le mode d’ouverture. Le mode "rt"
désigne le mode lecture pour un fichier texte. C’est le mode de défaut. Les
options possibles sont les suivantes :
Option Description
"r" read : en lecture, exception si le fichier n’existe pas
append : en écriture en ajout à la fin du fichier, crée le fichier si
"a" n’existe pas
write : en écriture, écrase le contenu existant, crée le fichier si
"w" n’existe pas
"x" create : crée le fichier, exception si le fichier existe
"t" text : mode texte
"b" binary : mode binaire
"+" en lecture et en écriture
Un fichier texte permet de lire et d’écrire des objets de type str sans avoir
à se préoccuper de l’encodage employé par le système sous-jacent. Le for
permet d’itérer ligne par ligne jusqu’à la fin du fichier :
Par défaut, le contenu du fichier est décodé selon le mode d’encodage des
caractères du fichier. Ce mode d’encodage est déterminé par des
conventions qui peuvent varier d’un système à l’autre. Il est possible de le
modifier par le paramètre encoding. Chacune des lignes est délimitée par
une <fin de ligne> selon le mode d’encodage du système d’exploitation.
Le str retourné contient une représentation de la fin de ligne par '\n'
indépendamment de la représentation réelle employée par le système.
CodePython/chapitre11/ExempleWith.py
1. """
2. Exemple du with
3. """
4. with open("Fichier1.txt", "r") as fichier:
5. for ligne in fichier:
6. print(ligne,end="")
La méthode read() retourne un objet str qui contient tout le contenu du
fichier jusqu’à la marque de fin de fichier.
214
1. """
2. Exemple du read()
3. """
4. fichier = open("Fichier1.txt", "rt")
5. contenu_fichier = fichier.read()
6. print("Taille fichier:", len(contenu_fichier))
7. print("Contenu:")
8. print(contenu_fichier)
9. fichier.close()
Exemple de fichier texte ASCII
CodePython/Fichier1.txt
a b c \r \n 1 2 \r \n
Sous Windows, la fin de ligne est représentée par la séquence des caractères
spéciaux ASCII, retour de chariot (\r) et saut de ligne (\n)18. Un code
supplémentaire, absent dans l’exemple, est ajouté à la fin de la séquence
pour indiquer la fin du fichier (0X0A sur Windows). Le résultat de
l’exécution du programme serait le suivant avec notre exemple :
Taille fichier: 7
Contenu:
abc
Les deux premiers octets représentent un indicateur d’ordre des octets (Byte
Order Mark) du code UTF-16 avec un ordre gros-boutiste (big endian), ce qui
signifie que le premier octet des deux octets employés pour chacun des
caractères (16 bits) est l’octet d’ordre supérieur. Le tableau suivant montre
quelques exemples d’indicateur d’ordre des octets.
Codage BOM
hexadécimal
UTF-819 EF BB BF
UTF-16 gros boutiste (Big Endian) FE FF
UTF-16 petit boutiste (Little Endian) FF FE
UTF-32 gros boutiste (Big Endian) 00 00 FE FF
UTF-32 petit boutiste (Little Endian) FF FE 00 00
ASCII absent
En exécutant le programme de lecture en mode texte avec le fichier encodé
sous UTF-16, on obtiendrait exactement le même résultat. Ainsi l’interface
programmatique isole le programme des détails du mécanisme d’encodage
employé par le système d’exploitation sous-jacent.
19 La norme Unicode ne recommande pas d’inclure un BOM dans le cas des chaînes UTF-
8.
216
possible de lire le contenu d’un fichier sans décodage, en mode dit binaire,
tel qu’illustré par l’exemple suivant, qui correspond au mode "rb".
CodePython/chapitre11/ExempleReadBinaire.py
1. """
2. Exemple du read() binaire
3. """
4. fichier = open("Fichier1.txt", "rb")
5. contenu_fichier = fichier.read()
6. print("Taille fichier:", len(contenu_fichier))
7. print("Contenu en bytes:")
8. print(contenu_fichier)
9. print("Contenu en binaire:")
10. for un_octet in contenu_fichier :
11. print("{:0=8b}".format(un_octet))
12. fichier.close()
13.
Le contenu_fichier retourné par le read() est un objet de type bytes
qui est une séquence d’octets. Le print(contenu_fichier) affiche
l’objet bytes avec un b suivie des codes ASCII des octets. Le résultat de
l’exécution avec l’exemple de la section précédente codé en ASCII est :
Taille fichier: 9
Contenu type bytes:
b'abc\r\n12\r\n'
Contenu en binaire:
01100001
01100010
01100011
00001101
00001010
00110001
00110010
00001101
00001010
Taille fichier: 20
Contenu type bytes:
b'\xfe\xff\x00a\x00b\x00c\x00\r\x00\n\x001\x002\x00\r\x00\n'
Contenu en binaire:
11111110
11111111
00000000
01100001
00000000
01100010
00000000
01100011
00000000
217
00001101
00000000
00001010
00000000
00110001
00000000
00110010
00000000
00001101
00000000
00001010
CodePython/chapitre11/ExempleWriteTexte.py
1. """
2. Exemple du write() fichier texte
3. """
4. fichier = open("FichierWrite.txt", "w")
5. fichier.write("abc\n12\n")
6. fichier.close()
L’écriture dans un fichier en mode texte a l’avantage de permettre à un
humain d’en interpréter le contenu sans difficulté. Cependant c’est un mode
qui est peu performant d’un point de vue de la consommation d’espace.
CodePython/chapitre11/ExempleWriteText1629696561.py
1. """
2. Exemple du write() entier en texte
3. """
4. fichier = open("Fichier1629696561.txt", "w")
218
5. fichier.write("1629696561")
6. fichier.close()
La sortie suivante serait produite en lisant ce fichier avec le programme de
lecture en binaire :
Taille fichier: 10
Contenu type bytes:
b'1629696561'
Contenu en binaire:
00110001
00110110
00110010
00111001
00110110
00111001
00110110
00110101
00110110
00110001
Il faudrait donc 10 octets pour stocker cette donnée, soit 1 octet par chiffre.
On peut économiser beaucoup d’espace en stockant la donnée en mode
binaire tel qu’illustré par l’exemple suivant. Le mode "wb" permet d’écrire
en mode binaire.
CodePython/chapitre11/ExempleWriteBin1629696561.py
1. """
2. Exemple du write() entier en binaire
3. """
4. fichier = open("Fichier1629696561.dat", "wb")
5. i = 1629696561
6. fichier.write(i.to_bytes(4, byteorder='big', signed=True))
7. fichier.close()
Le contenu du fichier a une taille de 4 octets plutôt que 10 comme c’était
le cas dans la version texte. Ceci permet de représenter n’importe quel entier
de 32 bits.
Taille fichier: 4
Contenu type bytes:
b'a#21'
Contenu en binaire:
01100001
00100011
00110010
00110001
219
incompréhensible parce que le programme Notepad interprète le contenu du
fichier comme une suite de caractères ASCII et non un entier de 4 octets :
1. """
2. Exemple du read() entier en binaire
3. """
4. fichier = open("Fichier1629696561.dat", "rb")
5. octets = fichier.read(4)
6. i = int.from_bytes(octets, byteorder='big', signed=True)
7. print(i)
8. fichier.close()
Le stockage en binaire nécessite de bien définir la disposition des données.
10,Cèdre en boule,10.99
20,Sapin,12.99
40,Epinette bleue,25.99
50,Chêne,22.99
220
60,Erable argenté,15.99
70,Herbe à puce,10.99
80,Poirier,26.99
81,Catalpa,25.99
90,Pommier,25.99
95,Génévrier,15.99
Le module csv permet de lire un fichier csv sous forme d’un itérateur et
de retourner chacune des lignes comme une liste de chaînes de caractères
(str), une par colonne :
CodePython/chapitre11/ExempleReadCSV.py
1. """
2. Lecture du fichier Plants.csv en format csv
3. """
4. import csv
5. with open('Plants.csv', mode='r') as fichier_csv:
6. lecteur_csv = csv.reader(fichier_csv)
7. for ligne in lecteur_csv:
8. print(ligne)
Le résultat affiché est :
221
struct.pack() permet de convertir un ensemble de données de types
divers en une séquence d’octets en spécifiant un format de conversion qui
détermine la manière de convertir chacune des données. L’exemple suivant
convertit un entier, une chaîne de caractères et un réel en une séquence
d’octets et inverse la conversion avec struct.unpack().
Le masque i5sf signifie que la première donnée est un entier (format i),
suivie d’une chaîne de 5 octets (format 5s), suivie d’un nombre en point
flottant (code f). Chacun des formats détermine comment la conversion
est opérée. Le format i spécifie que l’entier est converti en une suite de 4
octets, le format 5s spécifie que la chaîne d’octets est convertie en une suite
de 5 octets et le format f, que le nombre en point flottant est converti en
une suite de 4 octets. Donc, au total, on obtient une suite de 13 octets. Pour
plus de détails au sujet des formats et de leurs significations, voir la
documentation de struct.
CodePython/chapitre11/ExempleWriteFichierPlantsBinaire.py
1. """
2. Lecture du fichier Plants.csv en format csv et stockage en un
fichier binaire
3. Conversion des données avec struct
4. """
5.
6. import csv
7. import struct
8.
9. with open('Plants.csv', mode='r') as fichier_csv:
10. with open('Plants.dat',mode = 'wb') as fichier_binaire:
11. lecteur_csv = csv.reader(fichier_csv)
12. for ligne in lecteur_csv:
13.
fichier_binaire.write(struct.pack('i20sf',int(ligne[0]),str.encode(
ligne[1]),float(ligne[2])))
14.
L’exemple suivant parcoure les enregistrements du fichier binaire
Plants.dat un par un et en affiche le contenu.
222
CodePython/chapitre11/ExempleReadFichierPlantsBinaire.py
1. """
2. Lecture du fichier binaire Plants.dat encodé avec struct
3. """
4.
5. import csv
6. import struct
7.
8. with open('Plants.dat', mode='rb') as fichier_binaire:
9. while True:
10. r=fichier_binaire.read(28)
11. if not r:
12. break
13. print(struct.unpack('i20sf',r))
14.
Dans certaines applications, il peut être utile de lire ou d’écrire un
enregistrement particulier sans être obligé de parcourir tout le fichier. Ceci
devient important au fur et à mesure que le taille du fichier augmente. À cet
effet, la méthode seek() permet de sauter à un octet particulier dans un
fichier. Comme tous les enregistrements occupe le même espace, il est
possible de calculer la position d’un enregistrement. Par exemple, pour se
positionner au 3ième enregistrement, il faut faire un seek(56) parce que
chacun des enregistrements occupe 28 octets. L’exemple suivant effectue
une lecture de cet enregistrement.
CodePython/chapitre11/ExempleSeekFichierBinaire.py
1. """
2. Lecture en accès direct avec seek() dans le fichier binaire
Plants.dat encodé avec struct
3. """
4. import csv
5. import struct
6. with open('Plants.dat', mode='rb') as fichier_binaire:
7. fichier_binaire.seek(56)
8. enregistrement=fichier_binaire.read(28)
9. print(enregistrement)
10. print(struct.unpack('i20sf',enregistrement))
11.
Il est aussi possible d’écrire un enregistrement pour le modifier. S’il faut
permettre de gérer diverses modifications, telles que l’insertion, la
suppression, la modification de données, de manière sécuritaire et partagée,
ceci peut devenir complexe à programmer. Pour des applications qui
exigent une gestion de données volumineuses de manière performante,
flexible et sécuritaire, il est avantageux de recourir aux services d’un système
223
de gestion de bases de données. Ces systèmes emploient habituellement
une représentation binaire afin de minimiser l’espace consommé.
CodePython/chapitre11/ExemplePickle.py
1. """
2. Lecture du fichier Plants.csv en format csv, création d'une liste d'objets de la classe
Plant
3. Ecriture de l'objet liste_plants dans Plants.pickle avec le module pickle
4. """
5.
6. import csv
7. import struct
8. import pickle
9.
10. # Classe Plant
11. class Plant:
12. def __init__(self,numero,description,prix):
13. self.numero = numero
14. self.description = description
15. self.prix = prix
16. def __str__(self):
17. return '|{:>10}|{:>20}|{:8.2f}|'.format(self.numero,self.description,self.prix)
18.
19. liste_plants = []
20.
21. with open('Plants.csv', mode='r') as fichier_csv:
22. lecteur_csv = csv.reader(fichier_csv)
23. for ligne in lecteur_csv:
24. un_plant = Plant(int(ligne[0]),ligne[1],float(ligne[2]))
25. print(un_plant)
26. liste_plants.append(un_plant)
27.
28. with open('Plants.pickle', mode = 'wb') as fichier_binaire:
29. pickle.dump(liste_plants, fichier_binaire, pickle.HIGHEST_PROTOCOL)
224
L’exemple suivant lit le fichier Plants.pickle et convertit son contenu
sous forme de la liste des objets de la classe Plant.
CodePython/chapitre11/ExemplePickleLoad.py
1. """
2. Lecture du fichier Plants.pickle avec pickle.load()
3. """
4.
5. import csv
6. import struct
7. import pickle
8.
9. # Classe Plant
10. class Plant:
11. def __init__(self,numero,description,prix):
12. self.numero = numero
13. self.description = description
14. self.prix = prix
15. def __str__(self):
16. return '|{:>10}|{:>20}|{:8.2f}|'.format(self.numero,self.description,self.prix)
17.
18. with open('Plants.pickle', mode = 'rb') as fichier_binaire:
19. liste_plants = pickle.load(fichier_binaire)
20.
21. for un_plant in liste_plants:
22. print(un_plant)
CodePython/chapitre11/ExempleWriteJson.py
1. """
2. Lecture du fichier Plants.csv en format csv, création d'une liste d'objets de la classe
Plant
3. Ecriture des données de liste_plants dans Plants.json avec json.dump()
4. """
5.
6. import csv
7. import struct
8. import json
9.
10. # Classe Plant
11. class Plant:
12. def __init__(self,numero,description,prix):
13. self.numero = numero
14. self.description = description
15. self.prix = prix
16. def __str__(self):
17. return '|{:>10}|{:>20}|{:8.2f}|'.format(self.numero,self.description,self.prix)
18.
19. liste_plants = []
20.
21. with open('Plants.csv', mode='r') as fichier_csv:
225
22. lecteur_csv = csv.reader(fichier_csv)
23. for ligne in lecteur_csv:
24. un_plant = Plant(ligne[0],ligne[1],ligne[2])
25. # print(un_plant)
26. liste_plants.append(ligne)
27.
28. with open('Plants.json', mode = 'w') as fichier_json:
29. json.dump(liste_plants, fichier_json)
30.
CodePython/chapitre11/ExempleReadJson.py
1. """
2. Lecture du fichier Plants.json avec json.load()
3. """
4.
5. import json
6.
7. # Classe Plant
8. class Plant:
9. def __init__(self,numero,description,prix):
10. self.numero = numero
11. self.description = description
12. self.prix = prix
13. def __str__(self):
14. return '|{:>10}|{:>20}|{:8.2f}|'.format(self.numero,self.description,self.prix)
15.
16. with open('Plants.json', mode = 'r') as fichier_json:
17. liste_plants = json.load(fichier_json)
18.
19. for un_plant in liste_plants:
20. print(un_plant)
21.
CodePython/chapitre11/ExempleOsRepertoire.py
1. """
2. Exemples de base de manipulation de répertoire de fichier avec le module os
3. """
4.
5. import os
6.
7. repertoire_de_travail = os.getcwd() # Chercher le répertoire de travail
8. print("Répertoire de travail :",repertoire_de_travail)
9.
10. # Création d'un chemin par concaténation avec os.path.join
11. sous_repertoire = os.path.join(repertoire_de_travail, "repertoire_test")
12.
13. if not os.path.exists(sous_repertoire):
14. print("Création du sous-répertoire 'repertoire_test' dans le répertoire de travail")
15. os.mkdir(sous_repertoire)
16.
17. print("Changement du répertoire de travail")
18. os.chdir(sous_repertoire) # Changer le répertoire de travail
19.
20. nouveau_repertoire_de_travail = os.getcwd() # Chercher le nouveau répertoire de travail
21. print("Nouveau répertoire de travail :",nouveau_repertoire_de_travail)
22.
226
23. print("Création du fichier 'FichierTest.txt' dans le répertoire de travail")
24. fichier = open("FichierTest.txt", "w")
25. fichier.write("abc\n12\n")
26. fichier.close()
27.
28. print("Contenu du répertoire de travail:")
29. print(os.listdir(nouveau_repertoire_de_travail))
Résultat :
if not os.path.exists(sous_repertoire):
print("Création du sous-répertoire 'repertoire_test' dans le répertoire
de travail")
os.mkdir(sous_repertoire)
print(os.listdir(nouveau_repertoire_de_travail))
CodePython/chapitre11/ExempleCheminWindows.py
1. """
2. Exemple de chemin selon la syntaxe Windows
3. """
227
4.
5. import os
6.
7. os.chdir("C:\\Users\\Robert\\CodePython\\data") # Changer le répertoire de travail
8.
9. nouveau_repertoire_de_travail = os.getcwd() # Chercher le nouveau répertoire de travail
10. print("Nouveau répertoire de travail :",nouveau_repertoire_de_travail)
11.
12. print("Contenu du répertoire de travail:")
13. print(os.listdir(nouveau_repertoire_de_travail))
14.
Résultat :
CodePython/chapitre11/ExempleCheminUnix.py
1. """
2. Exemple de chemin selon la syntaxe Unix
3. """
4.
5. import os
6.
7. os.chdir("/Users/Robert/CodePython/data") # Changer le répertoire de travail
8.
9. nouveau_repertoire_de_travail = os.getcwd() # Chercher le nouveau répertoire de travail
10. print("Nouveau répertoire de travail :",nouveau_repertoire_de_travail)
11.
12. print("Contenu du répertoire de travail:")
13. print(os.listdir(nouveau_repertoire_de_travail))
CodePython/chapitre11/ExemplePathlib.py
1. """
2. Exemple de chemin avec Pathlib compatible Windows ou Unix
3. """
4.
5. from pathlib import Path
6. import os
7.
8. repertoire = Path("/Users/Robert/CodePython/data")
9.
10. os.chdir(repertoire) # Changer le répertoire de travail
11.
12. nouveau_repertoire_de_travail = os.getcwd() # Chercher le nouveau répertoire de travail
13. print("Nouveau répertoire de travail :",nouveau_repertoire_de_travail)
14.
15. print("Contenu du répertoire de travail:")
16. print(os.listdir(nouveau_repertoire_de_travail))
228
Le chemin est avec un objet de la classe Path en employant la syntaxe avec
/. La conversion dans la syntaxe appropriée est effectuée automatiquement,
ce qui produit du code portable peu importe le système.
229
12 Structures de données,
algorithmes et complexité
Ce chapitre introduit les notions de base des structures de données, des
algorithmes et de leur complexité. Ces sujets sont très vastes et des ouvrages
entiers y sont consacrés. Cette section vise à introduire les concepts de base
et les enjeux sous-jacents. Précédemment, différents types ont été présentés
pour la manipulation des collections (str, list, tuple, set, dict).
D’autres types prédéfinis sont aussi disponibles à cet effet. Chacun de ces
types possède une structure de données particulière adaptée aux traitements
envisagés. Ce chapitre prend le problème de recherche dans une collection
comme exemple pour illustrer les concepts.
Un algorithme est une recette pour résoudre un problème. Cette notion est
surtout employée dans le cas de la recherche d’une solution à un problème
non trivial. Il peut y avoir plusieurs solutions au problème, plusieurs
algorithmes. Par exemple, pour rechercher un élément dans une liste, il peut
y avoir plusieurs algorithmes possibles. L’algorithmique désigne l’étude des
algorithmes et la recherche des meilleurs algorithmes. Les algorithmes sont
intimement liés aux structures de données visées. Le meilleur algorithme
pour la recherche dans une liste est différent du meilleur algorithme de
recherche dans une liste triée.
230
CodePython/chapitre12/SimulationComplexiteRechercheLineaire.py
Résultat:
Chacun des éléments de la liste est vérifié un par un avec un for jusqu’à ce
que l’élément soit trouvé ou jusqu’à ce que la liste soit épuisée. Pour
analyser la performance d’un algorithme, on peut mesurer le temps
nécessaire dans une implémentation.
231
Quoique ce genre d’analyse soit utile, il dépend de l’implémentation
particulière choisie. Il est intéressant d’analyser un algorithme en faisant
abstraction de l’implémentation. On distingue souvent le meilleur cas, le
cas moyen et le pire cas. Pour chacune des possibilités, le nombre
d’opérations nécessaires est compté en fonction de la taille du problème,
ici n. Il peut être compliqué de chercher à mesurer le temps de chacune des
opérations mais il y a souvent un sous-ensemble plus critique qui peut être
étudié. Dans le cas de la recherche linéaire, le nombre de comparaisons
comp(n) avec l’élément recherché est une mesure fondamentale. Supposons
que l’élément n’est pas présent. Le nombre maximum de comparaisons en
meilleur cas, en moyenne et en pire cas est n car il faut vérifier tous les
éléments de la liste. Si l’élément est présent, le meilleur cas est 1, le pire cas
est encore n, la moyenne est n/2. Le meilleur cas est rare et il se produit
lorsque l’élément est en première position.
|f(n)| ≤ c |g(n)|
232
permet d’éviter le test de dépassement de l’indice par rapport à la taille de
la liste.
CodePython/chapitre12/ExempleRechercheDansListe.py
234
12.3 Formulation récursive de la recherche
binaire
Une fonction est dite récursive lorsqu’elle s’appelle elle-même. Plusieurs
fonctions peuvent naturellement être formulée récursivement en
décomposant un problème en un sous-problème de même nature mais de
taille inférieure dans ses paramètres. La recherche binaire peut être
formulée de manière récursive de manière très naturelle. Dans la
formulation précédente de la recherche binaire, la recherche dans une liste
d’éléments vérifie dans quelle sous-liste devrait se trouver l’élément par
comparaison avec l’élément milieu et la recherche se poursuit dans la sous-
liste. Cette recherche peut être exprimée récursivement en ajoutant deux
paramètres à la fonction de recherche, la borne inférieure et la borne
supérieure de la sous-liste à rechercher. La recherche dans la sous-liste est
ainsi exprimée par la même fonction en spécifiant les nouvelles valeurs des
bornes inférieures et supérieures.
236
13 Développement d’applications
Web
Une application Web est une application à laquelle on accède généralement
par l’entremise d’un navigateur Web (par ex., Safari, Chrome, Firefox, etc.).
L’application tourne généralement sur un serveur Web. Il est commun
d’héberger les services Web chez des fournisseurs d’infonuagiques tels que
Amazon (AWS) ou Microsoft (Azure). Les entreprises disposent parfois de
leurs propres serveurs Web, parfois hébergés au sein de salles de serveurs.
Nous sommes tous familiers avec les applications Web, celles-ci étant
omniprésentes : par ex., les sites de commerce électronique (Amazon), les
sites Web gouvernementaux, etc. comportent une ou plusieurs applications
Web.
Il est aussi possible de produire des applications Web avec lesquelles les
êtres humains n’interagissent pas directement : on parle alors souvent de
services Web. Par exemple, une application mobile sur votre téléphone
pourrait communiquer avec une application Web sans que vous en preniez
pleinement conscience. C’est le cas notamment de l’application mobile
Facebook. Les jeux vidéo en ligne communique aussi parfois avec des
applications Web dédiées.
Requête HTTP
Navigateur
Serveur Web
Web
Réponse HTTP
13.1.1 HTTP/HTTPS
Le protocole HTTP utilise un protocole de communication réseau de plus
bas niveau pour transmettre les informations par le réseau, tel que TCP
(Transmission Control Protocol) et UDP (User Datagram Protocol). TCP est un
protocole de transport orienté connexion, ce qui signifie qu’il établit une
connexion entre deux hôtes avant de transférer des données. UDP, en
revanche, est un protocole de transport sans connexion, ce qui signifie qu’il
ne nécessite pas d’établir une connexion avant de transférer des données.
Certains protocoles de la couche application peuvent utiliser UDP comme
protocole de transport sous-jacent. Par exemple, le protocole DNS
(Domain Name System) utilise UDP pour transférer des requêtes et des
réponses entre les clients DNS et les serveurs DNS.
TCP et UDP sont deux protocoles de communication réseau qui ont des
avantages et des inconvénients. Avec TCP, une connexion est initiée et les
données sont échangées en séquence. Le protocole UDP permet la
transmission des données dans le désordre avec perte des données
occasionnellement. TCP est parfois plus lent et utilise plus de bande
passante que UDP. Plus le réseau est encombré et peu fiable, plus TCP
perd en vitesse.
Le protocole visé est spécifié par l’URL. Un URL (Uniform Resource Locator)
est une chaîne de caractères qui identifie de manière unique une ressource
sur Internet. Il est utilisé pour localiser des pages Web, des images, des
vidéos, des fichiers, etc. sur le Web. Un URL est composé de plusieurs
238
parties, notamment le protocole, le nom d’hôte, le chemin et la chaîne de
requête.
https://www.google.com/
Les requêtes HTTP les plus simples sont de type GET. Une requête GET
comporte un hyperlien, et la réponse peut être, par exemple, un document
HTML. Bien que ça ne soit pas obligatoire, le navigateur peut supposer que
la même requête GET devrait toujours donner la même réponse : les
navigateurs peuvent utiliser ce fait pour mettre le résultat de la requête en
mémoire, et éviter de répéter la requête. L’autre type de requête
communément utilisé est la requête POST qui est normalement suivi par la
transmission d’une information détaillée. Il existe plusieurs autres types de
requêtes HTTP : PUT, DELETE, TRACE, CONNECT, etc.
Les ports TCP et UDP sont utilisés pour la communication réseau. Les
ports numérotés de 0 à 1023 sont réservés aux services système. Les
services système qui utilisent ces ports incluent:
240
Les types MIME sont généralement représentés sous forme de chaînes de
caractères qui contiennent deux parties: le type de média et le sous-type.
Par exemple, le type MIME pour les fichiers HTML est text/html,
où text est le type de média et html est le sous-type.
Les types MIME sont souvent utilisés dans les en-têtes HTTP pour
spécifier le type de contenu d’une réponse HTTP. Par exemple, un serveur
Web peut renvoyer une réponse avec l’en-tête Content-Type:
text/html pour indiquer que la réponse contient du contenu HTML. Il
existe des centaines de types MIME différents, chacun étant associé à un
type de fichier spécifique.
13.1.2 XML
Le XML constitue une technologie importante dans le contexte de
l’échange des données en ligne. Le XML est un « métalangage » permettant
d'échanger de l'information. Nous disons que c'est un métalangage parce
qu'il est une façon pratique de créer de nouveaux langages pour échanger des
informations, mais qu'il ne constitue pas un langage en soi. On dit que le
XML est « extensible » (peut être étendu) et que c'est un métalangage : les
deux affirmations vont dans le même sens.
Le XML définit une grammaire stricte. On dit d'un document qui respecte
cette grammaire qu'il est bien formé.
241
Xml et XML pour les spécifications XML. Ainsi, la balise <xmldanslavie>
est à éviter.
Pour les balises de début, aussi dites d'ouverture, on peut ajouter un attribut
au nom XML de la balise. Un attribut porte un nom XML qui doit respecter
les mêmes règles que les noms XML des balises ; il est suivi du symbole
= et d'une valeur placée entre guillemets ou apostrophes. Par exemple, la
balise <lavie age="5"> indique que l'élément lavie a un attribut
(age="5") qui a comme nom XML age et comme valeur 5. Une balise peut
avoir plusieurs attributs, comme <lavie age="5" sexe="M">, mais ils
doivent tous porter des noms XML différents : <lavie age="5"
age="7"> n'est pas autorisée.
http://www.iana.org/assignments/language-subtag-registry
Un élément est l'ensemble du texte borné par deux balises ayant le même
nom XML, comme <lavie> et </lavie>. On dit que l'élément
<lavie></lavie> a pour nom XML lavie. L'élément hérite des attributs
de sa balise de départ : l'élément <lavie age="5"></lavie> a l'attribut
age="5". Il est à noter que la casse est significative en XML : les balises
<A> et <a> n'ont pas le même nom XML. Dans le cas particulier où
l'élément est vide (sans contenu), on peut remplacer <lavie></lavie>
par <lavie/> pour abréger. Un élément peut contenir d'autres éléments,
comme dans <lavie><a></a></lavie>, ou du texte, ou du texte et des
éléments comme <lavie>fd<a>fsd</a>fd</lavie>.
242
Un document XML ne doit avoir qu'un et un seul élément appelé
« élément-racine », qui doit contenir tous les autres éléments. Il faut
correctement ouvrir et fermer les éléments en séquence, ainsi
<a><b></a></b> n'a aucun sens en XML, il faut plutôt écrire
<a><b></b></a> ou alors <a></a><b></b>. Les éléments peuvent eux
aussi avoir des attributs, par exemple <a att="test"></a>, mais il faut
obligatoirement mettre la valeur de l'attribut entre guillemets ( ") ou
apostrophes (').
Dans un document XML, l'ordre dans lequel les éléments sont présentés
importe. Par exemple, les deux documents XML suivants ne sont pas
équivalents.
1. <racine>
2. <element1>http://www.google.com</element1>
3. <element2>Un moteur de recherche</element2>
4. </racine>
5. <racine>
6. <element2>Un moteur de recherche</element2>
7. <element1>http://www.google.com</element1>
8. </racine>
En revanche, l'ordre dans lequel les attributs sont présentés est sans
importance. Les deux documents XML suivants sont donc équivalents.
<nom>John&Smith</nom>
On utilise aussi les appels d'entités pour noter les valeurs des attributs.
Supposons que la valeur d'un attribut est une apostrophe suivie d'un
guillemet ('"). Les deux choix possibles <nom att="'""> et <nom
att=''"'> ne sont pas valables. Dans ce cas, il faudra écrire <nom
att="'"">, <nom att=''"'>, <nom
att="'""> ou <nom att=''"'>.
243
Il arrive parfois qu'il soit trop lourd d'utiliser des appels d'entités, et on peut
alors utiliser un segment CDATA. Un tel segment débute par <![CDATA[ et
se termine par ]]>. Tout le texte au sein du segment est rendu tel quel, sans
modification ou interprétation (verbatim). Ainsi, le texte
<![CDATA[<monelement />]]> est équivalent à <monelement />.
1. <racine>
2. adresse relative:
3. <image lien="pong.png" />
4. </racine>
244
Le XML permet de spécifier un attribut xml:base qui sert à interpréter les
adresses relatives du document. Le document suivant pointe vers une image
à l'adresse http://domaine.ca/image/pong.png :
1. import xml.dom.minidom
2. document =
xml.dom.minidom.parseString("<element><t1>1</t1></element>")
3. print(getElementsByTagName("t1")[0].childNodes[0].nodeValue) #
affiche ‘1’
Nous pouvons créer de nouveaux documents XML d’une manière
semblable :
1. document = xml.dom.minidom.Document()
2. e = document.createElement("element")
3. t1 = document.createElement("t1")
4. e.appendChild(t1)
5. document.appendChild(e)
6. text = document.createTextNode('1')
7. t1.appendChild(text)
8. document.toprettyxml() //'<?xml version="1.0"
?>\n<element>\n\t<t>1</t>\n</element>\n'
Exercice. Concevez un programme Python qui représente un tableau
d’entiers en XML. Votre programme doit créer un document XML et le
lire ensuite pour récupérer le tableau à partir du XML.
245
de caractères au format XML comme paramètre et renvoie un tableau de
nombres. Elle utilise la fonction parseString() du module
xml.dom.minidom pour créer un document XML à partir de la chaîne de
caractères. Elle utilise ensuite la méthode getElementsByTagName() pour
obtenir une liste de tous les éléments nommés <element> dans le
document XML. Elle parcourt cette liste et ajoute à un tableau vide les
valeurs numériques des éléments, en utilisant la méthode firstChild()
pour accéder au contenu textuel des éléments, et la fonction int() pour
convertir les chaînes de caractères en nombres entiers. Elle renvoie ensuite
le tableau comme résultat.
CodePython/chapitre14/Exercice1.py
1. import xml.dom.minidom
2.
3. def tableau_vers_xml(tableau):
4. document = xml.dom.minidom.Document()
5. tab = document.createElement("tableau")
6. document.appendChild(tab)
7. for i in tableau:
8. element = document.createElement("element")
9. element.appendChild(document.createTextNode(str(i)))
10. tab.appendChild(element)
11. return document.toprettyxml()
12.
13. def xml_vers_tableau(xmlchaine):
14. tableau = []
15. document = xml.dom.minidom.parseString(xmlchaine)
16. for element in document.getElementsByTagName("element"):
17. tableau.append(int(element.firstChild.data))
18. return tableau
19.
20. tableau = [1, 2, 3, 4, 5]
21. xmlchaine = tableau_vers_xml(tableau)
22. print(xmlchaine)
23. print(xml_vers_tableau(xmlchaine))
13.1.3 HTML
Le HTML a été inventé par Tim Berners-Lee, alors qu'il travaillait pour le
centre de recherche CERN en Suisse. L'intention de Berners-Lee était de
proposer un système révolutionnaire de gestion de l'information qu'il
appela World Wide Web. Comme pièce maîtresse de son architecture, il
avait besoin d'un format de documents qu'il appela HTML (HyperText
Markup Language). La première page Web, dont l'adresse était
http://nxoc01.cern.ch/hypertext/WWW/TheProject.html, vit le
jour en 1990.
246
L'élément body contient des balises et du texte. Les retours de ligne sont
traités comme des espaces normaux.
1. <!doctype html>
2. <html>
3. <html>
4. <head>
5. <title>Titre de mon document</title>
6. </head>
7. <body>
8. Voici mon document.
9. Voici ma vie.
10. Voici mon chat.
11. </body>
12. </html>
La question qui se pose alors est : comment faire des paragraphes? En effet,
avec notre dernier exemple, le texte s'affichera sur une seule ligne. La
solution consiste à utiliser l'élément p pour paragraphe. Pour avoir trois
paragraphes, on remplace le document HTML précédent par celui qui suit.
Observez que chaque balise p ouverte doit être fermée.
1. <!doctype html>
2. <html>
3. <head>
4. <title>Titre de mon document</title>
5. </head>
6. <body>
7. <p>Voici mon document.</p>
8. <p>Voici ma vie.</p>
9. <p>Voici mon chat.</p>
10. </body>
11. </html>
Supposons que l'on veuille faire une liste, comme ceci :
1. <!doctype html>
2. <html>
3. <head>
4. <title>Titre de mon document</title>
5. </head>
6. <body>
247
7. <ul>
8. <li>Premier point: le chat est noir.</li>
9. <li>Second point: le chat est blanc.</li>
10. <li>Dernier point: le chat est marron.</li>
11. </ul>
12. </body>
13. </html>
Supposons maintenant que nous voulions une liste avec un compteur :
1. <!doctype html>
2. <html>
3. <head>
4. <title>Titre de mon document</title>
5. </head>
6. <body>
7. <ol>
8. <li>Le chat est noir.</li>
9. <li>Le chat est blanc.</li>
10. <li>Le chat est marron.</li>
11. </ol>
12. </body>
13. </html>
Supposons maintenant que nous voulions produire un tableau. On peut
l'obtenir à l'aide d'un élément « table ». Cet élément contiendra plusieurs
éléments tr (éléments correspondant à une ligne) qui eux-mêmes
contiennent des éléments td (éléments correspondant à une cellule).
1. <!doctype html>
2. <html>
3. <head>
4. <title>Titre de mon document</title>
5. </head>
6. <body>
7. <table>
8. <tr>
9. <td>Nom</td>
10. <td>Valeur</td>
11. </tr>
12. <tr>
13. <td>Mustang</td>
14. <td>50 $</td>
15. </tr>
16. <tr>
248
17. <td>Ferrari</td>
18. <td>500 $</td>
19. </tr>
20. </table>
21. </body>
22. </html>
Tous les tableaux ne sont pas si simples. On peut faire en sorte qu'une
même cellule occupe deux colonnes (<td colspan="2">) ou deux
rangées (<td rowspan="2">). Aussi, souvent, on utilise l'élément th au
lieu de l'élément td pour désigner la première rangée d'un tableau lorsque
celle-ci forme l'entête descriptive du tableau. Il est aussi possible d'utiliser
un élément caption au sein d'un tableau pour noter le titre du tableau :
1. <!doctype html>
2. <html>
3. <head>
4. <title>Titre de mon document</title>
5. </head>
6. <body>
7. <table border="1">
8. <caption>Valeur de différents véhicule</caption>
9. <tr>
10. <th>Nom</th>
11. <th>Valeur</th>
12. </tr>
13. <tr>
14. <td>Mustang</td>
15. <td>50 $</td>
16. </tr>
17. <tr>
18. <td>Ferrari</td>
19. <td>500 $</td>
20. </tr>
21. </table>
22. </body>
23. </html>
On peut très facilement utiliser des effets de polices de caractères en
HTML. Pour obtenir des caractères en italique, par exemple maman, il suffit
d'utiliser un élément i comme ceci : <i>maman</i>. Pour des caractères
en gras, comme maman, il suffit d'utiliser un élément b comme ceci :
<b>maman</b>. On peut également combiner les deux, comme maman, en
écrivant <i><b>maman</b></i> ou bien <b><i>maman</i></b>. Il est
cependant préférable d'utiliser em (emphase) au lieu de i et strong (fort)
au lieu de b : le navigateur choisira alors de rendre le texte dans un élément
em avec un italique ou une autre technique appropriée, et de rendre le texte
dans un élément strong en caractères gras ou une autre technique
appropriée. On évite ainsi de confondre la présentation (italique ou gras) et
la sémantique (emphase ou point fort). Dans le cas où un terme est défini,
249
vous devriez utiliser un élément dfn (définition) comme dans cet exemple:
La <dfn>mort</dfn> est la fin de la vie
Pour insérer une image dans un document HTML, il suffit d'utiliser une
balise img avec comme attribut src pour source, soit l'URL. Par exemple,
le code
<img src="https://monsite.ca/image.svg">
On peut également ajouter des marqueurs dans une page Web en utilisant
la syntaxe :
Dans un texte, il arrive qu'on veuille utiliser des exposants et des indices.
Les éléments sup et sub servent à cette fonction. Par exemple, « premier »
peut s'écrire 1<sup>er</sup>. Il n'est malheureusement pas possible de
noter automatiquement des notes en bas de page en HTML.
250
Il y a plusieurs éléments permettant de traiter de la programmation
informatique ou des mathématiques. Le code informatique peut s'écrire
dans un élément code. Le texte saisi à l'écran par un utilisateur peut s'écrire
dans un élément kbd. Les exemples de sortie à l'écran peuvent s'écrire dans
un élément samp (pour sample) et les variables peuvent s'écrire dans un
élément var. Voici un exemple :
<p>La valeur de la variable <var>i</var> est obtenue avec ce
code:</p><code>int i = 1; i+=1</code><p>On s'attend à ce que l'utilisateur
tape <kbd>Yes</kbd> pour oui. Voici un exemple de résultat:
<samp>Error!</samp>.</p>
251
1. <figure>
2. <img src="http://lemire.me/fr/images/JPG/profile2011B_152.jpg"
/>
3. <legend>Photo du professeur</legend>
4. </figure>
Exercice. Produisez une page HTML représentant un document ayant
trois sections. Votre document doit comprendre une table des matières qui
comportent des liens vers les trois sections.
Solution. Pour créer la table des matières, nous utilisons des balises <a>
dont la fonction est de créer un lien hypertexte, c’est-à-dire un texte
cliquable qui renvoie vers une autre page ou une autre partie de la même
page. Elle a un attribut href qui indique l’adresse du lien, et un attribut id
qui indique un identifiant unique pour l’élément. On peut utiliser cet
identifiant pour créer des liens internes, en utilisant le symbole # suivi de
l’identifiant, comme href="#section1". Si vous document est trop court,
il peut être difficile de voir l’intérêt de la table des matières. Dans la solution
proposée, nous utilisons margin-bottom pour définir la marge inférieure
d’un élément, c’est-à-dire l’espace entre le bord inférieur de l’élément et le
bord supérieur de l’élément suivant. La valeur peut être exprimée en pixels
(px), en centimètres (cm), en pourcentage (%), etc. Par exemple,
style="margin-bottom:10cm;" crée une marge inférieure de 10
centimètres pour l’élément. Une telle marge nous permet de faire un
document qui couvre beaucoup d’espace vertical sans avoir à inclure
beaucoup de contenu.
CodePython/chapitre14/Exercice2.html
1. <!DOCTYPE html>
2. <html>
3. <head>
4. <meta charset="UTF-8">
5. <title>Exemple de page html</title>
6. </head>
7. <body>
8. <h1>Exemple de page html</h1>
9. <p>Cette page contient trois sections et une table des matières comprenant des liens
vers les trois sections.</p>
10. <h2>Table des matières</h2>
11. <ul>
12. <li><a href="#section1">Section 1</a></li>
13. <li><a href="#section2">Section 2</a></li>
14. <li><a href="#section3">Section 3</a></li>
15. </ul>
16. <h2 id="section1">Section 1</h2>
17.
18. <p style="margin-bottom:10cm;">Ceci est la deuxième section de la page. Elle contient
du texte. </p>
19.
20.
21. <h2 id="section2">Section 2</h2>
22. <p>Ceci est la deuxième section de la page. Elle contient du texte et une liste.</p>
23. <ul style="margin-bottom:10cm;">
24. <li>Un élément de la liste</li>
25. <li>Un autre élément de la liste</li>
252
26. <li>Encore un élément de la liste</li>
27. </ul>
28.
29.
30. <h2 id="section3">Section 3</h2>
31. <p>Ceci est la troisième et dernière section de la page. Elle contient du texte et un
tableau.</p>
32. <table style="margin-bottom:30cm;">
33. <tr>
34. <th>Nom</th>
35. <th>Prénom</th>
36. <th>Age</th>
37. </tr>
38. <tr>
39. <td>Dupont</td>
40. <td>Jean</td>
41. <td>25</td>
42. </tr>
43. <tr>
44. <td>Durand</td>
45. <td>Marie</td>
46. <td>30</td>
47. </tr>
48. <tr>
49. <td>Martin</td>
50. <td>Pierre</td>
51. <td>35</td>
52. </tr>
53. </table>
54.
55. </body>
56. </html>
13.1.4 CSS
Au sein d'un navigateur, la forme un document HTML ou XML est
précisée par un ou plusieurs documents CSS. Un document CSS permet de
spécifier comment le contenu du document XML ou HTML sera affiché.
1. facture {
2. display: block;
3. margin-bottom: 30pt;
4. }
5.
6. montant {
7. color: red;
8. }
9.
10. raison {
11. display: block;
12. font-style: italic;
13. margin-left: 1cm;
14. }
Pour vérifier que c'est bien le cas, il suffit de créer un fichier chap12.css
avec le contenu CSS précédent et de modifier le fichier XML en y ajoutant
une ligne pointant vers le fichier CSS (<?xml-stylesheet
type="text/css" href="chap12.css"?>), comme ceci :
254
• Les instructions margin-bottom: 30pt; et margin-left:
1cm; définissent des marges en bas et à gauche de 30 points et
de 1 cm respectivement.
Pour définir une couleur précise, et non les couleurs courantes comme red,
green, blue, yellow, white, black, etc., elle peut-être spécifiée selon sa
composition en couleurs de base RGB (« red », « green , « blue ») avec
une instruction comme background-color:rgb(200,200,200); où
chaque valeur numérique est entre 0 et 255 inclusivement.
En CSS, la taille d'un objet peut être spécifiée avec plusieurs unités de
mesure, par exemple cm pour centimètre ou px pour pixel. Ainsi donc
l'instruction width:1px spécifie une largeur de 1 pixel. On peut aussi
utiliser des unités relatives comme em, rem ou %. Une mesure de
50% indique que l'objet devrait occuper la moitié de l'espace disponible.
Une mesure de 1em correspond à la taille de la police de caractère dans
l'élément courant alors que 1rem correspond à la taille de la police de
caractère dans l'élément-racine du document. On peut aussi combiner les
unités, par exemple, pour spécifier une dimension correspondant à tout
l'espace disponible moins 80 pixels, on peut utiliser la valeur calc(100% -
80px).
L’affichage d’un élément peut être contrôlé avec la propriété display qui
peut prendre plusieurs valeurs dont celles-ci :
255
Voici un exemple :
1. p { display: block }
2. strong { display: inline }
3. li { display: list-item }
4. img { display: none }
La propriété float d'un élément lui permet de sortir du flot normal des
éléments et de se placer à gauche ou à droite. Par exemple, une image en
HTML s'affiche normalement comme un bloc. On peut forcer l'image à
s'intégrer au paragraphe suivant avec l'instruction float: right ou
float: left. La propriété float permet aussi de créer plusieurs colonnes
de texte comme dans un journal.
Bien que la justification du texte puisse être changée avec une instruction
comme text-align: center, centrer un élément requiert plutôt une
manipulation des marges avec la valeur spéciale auto, comme dans cet
exemple :
1. p { width: 5cm;
2. margin-left: auto;
3. margin-right: auto;}
Il aurait été sans doute préférable d'avoir une instruction dédiée pour
centrer les éléments comme il s'agit d'une opération courante.
1. blockquote:before {content:"«";}
2. blockquote:after {content:"»";}
256
Ceci n’est pas limité au texte cependant. Il est possible, par exemple,
d'ajouter automatiquement une image avant chaque élément comme ceci :
1. p:before {content:url("monimage.png";}
Certains sélecteurs n'agissent qu'en réponse aux comportements de
l'utilisateur. Par exemple, le sélecteur p:hover sélectionne les éléments p
qui sont survolés par le curseur de la souris. Il existe plusieurs sélecteurs
d'interaction dont :link (lien non visité), :visited (lien visité), :active
(l'utilisateur utilise un élément), :focus (l'élément est sélectionné par
l'utilisateur). On peut aussi combiner les sélecteurs comme dans
a:hover:focus. On appelle aussi ces sélecteurs pseudo-class.
Dans le cas du HTML, les navigateurs utilisent une liste de règles par défaut.
Ces règles vont varier d'un navigateur à l'autre, mais voici un exemple de
règles utilisées par des navigateurs :
1. html, div {
2. display: block;
3. }
4.
5. body {
6. display: block;
7. margin: 8px;
8. }
9.
10. p, dl, multicol {
11. display: block;
12. margin: 1em 0;
13. }
14.
15. blockquote {
16. display: block;
17. margin: 1em 40px;
18. }
19.
20. h1 {
21. display: block;
22. font-size: 2em;
23. font-weight: bold;
24. margin: .67em 0;
25. }
26.
27. h2 {
28. display: block;
29. font-size: 1.5em;
30. font-weight: bold;
31. margin: .83em 0;
32. }
33.
257
34.
35. pre {
36. display: block;
37. white-space: pre;
38. margin: 1em 0;
39. }
40.
41. b, strong {
42. font-weight: bolder;
43. }
44.
45. i, cite, em, var, dfn {
46. font-style: italic;
47. }
48.
49.
50. u, ins {
51. text-decoration: underline;
52. }
53.
54. s, strike, del {
55. text-decoration: line-through;
56. }
57.
58. big {
59. font-size: larger;
60. }
61.
62. small {
63. font-size: smaller;
64. }
65.
66. sub {
67. vertical-align: sub;
68. font-size: smaller;
69. line-height: normal;
70. }
71.
72. sup {
73. vertical-align: super;
74. font-size: smaller;
75. line-height: normal;
76. }
77.
78. ul, menu, dir {
79. display: block;
80. list-style-type: disc;
81. margin: 1em 0;
82. }
83.
84. ol {
85. display: block;
86. list-style-type: decimal;
258
87. margin: 1em 0;
88. }
89.
90. li {
91. display: list-item;
92. }
93.
94. area, base, basefont, head, meta, script, style, title,
95. noembed, param {
96. display: none;
97. }
L'astérisque nous permet d'appliquer une règle à tous les éléments, comme
dans *{color:red;}.
Avec les CSS, en utilisant les crochets, nous pouvons sélectionner tous les
éléments ayant un attribut donné. Par exemple, l'instruction
*[monattribut] {color:red;} met en rouge tous les éléments ayant
un attribut portant le nom monattribut. Nous pouvons aussi, bien sûr,
limiter la sélection à des éléments portant un nom donné comme dans
maman[monattribut] {color:red;} où les éléments maman ayant un
attribut monattribut seront en rouge. Finalement, nous pouvons de plus
limiter la sélection à des attributs ayant une certaine valeur, comme dans
maman[monattribut="papa"] {color:red;}.
259
Supposons maintenant que nous voulions afficher en rouge tous les
éléments facture et maison. Nous pouvons le faire avec deux
instructions :
facture {color:red;}
maison {color:red;}
Supposons maintenant que nous ne voulions pas afficher tous les éléments
personne en rouge, mais seulement les éléments personne contenus dans
un élément facture. Ce résultat est obtenu en plaçant les deux noms
d'élément côte-à-côte (séparé par un espace). Ainsi, l'instruction facture
personne {color:red;} affichera en rouge tous les éléments
« personne » contenus dans un élément facture, comme dans l'exemple
qui suit :
260
Supposons maintenant, dans l'exemple suivant, que nous voulions indenter
le premier paragraphe (élément p) suivant le titre (élément titre) :
Par ailleurs, nous pouvons combiner les espaces, les +, les virgules et les >
dans un même sélecteur. Par exemple, a + b, c { ... } s'applique aux
éléments c et aux éléments « b » qui suivent immédiatement un élément a.
Si vous avez des éléments ayant des attributs ayant une valeur de type ID,
leur valeur est un nom XML et elle ne doit être utilisée qu'une seule fois.
C'est le cas des attributs de la forme id="xxx" que l'on peut utiliser avec
pratiquement tous les éléments HTML. On peut sélectionner un élément
basé sur la valeur d'un tel attribut en utilisant le symbole # :
1. #xxx {
2. color: red;
3. }
Dans ce cas, le contenu d'un élément comme une balise HTML <p
id='xxx'>test</p> s'affichera en rouge. On peut combiner les sélecteurs
# avec les autres règles que nous avons traitées, par exemple, le code suivant
261
ne mettra en rouge que les éléments i contenus dans un élément ayant un
attribut de type ID avec pour valeur xxx :
1. #xxx i{
2. color: red;
3. }
Plusieurs instructions peuvent s'appliquer en même temps : un texte peut
être en rouge et souligné. Il peut arriver cependant que deux instructions
CSS se contredisent. Par exemple, un texte ne peut être à la fois en rouge
et en bleu. Dans ce cas, la sélection la plus spécifique l'emporte. Ainsi, dans
l'exemple qui suit...
1. * {
2. color: black;
3. }
4.
5. montant {
6. color: red;
7. }
8.
9. montant > montant {
10. color: yellow;
11. }
Les éléments montant seront en rouge, sauf s'ils sont immédiatement
contenus dans un autre élément montant, auquel cas ils seront en jaune.
1. montant {
2. color: red;
3. }
4.
5. montant {
6. color: black;
7. }
On peut cependant forcer le navigateur à considérer une règle qui apparaît
avant une autre comme ayant tout de même priorité (à spécificité égale). Il
suffit d'utiliser la mention !important.
1. montant {
2. color: red !important;
3. }
4.
5. montant {
6. color: black;
262
7. }
Les éléments montant seront en rouge. Notez que la mention !important
permet souvent d'ignorer la spécificité de la règle : les règles avec cette
mention l'emportent toujours sur les autres. (Si une autre règle avec la
mention !important a une plus grande spécificité, elle aura bien sûr
priorité.)
Les règles par défaut utilisées par votre navigateur dans le cas du HTML
sont lues en premier. De cette manière, toutes les règles que vous stipulez
dans vos propres fichiers CSS et à même le XML ou HTML ont priorité
pour une même spécificité. On peut considérer que les règles avec la
mention !important sont lues en dernier dans la mesure où elles
l'emportent toujours sur les autres.
1. p {
2. width: 20px;
3. }
Le CSS supporte plusieurs unités de mesure outre les pixels dont les pouces
(in), les centimètres (cm), pourcentage (%), etc.
On peut ensuite définir une marge (margin en anglais) autour de tout objet.
La marge est une région où rien ne peut apparaître, incluant un autre objet.
Mais la marge ne fait pas partie de l'objet lui-même. Ainsi, si un objet fait
10 pixels de hauteur et de largeur, et que vous définissez une marge de
10 pixels tout autour de l'objet, un espace d'une superficie de 900 pixels
sera occupé sur l'écran. Voici un exemple de marge :
1. p {
2. margin-top:10px;
3. margin-bottom:10px;
4. margin-right:10px;
5. margin-left:10px;
6. }
Outre la marge, on peut aussi définir une région d'espacement ( padding
en anglais). Contrairement à la marge, la région d'espacement fait partie de
l'objet dans la mesure où, si vous définissez une couleur de fond pour
l'objet, la région d'espacement sera colorée, car elle fait partie de l'objet.
Tout comme la marge, rien n'occupe cette région et elle n'est pas
comptabilisée dans la hauteur et la largeur de l'objet.
263
1. p {
2. padding-top:10px;
3. padding-bottom:10px;
4. padding-right:10px;
5. padding-left:10px;
6. }
On peut définir une bordure (border en anglais) afin de tracer une ligne
tout autour de l'objet. La bordure se place entre la région d'espacement et
la marge. La bordure peut prendre différentes épaisseurs (telles que thin,
medium, et thick) et aussi une couleur.
1. p {
2. border-color: black;
3. border-width: medium;
4. }
Il arrive qu'une image ou qu'un texte excède la taille de l'élément dans lequel
il a été placé. Par défaut, ce texte ou cet image s'affichera au-delà du cadre
de sa boîte (overflow:visible). L'instruction overflow:hidden permet
de faire disparaître la partie d'un texte ou d'une image qui excède la boîte,
alors que l'instruction overflow:scroll va faire apparaître des barres de
défilement pour permettre à l'utilisateur d'avoir accès à tout le contenu sans
pour autant avoir un dépassement.
Il arrive qu'on veuille qu'une page s'affiche différemment selon qu'on utilise
un écran, qu'on l'imprime sur papier ou qu'on utilise un téléphone cellulaire.
L'instruction @media permet de préciser quel médium est concerné par la
règle CSS. Plusieurs média sont reconnus dont handheld (appareil portable
comme un téléphone cellulaire), screen (à l'écran), tv (sur un téléviseur),
print (sur papier), etc. Lorsque l'instruction @media n'est pas précisée, la
règle s'applique tout le temps. Voici un exemple :
1. @import "mineure.css";
264
2. @import "print-mineure.css" print;
On peut indenter la première ligne d'un paragraphe avec l'instruction text-
indent. Par exemple, pour indenter la première ligne de tous les
paragraphes par 1 cm, on peut utiliser l'instruction text-indent: 1cm.
On peut définir des compteurs pour, par exemple, numéroter des chapitres
ou paragraphes. Ce premier exemple numérote les paragraphes (éléments
p) contenus entre chaque élément h1 avec des nombres romains :
1. p {counter-increment: par-num}
2. h1 {counter-reset: par-num}
3. p:before {content: counter(par-num, upper-roman) ". "}
Ce second exemple numérote les éléments h1 avec des nombres arabes :
1. h1 {counter-increment: h1-num}
2. h1:before {content: counter(h1-num, decimal) ". "}
Ce troisième exemple « numérote » les éléments h1 et h2 avec des lettres
(a,b,c,...) :
265
Exercice. Écrivez un document CSS qui permet de donner aux lignes
successives d’un tableau les couleurs « rouge, bleu et blanc ». L’entête du
tableau ne doit pas recevoir de couleur.
CodePython/chapitre14/Exercice3.html
1. <!DOCTYPE html>
2. <html>
3. <head>
4. <meta charset="UTF-8">
5. <title>Exemple de page html</title>
6. <style type>
7. /* On utilise le pseudo-sélecteur :nth-child(n) pour cibler les lignes selon leur
position */
8. /* On utilise la notation an+b pour indiquer un motif répétitif */
9. /* Par exemple, :nth-child(3n+4) signifie "tous les éléments dont la position est
un multiple de 3 plus 4" */
10.
11. /* On sélectionne les lignes dont la position est un multiple de 3 plus 1, c'est-
à-dire la première, la quatrième, la septième, etc. */
12. tr:nth-child(3n+4) {
13. /* On définit la couleur de fond de ces lignes à rouge */
14. background-color: red;
15. }
16.
17. /* On sélectionne les lignes dont la position est un multiple de 3 plus 2, c'est-
à-dire la deuxième, la cinquième, la huitième, etc. */
18. tr:nth-child(3n+2) {
19. /* On définit la couleur de fond de ces lignes à bleu */
20. background-color: blue;
21. }
22.
23. /* On sélectionne les lignes dont la position est un multiple de 3, c'est-à-dire
la troisième, la sixième, la neuvième, etc. */
24. tr:nth-child(3n) {
25. /* On définit la couleur de fond de ces lignes à blanc */
26. background-color: white;
27. }</style>
28. </head>
29. <body>
30. <h1>Exemple de page html</h1>
31. <table>
32. <tr>
33. <th>Nom</th>
34. <th>Prénom</th>
35. <th>Age</th>
36. </tr>
37. <tr>
38. <td>Dupont</td>
39. <td>Jean</td>
40. <td>25</td>
41. </tr>
42. <tr>
43. <td>Durand</td>
44. <td>Marie</td>
45. <td>30</td>
46. </tr>
47. <tr>
48. <td>Martin</td>
49. <td>Pierre</td>
50. <td>35</td>
51. </tr>
52. <tr>
53. <td>Luc</td>
54. <td>Roger</td>
266
55. <td>33</td>
56. </tr>
57. <tr>
58. <td>Annie</td>
59. <td>Marie</td>
60. <td>31</td>
61. </tr>
62. </table>
63.
64. </body>
65. </html>
13.1.5 JSON
Il existe plusieurs formats de documents semi-structurés basés sur du texte
(par exemple, HTML, XML, JSON). Le JSON est peut-être le format le
plus populaire en ligne pour l'échange de données. Plusieurs systèmes de
base de données tels que CouchDB, RethinkDB, MongoDB, SimpleDB et
JSON Tiles utilisent JSON comme principal format d'échange.
Un document JSON doit être stocké dans une chaîne Unicode (UTF-8)
valide. La syntaxe JSON est presque un sous-ensemble strict du langage de
programmation populaire JavaScript. Elle comporte quatre types primitifs
(chaîne, nombre, booléen, null) et deux types composés (tableaux et objets).
Un objet se présente sous la forme d'une série de paires clé-valeur entre
accolades, où les clés sont des chaînes de caractères et les valeurs des types
primitifs ou composés (par exemple, {"nom":"Jack", "âge":22}). Un
tableau est une liste de valeurs séparées par des virgules (qu'elles soient
primitives ou composées) entre crochets (par exemple, [1, "abc",
null]). La spécification JSON comporte six caractères structurels ( [, ], {,
}, :, "), pour délimiter l'emplacement et la structure des objets et des
tableaux.
1. {
2. "nom": "Daniel Lemire",
3. "âge": 72,
4. "téléphone": ["442-4321","442-4323"]
5. }
1. <personne>
2. <nom>Daniel Lemire</nom>
3. <age>72</age>
4. <telephone>442-4321</telephone>
5. <telephone>442-4323</telephone>
6. </personne>
267
Il est relativement facile de convertir un document JSON en structures de
données Python :
1. import json
2. data = json.loads('{"nom": "Daniel Lemire", "âge": 72,
"téléphone": ["442-4321","442-4323"],}')
3. // résultat : {'nom': 'Daniel Lemire', 'âge': 72, 'téléphone':
['442-4321', '442-4323']}
Nous pouvons aussi générer du JSON à partir d’une structure de données
Python :
1. import json
2. json.dumps({'nom': 'Daniel Lemire', 'âge': 72, 'téléphone':
['442-4321', '442-4323']}, ensure_ascii=False)
3. // résultat'{"nom": "Daniel Lemire", "âge": 72, "téléphone":
["442-4321", "442-4323"]}'
Exercice. Écrivez programme Python qui charge un document JSON
intitulé exercice4.json contenant des numéros de téléphones et qui trouve
le numéro de téléphone de John Galt.
1. [
2. {
3. "name": "Alice",
4. "phone": "+1 234 567 8901"
5. },
6. {
7. "name": "Bob",
8. "phone": "+1 345 678 9012"
9. },
10. {
11. "name": "John Galt",
12. "phone": "+1 456 789 0123"
13. },
14. {
15. "name": "Eve",
16. "phone": "+1 567 890 1234"
17. }
18. ]
Solution. Notre solution est un programme Python qui fait les choses
suivantes. Il importe le module json, qui permet de manipuler des données
au format. Il ouvre le fichier exercice4.json, qui contient une liste de
numéros de téléphone au format JSON, en mode lecture ( r) et l’assigne à
la variable f. Il utilise la fonction json.load(f) pour charger les données
JSON du fichier f dans une variable appelée data. Cette variable est une
liste de dictionnaires, où chaque dictionnaire représente un numéro de
téléphone avec deux clés: name et phone. Il parcourt la liste de numéros de
téléphone avec une boucle for, en utilisant la variable item pour accéder
268
à chaque dictionnaire de la liste. Il vérifie si la valeur associée à la clé name
dans le dictionnaire item est égale à John Galt avec une condition if. Si
c’est le cas, il affiche le numéro de téléphone de John Galt avec la fonction
print(), en utilisant la valeur associée à la clé phone dans le dictionnaire
item. Il utilise l’instruction break pour arrêter la boucle for dès qu’il a
trouvé le numéro de John Galt, sans parcourir le reste de la liste.
CodePython/chapitre14/Exercice4.py
Le mot-clé var est le mot-clé le plus fréquemment utilisé pour déclarer des
variables. Ce mot-clé était disponible avant let et const. Une variable
qu'on déclare avec var est disponible dans la fonction dans laquelle elle est
déclarée.
Les objets en JavaScript sont des collections de paires clé-valeur. Ils sont
similaires aux dictionnaires en Python.
1. let objet = {
2. nom: "Daniel",
3. age: 12,
4. };
5. console.log(objet.nom); // Affiche "Daniel"
6. console.log(objet.age); // Affiche 12
7. console.log(objet["nom"]); // Affiche "Daniel"
8. objet.nom = "Lucie";
270
On peut facilement créer de nouveaux tableaux en JavaScript avec les
crochets [].
1. for (const i of a) {
2. // Faire quelque chose avec i
3. }
Un tableau JavaScript peut être convertit en chaîne de caractères avec
l’attribut toString : a.toString().
1. function moyenne(a, b) {
2. return (a + b) / 2;
3. }
4. moyenne(2, 3); // 2.5
On peut aussi définir des fonctions anonymes. Une fonction anonyme est
une fonction qui n'a pas de nom. On peut par exemple stocker une fonction
anonyme dans une variable.
271
1. a.forEach((element) => { element + 1; });
On peut aussi créer un nouveau tableau avec Arrays.from().
1. const data = `[
2. {
3. "name": "Alice",
4. "phone": "+1 234 567 8901"
5. },
6. {
7. "name": "Bob",
8. "phone": "+1 345 678 9012"
9. },
10. {
11. "name": "John Galt",
12. "phone": "+1 456 789 0123"
13. },
14. {
15. "name": "Eve",
16. "phone": "+1 567 890 1234"
17. }
18. ]`;
Solution. Notre solution est un programme JavaScript qui fait les choses
suivantes. Il définit une variable data qui contient une chaîne de caractères au
format. Cette chaîne représente une liste de numéros de téléphone, où chaque
numéro est associé à un nom. Il utilise la fonction JSON.parse(data) pour
convertir la chaîne JSON en un objet Javascript, qui est une structure de
données qui peut contenir des valeurs de différents types. Cette fonction renvoie
une liste d’objets, où chaque objet représente un numéro de téléphone avec
deux propriétés: name et phone. Il assigne cette liste d’objets à une variable
appelée phoneList. Il parcourt la liste de numéros de téléphone avec une
boucle for…of, qui permet d’itérer sur les éléments d’un objet itérable
(comme une liste). Il utilise la variable item pour accéder à chaque objet de la
liste. Il vérifie si la valeur de la propriété name de l’objet item est égale à John
Galt avec une condition if. Si c’est le cas, il affiche le numéro de téléphone de
John Galt avec la fonction console.log(), en utilisant la valeur de la
propriété phone de l’objet item. Il utilise l’instruction break pour arrêter la
boucle for dès qu’il a trouvé le numéro de John Galt, sans parcourir le reste
de la liste.
272
CodePython/chapitre14/Exercice5.js
1. const data = `[
2. {
3. "name": "Alice",
4. "phone": "+1 234 567 8901"
5. },
6. {
7. "name": "Bob",
8. "phone": "+1 345 678 9012"
9. },
10. {
11. "name": "John Galt",
12. "phone": "+1 456 789 0123"
13. },
14. {
15. "name": "Eve",
16. "phone": "+1 567 890 1234"
17. }
18. ]`;
19. // Convertir les données JSON en un objet Javascript
20. const phoneList = JSON.parse(data);
21.
22. // Parcourir la liste de numéros de téléphone avec une boucle for...of
23. for (const item of phoneList) {
24. // Vérifier si le nom correspond à John Galt
25. if (item.name === "John Galt") {
26. // Afficher le numéro de téléphone de John Galt
27. console.log("Le numéro de téléphone de John Galt est:", item.phone);
28. // Arrêter la boucle
29. break;
30. }
31. }
Une application Web écrite en Python utilisera souvent des librairies. Il est
possible pour un programmeur de développer plusieurs applications Web
distinctes sur un même ordinateur, et chaque application Web peut
dépendre de librairies différentes. Parfois, deux applications peuvent
utiliser la même librairie, mais viser deux versions différentes de la librairie
en question. Pour cette raison, il est important de pouvoir isoler les librairies
utilisées par une application Web afin qu'elles ne puissent pas interférer
avec les librairies utilisées par d'autres applications sur le même ordinateur.
Les environnements virtuels permettent d'obtenir cette isolation. Un
environnement virtuel est un environnement Python isolé qui permet aux
développeurs de travailler sur des projets sans interférer avec les autres
projets ou avec l’installation Python globale. Les environnements virtuels
sont utiles pour les projets qui ont des dépendances spécifiques à une
version de bibliothèque ou de module Python, car ils permettent aux
développeurs de travailler avec différentes versions de Python et de
bibliothèques sans conflit. Les environnements virtuels sont également
utiles pour les projets qui doivent être exécutés sur différents systèmes, car
ils permettent de nous aider à faire en sorte que toutes les dépendances sont
installées correctement.
Les programmeurs utilisent parfois l'outil venv en Python pour créer des
environnements virtuels. Il existe d'autres outils qui peuvent être utilisés
pour créer des environnements virtuels. Dans ce cours, nous allons utiliser
ce qui est disponible par défaut en Python (à compter de Python 3). Pour
créer un environnement virtuel, il faut d'abord créer un répertoire pour le
projet. Ensuite, un environnement virtuel peut être créé dans ce répertoire.
Vous pouvez maintenant installer les librairies Python dont vous avez
besoin pour votre projet dans l’environnement virtuel.
source mon_env/bin/activate
275
mon_env\Scripts\activate.bat
Pour vérifier que flask a été installé, lancez la console Python (tapez
python) et exécutez l'instruction Python suivante :
Vous devriez recevoir une erreur puisque la librairie flask n'est pas
installée en dehors de l'environnement virtuel. (Il est possible qu'il n'y ait
pas d'erreur si vous aviez précédemment installé flask sur votre
ordinateur.)
276
Alors que vous êtes dans votre répertoire de travail (en dehors du répertoire
de l'environnement virtuel, mon_env), vous pouvez créer un fichier Python
et l'exécuter. Par exemple, vous pouvez créer un fichier projet.py
contenant le code suivant :
1. import pysimdjson
2. from roaringbitmap import RoaringBitmap
3.
4. # Créer un objet RoaringBitmap
5. rb = RoaringBitmap()
6.
7. # Ajouter des éléments à l'objet RoaringBitmap
8. rb.add(1)
9. rb.add(2)
10. rb.add(3)
11.
12. # Convertir l'objet RoaringBitmap en JSON
13. json_data = rb.to_json()
14.
15. # Analyser le JSON avec pysimdjson
16. data = pysimdjson.loads(json_data)
17.
18. # Afficher les éléments de l'objet RoaringBitmap
19. for i in data:
20. print(i)
Si vous exécutez le programme ( python projet.py), vous devriez
recevoir un message d'erreur puisque les librairies roaringbitmap et
pysimdjson ne sont probablement pas présentes sur votre ordinateur.
Pour installer ces librairies, vous pouvez exécuter la commande
Si vous souhaitez installer toutes les librairies d'un projet, vous pouvez créer
un fichier requirements.txt contenant la liste des librairies à installer.
Pour générer un fichier requirements.txt à l’aide de pip, vous pouvez
utiliser la commande python -m pip freeze > requirements.txt.
Cette commande enregistre toutes les bibliothèques Python installées avec
leur version actuelle dans un fichier requirements.txt. Vous pouvez ensuite
277
utiliser ce fichier pour installer les mêmes bibliothèques sur un autre
système ou pour partager votre environnement de développement avec
d’autres. Vous pouvez installer toutes les librairies en exécutant la
commande python -m pip install -r requirements.txt.
1. if __name__ == "__main__":
2. ma_fonction()
Cela garantit que la fonction ma_fonction() n’est exécutée que si le
fichier mon_module.py est exécuté en tant que programme principal. Le
module Flask utilise la variable __name__ pour identifier les ressources
telles que les modèles, les fichiers statiques et le dossier d’instance. Lorsque
vous créez une instance de l’objet Flask, vous passez le nom du module
courant à la variable __name__. Flask utilise ensuite cette variable pour
identifier les ressources nécessaires à l’exécution de l’application.
Flask est une infrastructure logiciel (ou framework) Web flexible pour Python
qui permet de créer rapidement des applications Web. Pour créer un
serveur HTTP avec Flask, il suffit d’associer chaque route à une fonction
qui renvoie le contenu à afficher. Vous pouvez également définir des
paramètres pour chaque route, tels que les méthodes HTTP acceptées, les
types de données acceptés et les types de données renvoyés. Voici quelques
exemples de routes Flask :
278
• /login: associé à la fonction login() qui gère la connexion de
l’utilisateur.
279
Après avoir tapé python server.py, vous devriez avoir un message comme
celui-ci :
Dans ce cas, Flask a choisi le port 5000. Vous pouvez aussi spécifier un
port avec le paramètre port de la fonction run :
1. if __name__ == '__main__':
2. app.run(port=5000)
Le message d’avertissement concernant les « WSGI servers » peut être
ignoré.
280
Voici un exemple de serveur Flask qui renvoie une réponse JSON :
CodePython/chapitre14/virtuel/servercombine.py
1. import Flask
2. app = Flask(__name__)
281
3. @app.route('/utilisateur/<nom>')
4. def show_user_profile(nom):
5. # Affiche le profil de l'utilisateur spécifié par <nom>
6. return '<html><body>Bonjour %s</body></html>' % nom
7.
8. app.run()
Si vous lancez ce serveur et ouvrez la page http://127.0.0.1:5000/
utilisateur/daniel dans votre navigateur, vous devriez voir un
message Bonjour Daniel.
Solution. Note code est un programme Python qui utilise le module Flask.
Il crée une instance de l’application Flask avec la commande app =
Flask(__name__), en utilisant le paramètre __name__ qui représente le
nom du module courant. Il définit la route principale de l’application web
avec le décorateur @app.route("/"), qui associe la fonction index à
l’URL racine de l’application. Il définit la fonction index, qui sera exécutée
quand un utilisateur accède à la route principale. Il importe le module
datetime de la bibliothèque standard Python, qui permet de manipuler des
dates et des heures. Il importe le module locale, qui permet de gérer les
paramètres régionaux, comme la langue, le format de la date, etc. Il définit
le paramètre régional à fr_CA pour le français du Canada avec la
commande locale.setlocale(locale.LC_TIME, "fr_CA"), en
utilisant la constante locale.LC_TIME qui indique le paramètre de la date
et de l’heure. Il obtient la date et l’heure actuelles avec la commande now =
datetime.now(), qui renvoie un objet datetime. Il formate la date et
l’heure en français avec la méthode strftime(), qui convertit un objet
datetime en une chaîne de caractères selon un format spécifié. Il utilise
les directives suivantes: %A pour le nom du jour de la semaine en toutes
lettres, %d pour le jour du mois en chiffres, %B pour le nom du mois en
toutes lettres, %Y pour l’année en quatre chiffres, %H pour l’heure en format
282
24 heures, %M pour les minutes, %S pour les secondes. Il assigne la date et
l’heure formatées à deux variables : date et heure. Il retourne une page
Web avec la date et l’heure avec la commande return f"""...""", qui
utilise une chaîne de caractères littérale étendue (ou template literal en anglais)
pour insérer les variables date et heure dans le code HTML. Il lance
l’application Flask avec la commande app.run(), qui démarre un serveur
web local pour héberger l’application. Il utilise la condition if __name__
== "__main__" pour s’assurer que cette commande n’est exécutée que si
le programme est lancé directement, et non pas importé comme un module.
CodePython/chapitre14/Exercice6.py
1. # Importer le module flask
2. from flask import Flask
3.
4. # Créer une instance de l'application Flask
5. app = Flask(__name__)
6.
7. # Définir la route principale
8. @app.route("/")
9. def index():
10. # Importer le module datetime
11. from datetime import datetime
12.
13. # Importer le module locale, qui permet de gérer les paramètres régionaux
14. import locale
15.
16. # Définir le paramètre régional à "fr_CA" pour le français du Canada
17. locale.setlocale(locale.LC_TIME, "fr_CA")
18.
19. # Obtenir la date et l'heure
20. now = datetime.now()
21.
22. # Formater la date et l'heure en français avec la méthode strftime
23. date = now.strftime("%A %d %B %Y")
24. heure = now.strftime("%H:%M:%S")
25.
26. # Retourner une page web avec la date et l'heure
27. return f"""
28. <html>
29. <head>
30. <title>Date et heure</title>
31. </head>
32. <body>
33. <h1>Bonjour, voici la date et l'heure:</h1>
34. <p>Date: {date}</p>
35. <p>Heure: {heure}</p>
36. </body>
37. </html>
38. """
39.
40. # Lancer l'application Flask
41. if __name__ == "__main__":
42. app.run()
Il peut être un peu lourd de toujours utiliser du code Python pour générer
du HTML. Les développeurs inversent souvent le développement : au lieu
d’écrire du HTML au sein de Python, on peut aussi mettre (un peu) de
Python au sein du HTML. Ou tout simplement, le développeur peut utiliser
du HTML pur au sein de son application écrite en Python. À cette fin, on
utilise le répertoire templates. Le répertoire templates dans Flask est
l’endroit où vous stockez les fichiers de modèle pour votre application
283
Web. Les fichiers de modèle sont des fichiers HTML qui contiennent des
variables et des instructions de contrôle de flux qui sont remplacées par des
valeurs dynamiques lorsqu’un utilisateur visite une page Web. Les fichiers
de modèle doivent être stockés dans le répertoire templates pour que
Flask puisse les trouver automatiquement. Vous pouvez aussi personnaliser
l’emplacement du répertoire de modèles en utilisant le
paramètre template_folder lors de la création d’une instance
d’application Flask. Pour spécifier le répertoire de modèles dans Flask, vous
pouvez utiliser le paramètre template_folder lors de la création d’une
instance d’application Flask. Par exemple, pour définir le répertoire de
modèles sur /path/to/templates, vous pouvez utiliser le code suivant :
1. TEMPLATE_DIR = '/path/to/templates'
2. app = Flask(__name__, template_folder=TEMPLATE_DIR)
Nous allons développer une application Web qui accepte des fichiers
d’images JPEG et qui retourne à l’utilisateur la position géographique où la
photo a été prise. Cette information est généralement présente à même le
fichier au format EXIF. EXIF (Exchangeable Image File Format) est un format
de métadonnées standard pour les fichiers image numériques. Les données
EXIF sont stockées dans les fichiers JPEG et contiennent des informations
sur les paramètres de prise de vue, tels que la date et l’heure de la prise de
vue, la marque et le modèle de l’appareil photo, les paramètres d’exposition,
la distance focale, la vitesse d’obturation, l’ouverture et plus encore. Les
données EXIF sont stockées dans un segment d’application défini par
JPEG. Les données EXIF peuvent être lues et modifiées par des logiciels
de traitement d’image tels que Photoshop ou Lightroom.
1. import exifread
2. # Ouvrir le fichier image pour la lecture (mode binaire)
3. f = open('img.jpg', 'rb')
4.
5. # Extraire les tags EXIF
6. tags = exifread.process_file(f)
284
7.
8. # Parcourir tous les tags et afficher les valeurs
9. for tag in tags.keys():
10. if tag not in ('JPEGThumbnail', 'TIFFThumbnail', 'Filename', 'EXIF MakerNote'):
11. print("Key: %s, value %s" % (tag, tags[tag]))
Dans cet exemple, le fichier image img.jpg est ouvert en mode binaire et
exifread.process_file() extrait les tags EXIF du fichier. Les tags
sont parcourus pour en afficher leurs valeurs. La fonction
get_exif_data() récupère toutes les métadonnées :
1. def get_exif_data(image_file):
2. with open(image_file, 'rb') as f:
3. exif_tags = exifread.process_file(f)
4. return exif_tags
Toutes les valeurs ne sont pas pertinentes ou présentes. Pour éviter d’avoir
à gérer des exceptions, il est pratique d’avoir une fonction qui retourne la
valeur None lorsqu’une valeur est manquante :
285
cartographie Web comme Google Map, Apple Plan, OpenStreetMap, etc.
Nous pouvons y arriver avec le code suivant :
1. def convert_to_degrees(value):
2. d = float(value.values[0].num) / float(value.values[0].den)
3. m = float(value.values[1].num) / float(value.values[1].den)
4. s = float(value.values[2].num) / float(value.values[2].den)
5. return d + (m / 60.0) + (s / 3600.0)
6.
7. def get_exif_location(exif_data):
8. lat = None
9. lon = None
10.
11. gps_latitude = get_if_exist(exif_data, 'GPS GPSLatitude')
12. gps_latitude_ref = get_if_exist(exif_data, 'GPS GPSLatitudeRef')
13. gps_longitude = get_if_exist(exif_data, 'GPS GPSLongitude')
14. gps_longitude_ref = get_if_exist(exif_data, 'GPS GPSLongitudeRef')
15.
16. if gps_latitude and gps_latitude_ref and gps_longitude and gps_longitude_ref:
17. lat = convert_to_degrees(gps_latitude)
18. if gps_latitude_ref.values[0] != 'N':
19. lat = 0 - lat
20.
21. lon = convert_to_degrees(gps_longitude)
22. if gps_longitude_ref.values[0] != 'E':
23. lon = 0 - lon
24.
25. return lat, lon
1. <html>
2. <body>
3. <form action = "{{ url_for('upload_file') }}" method = "POST"
4. enctype = "multipart/form-data">
5. <input type = "file" name = "file" />
6. <input type = "submit"/>
7. </form>
8. <p>{{ message }}</p>
9. </body>
10. </html>
{% if i == 3 %} … {%endif%}
Les instructions de contrôle de flux sont utilisées pour les opérations qui
ne doivent pas être affichées à l’utilisateur.
1. @app.route('/upload')
2. def upload_file_render():
3. return render_template('upload.html', message = 'écrire un message ici')
Pour que l’application Web soit fonctionnelle, il faut aussi créer une route
correspondant à une fonction upload_file() puisque c’est avec cette
route que le formulaire communique. Cette route devra recevoir une image.
Le code Flask suivant crée une route qui accepte les données d’une image
et qui en fait un fichier :
288
envoyées dans une requête POST sont généralement stockées dans le corps
de la requête et peuvent être utilisées pour créer, mettre à jour ou supprimer
des ressources sur le serveur web.
Les routes de type POST servent souvent pour les formulaires web, où les
utilisateurs saisissent des informations qui doivent être envoyées au serveur
Web pour traitement. Les données envoyées dans une requête POST
peuvent être cryptées pour plus de sécurité, ce qui les rend plus appropriées
pour l’envoi d’informations sensibles telles que des noms d’utilisateur et
des mots de passe.
Nous avons maintenant une application Web complète qui peut recevoir
une image, en extraire les coordonnées géographiques et offrir un lien vers
la carte géographique correspondante :
CodePython/chapitre14/virtuel/imageserver.py
1. import exifread
2. from flask import Flask, render_template, request, redirect
3.
4. def get_if_exist(data, key):
5. if key in data:
6. return data[key]
7. return None
8.
9. def convert_to_degrees(value):
289
10. d = float(value.values[0].num) / float(value.values[0].den)
11. m = float(value.values[1].num) / float(value.values[1].den)
12. s = float(value.values[2].num) / float(value.values[2].den)
13.
14. return d + (m / 60.0) + (s / 3600.0)
15.
16. def get_exif_location(exif_data):
17. lat = None
18. lon = None
19.
20. gps_latitude = get_if_exist(exif_data, 'GPS GPSLatitude')
21. gps_latitude_ref = get_if_exist(exif_data, 'GPS GPSLatitudeRef')
22. gps_longitude = get_if_exist(exif_data, 'GPS GPSLongitude')
23. gps_longitude_ref = get_if_exist(exif_data, 'GPS GPSLongitudeRef')
24.
25. if gps_latitude and gps_latitude_ref and gps_longitude and gps_longitude_ref:
26. lat = convert_to_degrees(gps_latitude)
27. if gps_latitude_ref.values[0] != 'N':
28. lat = 0 - lat
29.
30. lon = convert_to_degrees(gps_longitude)
31. if gps_longitude_ref.values[0] != 'E':
32. lon = 0 - lon
33.
34. return lat, lon
35.
36. def get_exif_data(image_file):
37. with open(image_file, 'rb') as f:
38. exif_tags = exifread.process_file(f)
39. return exif_tags
40.
openssl req -x509 -newkey rsa:4096 -nodes -out cert.pem -keyout key.pem -days
365
290
Cette commande génère un certificat auto-signé qui est valide pendant 365
jours. La durée de validité est spécifiée par l’argument -days. Une fois que
le certificat SSL généré, il peut être employé pour exécuter votre application
Flask sur HTTPS. Pour ce faire, il faut ajouter les options ssl_context et
port à l’appel app.run(). Voici un exemple de code Python qui utilise un
certificat auto-signé pour exécuter une application Flask sur HTTPS :
La syntaxe SQL est utilisée pour interagir avec les bases de données
relationnelles. Voici un résumé des commandes SQL les plus courantes:
291
• CREATE TABLE : Cette commande crée une nouvelle table dans
une base de données. La syntaxe est la suivante: CREATE TABLE
table_name (column1 datatype, column2 datatype,
column3 datatype, ...);. Les paramètres column1, column2,
etc. spécifient les noms des colonnes de la table, tandis que les
paramètres datatype spécifient le type de données de chacune
des colonnes.
292
lancement du serveur. Il est possible de le faire avec un code semblable à
celui-ci :
293
CodePython/chapitre14/bd/templates/maison.html
1. <!DOCTYPE html>
2. <html>
3. <head>
4. <title>Page d'accueil</title>
5. </head>
6. <body>
7. {% if users | length != 0 %}
8. <h1>Utilisateurs</h1>
9. <table>
10. <tr>
11. <th>Nom</th>
12. <th>Courriel</th>
13. </tr>
14. {% for user in users %}
15. <tr>
16. <td>{{ user[1] }}</td>
17. <td>{{ user[2] }}</td>
18. </tr>
19. {% endfor %}
20. </table>
21. {% endif %}
22.
23. <h1>Ajouter un utilisateur</h1>
24. <form action="/add" method="post">
25. <label for="name">Nom :</label>
26. <input type="text" id="name" name="name"><br><br>
27. <label for="email">Courriel :</label>
28. <input type="text" id="email" name="email"><br><br>
29. <input type="submit" value="Submit">
30. </form>
31. </body>
32. </html>
La ligne {% if users | length != 0 %} est une instruction Jinja2 qui
vérifie si la variable users contient des éléments. Si c’est le cas, le contenu
entre {% if users | length != 0 %} et {% endif %} sera affiché.
Sinon, il sera ignoré. Les balises <tr>, <th> et <td> définissent les lignes
et les colonnes du tableau. La ligne {% for user in users %} est une
instruction qui crée une boucle pour chaque élément dans la
variable users. Le contenu entre {% for user in users %} et {%
endfor %} sera répété pour chaque élément dans users. Les lignes {{
user[1] }} et {{ user[2] }} sont des instructions Jinja2 qui affichent
les éléments 1 et 2 de chaque élément dans users.
1. def log(long,lat):
2. with sqlite3.connect("img.db") as con:
3. tables = [row[0] for row in con.execute("SELECT name FROM sqlite_master WHERE
type='table'")]
4. if not "geo" in tables:
5. con.execute("CREATE TABLE geo (date TEXT, long NUMERIC, lat NUMERIC)")
6. dt = datetime.now()
7. con.execute("INSERT INTO geo (date,long,lat) values (\""+str(dt)+"\","+str(long)+",
"+str(lat)+") ")
296
CodePython/chapitre14/bd/server.py
1. import exifread
2. from flask import Flask, render_template, request, redirect
3. from datetime import datetime
4. import sqlite3
5.
6. def get_if_exist(data, key):
7. if key in data:
8. return data[key]
9. return None
10.
11. def convert_to_degrees(value):
12. """
13. Helper function to convert the GPS coordinates stored in the EXIF to degress in float
format
14. :param value:
15. :type value: exifread.utils.Ratio
16. :rtype: float
17. """
18. d = float(value.values[0].num) / float(value.values[0].den)
19. m = float(value.values[1].num) / float(value.values[1].den)
20. s = float(value.values[2].num) / float(value.values[2].den)
21.
22. return d + (m / 60.0) + (s / 3600.0)
23.
24. def get_exif_location(exif_data):
25. """
26. Returns the latitude and longitude, if available, from the provided exif_data (obtained
through get_exif_data above)
27. """
28. lat = None
29. lon = None
30.
31. gps_latitude = get_if_exist(exif_data, 'GPS GPSLatitude')
32. gps_latitude_ref = get_if_exist(exif_data, 'GPS GPSLatitudeRef')
33. gps_longitude = get_if_exist(exif_data, 'GPS GPSLongitude')
34. gps_longitude_ref = get_if_exist(exif_data, 'GPS GPSLongitudeRef')
35.
36. if gps_latitude and gps_latitude_ref and gps_longitude and gps_longitude_ref:
37. lat = convert_to_degrees(gps_latitude)
38. if gps_latitude_ref.values[0] != 'N':
39. lat = 0 - lat
40.
41. lon = convert_to_degrees(gps_longitude)
42. if gps_longitude_ref.values[0] != 'E':
43. lon = 0 - lon
44.
45. return lat, lon
46.
47. def get_exif_data(image_file):
48. with open(image_file, 'rb') as f:
49. exif_tags = exifread.process_file(f)
50. return exif_tags
51.
52. def log(long,lat):
53. with sqlite3.connect("img.db") as con:
54. tables = [row[0] for row in con.execute("SELECT name FROM sqlite_master WHERE
type='table'")]
55. if not "geo" in tables:
56. con.execute("CREATE TABLE geo (date TEXT, long NUMERIC, lat NUMERIC)")
57. dt = datetime.now()
58. con.execute("INSERT INTO geo (date,long,lat) values (\""+str(dt)+"\","+str(long)+",
"+str(lat)+") ")
59.
60. app = Flask(__name__)
61.
62. @app.route('/')
63. def index():
64. return redirect('/upload')
65.
66. @app.route('/upload')
67. def upload_file_render():
297
68. return render_template('formulaire.html', message = "Choisissez une image JPEG.")
69.
70. @app.route('/uploader', methods = ['POST'])
71. def upload_file():
72. f = request.files['file']
73. f = request.files['file']
74. lat, long = get_exif_location(exifread.process_file(f))
75. if lat is None:
76. return render_template('formulaire.html', "Il n'y avait pas de données GPS dans
l'image. Choisissez une autre image.")
77. link = "https://www.openstreetmap.org/?mlat="+str(lat)+"&mlon="+str(long)+"&zoom=15"
78. log(long,lat)
79. return "<html><body><a href=\""+link+"\">carte</a></body></html>"
80.
81. app.run()
Exercice. Écrivez un programme Python avec Flask qui stocke dans une
base de données les dates d’accès au serveur.
CodePython/chapitre14/Exercice7.py
1. # Importer le module flask
2. from flask import Flask
3.
4. # Importer le module sqlite3, qui permet de manipuler des bases de données sqlite
5. import sqlite3
6.
7. # Créer une instance de l'application Flask
8. app = Flask(__name__)
9.
10. # Définir le nom du fichier de la base de données
11. db_file = "access.db"
12.
13. # Définir la fonction qui crée la table de la base de données si elle n'existe pas
14. def create_table():
15. # Se connecter à la base de données
16. conn = sqlite3.connect(db_file)
17. # Créer un curseur pour exécuter des commandes SQL
18. cursor = conn.cursor()
19. # Créer la table access si elle n'existe pas, avec deux colonnes: id et date
20. cursor.execute("CREATE TABLE IF NOT EXISTS access (id INTEGER PRIMARY KEY, date
TEXT)")
21. # Valider les changements
22. conn.commit()
23. # Fermer la connexion
24. conn.close()
25.
26. # Définir la fonction qui insère la date du dernier accès dans la base de données
27. def insert_date(date):
28. # Se connecter à la base de données
29. conn = sqlite3.connect(db_file)
30. # Créer un curseur pour exécuter des commandes SQL
31. cursor = conn.cursor()
32. # Insérer la date dans la table access, en laissant l'id se générer automatiquement
33. cursor.execute("INSERT INTO access (date) VALUES (?)", (date,))
34. # Valider les changements
35. conn.commit()
36. # Fermer la connexion
37. conn.close()
38.
39. # Définir la fonction qui récupère le contenu de la table access
40. def get_table_content():
41. # Se connecter à la base de données
299
42. conn = sqlite3.connect(db_file)
43. # Créer un curseur pour exécuter des commandes SQL
44. cursor = conn.cursor()
45. # Sélectionner toutes les lignes de la table access
46. cursor.execute("SELECT * FROM access")
47. # Récupérer le résultat sous forme de liste de tuples
48. result = cursor.fetchall()
49. # Fermer la connexion
50. conn.close()
51. # Retourner le résultat
52. return result
53.
54. # Définir la route principale
55. @app.route("/")
56. def index():
57. # Importer le module datetime
58. from datetime import datetime
59.
60. # Importer le module locale, qui permet de gérer les paramètres régionaux
61. import locale
62.
63. # Définir le paramètre régional à "fr_CA" pour le français du Canada
64. locale.setlocale(locale.LC_TIME, "fr_CA")
65.
66. # Obtenir la date et l'heure
67. now = datetime.now()
68.
69. # Formater la date et l'heure en français avec la méthode strftime
70. date = now.strftime("%A %d %B %Y")
71. heure = now.strftime("%H:%M:%S")
72.
73. # Créer la table de la base de données si elle n'existe pas
74. create_table()
75.
76. # Insérer la date du dernier accès dans la base de données
77. insert_date(date)
78.
79. # Récupérer le contenu de la table access
80. content = get_table_content()
81.
82. # Créer une variable pour stocker le code HTML du tableau
83. table = ""
84.
85. # Parcourir le contenu de la table
86. for row in content:
87. # Ajouter une ligne au tableau avec les valeurs de l'id et de la date
88. table += f"<tr><td>{row[0]}</td><td>{row[1]}</td></tr>"
89.
90. # Retourner une page web avec la date et l'heure et le tableau
91. return f"""
92. <html>
93. <head>
94. <title>Date et heure</title>
95. </head>
96. <body>
97. <h1>Bonjour, voici la date et l'heure:</h1>
98. <p>Date: {date}</p>
99. <p>Heure: {heure}</p>
100. <h2>Voici le contenu de la table access:</h2>
101. <table border="1">
102. <tr><th>Id</th><th>Date</th></tr>
103. {table}
104. </table>
105. </body>
106. </html>
107. """
108.
109. # Lancer l'application Flask
110. if __name__ == "__main__":
111. app.run()
300
14 Développement d’applications
WebSocket asynchrones
Les applications Web conventionnelles utilisent le protocole HTTP. Le
protocole HTTP est essentiellement asymétrique : une application-client
telle qu’un navigateur émet des requêtes et le serveur (par exemple Flask)
répond. Il n’est pas possible pour le serveur d’initier une communication
vers le client. Certains types d’applications sont donc plus difficiles à
concevoir. Par exemple, si nous souhaitons concevoir un jeu vidéo
multijoueur avec le protocole http, tel qu’un jeu d’échec, nous pourrions
avoir un serveur, et deux navigateurs branchés au serveur. Quand un des
joueurs déplace une pièce au sein de son navigateur, le navigateur peut en
informer le serveur par l’entremise d’une requête http. Mais comment en
informer le second navigateur ? Une solution consiste à faire en sorte que
les navigateurs font des requêtes à intervalles réguliers au serveur. Une
meilleure solution consiste en l’utilisation d’un autre protocole, le protocole
WebSocket.
14.1 WebSocket
WebSocket est un protocole réseau qui permet de créer des canaux de
communication bidirectionnels entre les navigateurs et les serveurs web. La
plupart des navigateurs supportent WebSocket, bien que la norme soit
relativement récente (2011). Il permet la notification au client d’un
changement d’état du serveur, sans que ce dernier ait à effectuer une
requête.
WebSocket s’appuie sur TCP ce qui signifie qu’il requiert une notion de
connexion. Cette exigence introduit une certaine complexité au sein du
protocole, et peut limiter la performance par rapport à une approche sur
301
UDP. En revanche, en utilisant TCP, le protocole WebSocket simplifie le
travail du programmeur puisque les données arrivent en séquence et il n’y
a normalement pas de pertes de données.
1. import asyncio
2.
3. async def my_coroutine():
4. print("Hello, world!")
5.
6. async def main():
7. await my_coroutine()
8.
9. asyncio.run(main())
Dans cet exemple, la fonction my_coroutine() est une coroutine qui
affiche Hello, world!. La fonction main() appelle la coroutine en
utilisant les mots-clés async/await. Enfin, la
fonction asyncio.run() est utilisée pour exécuter la coroutine.
302
utilise asyncio.create_task() pour exécuter deux coroutines en
parallèle :
1. import asyncio
2.
3. async def coroutine1():
4. print("Coroutine 1 débutée")
5. await asyncio.sleep(2)
6. print("Coroutine 1 terminée")
7.
8. async def coroutine2():
9. print("Coroutine 2 débutée")
10. await asyncio.sleep(1)
11. print("Coroutine 2 terminée")
12.
13. async def main():
14. task1 = asyncio.create_task(coroutine1())
15. task2 = asyncio.create_task(coroutine2())
16.
17. await task1
18. await task2
19.
20. asyncio.run(main())
21.
La méthode asyncio.gather() offre une autre approche pour exécuter
plusieurs coroutines en parallèle. Elle prend en entrée une liste de
coroutines et les exécute simultanément. Voici comment vous pouvez
réécrire l’exemple précédent en utilisant asyncio.gather() :
1. import asyncio
2.
3. async def coroutine1():
4. print("Coroutine 1 débutée")
5. await asyncio.sleep(2)
6. print("Coroutine 1 terminée")
7.
8. async def coroutine2():
9. print("Coroutine 2 débutée")
10. await asyncio.sleep(1)
11. print("Coroutine 2 terminée")
12.
13. async def main():
14. await asyncio.gather(coroutine1(), coroutine2())
15.
16. asyncio.run(main())
Pour illustrer l’utilité de ce concept, supposons que vous souhaitiez charger
deux pages Web en même temps. Il est possible de cette manière d’aller
plus vite. Le code Python suivant utilise les mots-clés async/await et le
module aiohttp pour charger deux pages Web en même temps. La
303
fonction charge le module aiohttp pour récupérer le contenu d’une page
Web donnée. La fonction main crée deux tâches en utilisant la
fonction asyncio.create_task() pour charger les pages Web de
Google et de l’Université du Québec à Montréal (UQAM) en parallèle. La
fonction asyncio.gather() est utilisée pour exécuter les deux tâches en
parallèle et renvoyer les résultats. Enfin, la fonction main affiche la
longueur du contenu de chaque page Web. Lorsque vous exécutez ce code,
vous verrez que les deux pages Web sont chargées en même temps, ce qui
permet d’accélérer le temps de chargement total.
1. import aiohttp
2. import asyncio
3.
4. async def charge(url):
5. print("charge ",url)
6. async with aiohttp.ClientSession() as session:
7. async with session.get(url) as response:
8. r = await response.text()
9. print("chargement de l'URL ",url," terminé")
10. return r
11.
12. async def main():
13. task1 = asyncio.create_task(
14. charge("https://www.google.com/"))
15.
16. task2 = asyncio.create_task(
17. charge("https://www.uqam.ca/"))
18. await asyncio.gather(task1, task2)
19. print(len(task1.result()))
20. print(len(task2.result()))
21.
22. if __name__ == '__main__':
23. asyncio.run(main())
Exercice. Écrivez programme Python qui charge deux fichiers
(fichier1.txt et fichier2.txt) en même temps.
304
CodePython/chapitre15/Exercice1.py
1. import websockets
2. async def client():
3. async with websockets.connect('ws://localhost:8080') as websocket:
4. while True:
5. response = await websocket.recv()
6. await websocket.send('allo!')
CodePython/chapitre15/benchmark/client.py
1. import websockets
2. import time
3.
4. async def client1():
5. async with websockets.connect('ws://localhost:8080') as websocket:
6. round_trips = 0
7. start = time.time_ns()
8. await websocket.send('allo')
9. while True:
10. response = await websocket.recv()
11. round_trips += 1
12. if(round_trips % 65536 == 0):
13. end = time.time_ns()
14. duration = (end - start)/1000000000
15. print("rate: ",round_trips/duration," round trips per second")
16. await websocket.send('allo!')
17.
18. async def client2():
19. async with websockets.connect('ws://localhost:8080') as websocket:
20. while True:
21. response = await websocket.recv()
22. await websocket.send('allo!')
306
La fonction client2() est également définie comme une fonction
asynchrone qui se connecte à un serveur WebSocket en utilisant
l’adresse ws://localhost:8080. Une fois la connexion établie, la
fonction attend une réponse du serveur et envoie le message allo! au
serveur.
1. import asyncio
2. async def main():
3. task1 = asyncio.create_task(client1())
4. task2 = asyncio.create_task(client2())
5. await asyncio.gather(task1, task2)
6.
7. asyncio.run(main())
La fonction main() est définie comme une fonction asynchrone qui crée
deux tâches asynchrones en utilisant la
fonction asyncio.create_task(). La première tâche est créée en
appelant la fonction client1(), et la deuxième tâche est créée en appelant
la fonction client2(). Les deux tâches sont ensuite exécutées
simultanément en utilisant la fonction asyncio.gather(). La
fonction asyncio.run() est utilisée pour exécuter la fonction main(). La
fonction main() peut être combinée avec les fonctions client1() et
client2() dans un seul fichier nommé client.py.
Il reste à écrire le serveur qui exploite le module sanic à cette fin. Pour
l’installer avec pip :
CodePython/chapitre15/benchmark/server.py
1. import sanic
2.
3. app = sanic.Sanic(__name__)
4.
307
5. connected = set()
6.
7. @app.websocket('/')
8. async def sendToOthers(request, ws):
9. connected.add(ws)
10. try:
11. while True:
12. message = await ws.recv()
13. for client in connected:
14. if client is not ws:
15. await client.send(message)
16. finally:
17. connected.remove(ws)
18.
19. if __name__ == '__main__':
20. app.run(host='0.0.0.0', port=8080)
21.
La fonction sendToOthers() est définie comme une fonction asynchrone
qui est appelée chaque fois qu’un client se connecte au serveur WebSocket.
La fonction ajoute le client à l’ensemble connected et commence à
écouter les messages entrants. Si un message est reçu, la fonction envoie le
message à tous les clients connectés, sauf à celui qui a envoyé le message.
La fonction app.run() est utilisée pour exécuter le serveur WebSocket sur
l’adresse IP 0.0.0.0 et le port 8080.
Pour tester ce code, vous devez lancer le script Python server.py. Puis,
sans l’interrompre, vous pouvez lancer le script client.py. Naturellement,
il faut que le port 8080 soit disponible sur votre machine. Vous pouvez
changer, dans le script du client et dans le script du serveur le port 8080
pour un autre port comme par exemple 8087. Après un long moment, le
temps nécessaire pour faire 65536 boucles, le client devrait afficher une
indication de la vitesse. Vous pourriez, par exemple, voir ce message qui
indique que votre machine peut effectuer plus de 3000 boucles complètes
du client 1 au client 2 :
Vous pouvez mettre fin au serveur et au client en tapant ctrl-c alors que
vous êtes dans la console.
Exercice. Écrivez un serveur Web qui affiche une page Web changeant de
couleur régulièrement selon les indications du serveur. La nouvelle couleur
doit être communiquée par le serveur.
309
CodePython/chapitre15/Exercice2.py
1. # Importer le module sanic, qui est un framework web rapide et asynchrone
2. import sanic
3.
4. # Importer le module random, qui permet de générer des nombres aléatoires
5. import random
6.
7. # Importer le module asyncio, qui nous permet de faire des pauses
8. import asyncio
9.
10. # Créer une instance de l'application sanic
11. app = sanic.Sanic(__name__)
12.
13. # Définir une liste de couleurs possibles
14. couleurs = ["red", "green", "blue", "yellow", "pink", "orange"]
15.
16. # Définir une fonction asynchrone qui envoie une couleur aléatoire au client via websocket
17. async def envoyer_couleur(ws):
18. # Choisir une couleur aléatoire dans la liste
19. couleur = random.choice(couleurs)
20. # Envoyer la couleur au client
21. await ws.send(couleur)
22.
23. # Définir une route qui renvoie une page HTML avec un script qui ouvre un websocket
24. @app.route("/")
25. async def index(request):
26. return sanic.response.html("""
27. <html>
28. <head>
29. <title>Changer la couleur de la page</title>
30. </head>
31. <body>
32. <h1>Changer la couleur de la page</h1>
33. <script>
34. // Créer un objet websocket qui se connecte au serveur
35. var ws = new WebSocket("ws://localhost:8000/ws");
36. // Définir une fonction qui change la couleur de fond de la page
37. var changer_couleur = function(couleur) {
38. document.body.style.backgroundColor = couleur;
39. }
40. // Quand le websocket reçoit un message du serveur
41. ws.onmessage = function(event) {
42. // Récupérer la couleur envoyée par le serveur
43. var couleur = event.data;
44. // Appeler la fonction qui change la couleur de la page
45. changer_couleur(couleur);
46. }
47. </script>
48. </body>
49. </html>
50. """)
51.
52. # Définir une route websocket qui communique avec le client
53. @app.websocket("/ws")
54. async def websocket(request, ws):
55. # Tant que le websocket est ouvert
56. while True:
57. # Appeler la fonction qui envoie une couleur aléatoire au client
58. await envoyer_couleur(ws)
59. # Attendre une seconde
60. await asyncio.sleep(1)
61.
62. # Lancer l'application sanic
63. if __name__ == "__main__":
64. app.run(host="localhost", port=8000)
310
1. Un outil de gestion de projet qui utilise WebSocket pour fournir des
mises à jour en temps réel aux utilisateurs. Dès qu'un utilisateur ajoute
un élément à un projet, tous les utilisateurs reçoivent une notification.
2. Un outil de clavardage en ligne qui permet aux utilisateurs de discuter,
d'échanger des messages, etc.
3. Un outil d'édition collaborative de documents. Dès qu'un des
utilisateurs a modifié un texte, tous les utilisateurs voient le texte
modifié.
Nous allons plutôt concevoir une application de dessin collaboratif. Nous
voulons créer un tableau blanc commun et permettre à tous les utilisateurs de le
modifier, en temps réel. Afin de garder l’application simple, une seule couleur
(bleu) sera permise et une seule grille de 8 par 8. La Figure 16 donne un exemple
du résultat souhaité.
311
Ce code utilise le framework Sanic pour créer un serveur web. Il définit une
application Sanic nommée app et crée une route pour l’URL racine /.
Lorsque l’URL racine est demandée, la fonction index() est appelée.
Cette fonction renvoie le contenu du fichier index.html. La dernière
ligne app.run() lance l’application Sanic.
Prenez en note que la variable globale state peut être réassignée au sein
de la fonction sendToOthers(). Il peut y avoir plusieurs instances de la
fonction sendToOthers() en cours d’exécution, mais il ne peut y avoir
qu’une seule variable state.
Il ne reste plus qu’à combiner nos deux serveurs dans un seul script Python.
Heureusement, ce n’est pas difficile :
CodePython/chapitre15/boxes/server.py
313
élément utilisé pour indiquer l’état de la connexion (ayant comme
l’identifiant message). Le fichier HTML aura donc cette forme :
1. <body>
2. <div class="wrapper">
3. <div class="box"></div>
4. <div class="box"></div>
5. …
6. <div class="box"></div>
7. <div class="box"></div>
8. </div>
9. <div id="message"></div>
10. </body>
Au lieu de créer autant de HTML, un script JavaScript pourrait remplir le
corps du document, mais nous voulons garder l’exemple aussi simple que
possible.
1. body {
2. background: #d3d3d3;
3. }
Cet extrait de code définit la couleur de fond de l’élément body en gris clair.
La propriété background définit la couleur de fond d’un élément HTML.
La valeur de la propriété background peut être spécifiée de différentes
manières, telles que l’utilisation d’un nom de couleur, d’une valeur
hexadécimale, d’une valeur RGB ou d’une valeur HSL 1. Nous avons ici
choisi le gris clair.
1. #message {
2. margin-left: auto;
3. margin-right: auto;
4. width: 30vw;
5. padding: 2em;
6. }
Ce code CSS définit les propriétés de style pour un élément HTML ayant
l’identifiant message. Voici ce que chaque propriété signifie :
314
automatiquement ajustées pour centrer l’élément horizontalement dans
son conteneur parent.
• width: 30vw; : Cette propriété définit la largeur de l’élément
message à 30% de la largeur de son conteneur parent. La valeur vw
signifie viewport width, ce qui signifie que la largeur de l’élément
sera calculée en fonction de la largeur de l’écran de l’utilisateur.
• padding: 2em; : Cette propriété définit l’espacement intérieur de
l’élément message à 2 fois la taille de la police actuelle. Cela signifie
que le texte à l’intérieur de l’élément sera espacé de 2 fois la taille de la
police.
Le CSS suivant met en forme les éléments de la grille (box),:
1. .box {
2. flex-grow: 1;
3. width: 12.5%;
4. border: 1px solid black;
5. background-color: white;
6. }
Ce code CSS définit les propriétés de style pour les éléments HTML de
classe box. Voici ce que chaque propriété signifie:
315
Il reste maintenant à mettre en forme l’élément contenant la grille (classe
wrapper) :
1. .wrapper {
2. margin-left: auto;
3. margin-right: auto;
4. display: flex;
5. width: 30vw;
6. height: 30vw;
7. gap: 1px;
8. flex-wrap: wrap;
9. }
Voici ce que chaque propriété signifie:
316
Ce code JavaScript crée un objet WebSocket nommé socket qui se
connecte à un serveur WebSocket en utilisant l’URL
"ws://"+window.location.host+"/ws". Voici ce que chaque partie de
l’URL signifie:
1. function getstatus() {
2. let boxes = document.querySelectorAll(".box");
3. let data = Array.from(boxes, function (box) {
4. return box.style.background[0];
5. }).toString();
6. return data;
7. }
Le code JavaScript que vous avez fourni définit une fonction nommée
getstatus() qui renvoie une chaîne de caractères représentant les
couleurs de fond des éléments HTML ayant la classe box. Voici ce que
chaque partie de la fonction signifie :
318
Il ne nous reste plus qu’à gérer la réception des messages du serveur. Dans
de tels cas, il faut rapidement mettre à jour les couleurs de la grille.
CodePython/chapitre15/boxes/index.html
1. <html>
2. <head>
3. <meta charset="utf-8">
4. <script>
5. let socket = new WebSocket("ws://"+window.location.host+"/ws");
6.
7. function getstatus() {
8. let boxes = document.querySelectorAll(".box");
9. let data = Array.from(boxes, function (box) {
10. return box.style.background[0];
11. }).toString();
12. return data;
13. }
14.
15. socket.addEventListener("open", (event) => {
16. document.getElementById("message").innerHTML = "ok";
17. let boxes = document.querySelectorAll(".box");
18.
19. Array.from(boxes, function (box) {
20. box.addEventListener("click", function (event) {
21. if (event.target.style.background == "blue") event.target.style.background =
"white";
22. else event.target.style.background = "blue";
23. let data = getstatus();
24. socket.send(data);
25. document.getElementById("message").innerHTML = "transmis";
26.
27. });
319
28. });
29. });
30.
31. socket.addEventListener("message", (event) => {
32. document.getElementById("message").innerHTML = "reçu";
33. let boxes = document.querySelectorAll(".box");
34. let data = event.data.split(",");
35. Array.from(boxes, function (box, index) {
36. if (data[index] == "b") box.style.background = "blue";
37. else box.style.background = "white";
38. });
39. });
40.
41. socket.addEventListener("error", (event) => {
42. document.getElementById("message").innerHTML = "error" + event.data;
43. });
44.
45. socket.addEventListener("close", (event) => {
46. document.getElementById("message").innerHTML = "fermé" + event.data;
47. });
48.
49. </script>
50. <style>
51. body {
52. background: #d3d3d3;
53. }
54. #message {
55. margin-left: auto;
56. margin-right: auto;
57. width: 30vw;
58. padding: 2em;
59. }
60.
61. .wrapper {
62. margin-left: auto;
63. margin-right: auto;
64. display: flex;
65. width: 30vw;
66. height: 30vw;
67. gap: 1px;
68. flex-wrap: wrap;
69. }
70.
71. .box {
72. flex-grow: 1;
73. width: 12.5%;
74. border: 1px solid black;
75. background-color: white;
76. }
77. </style>
78. </head>
79. <body>
80. <div class="wrapper">
81. <div class="box"></div>
82. <div class="box"></div>
83. …
84. <div class="box"></div>
85. <div class="box"></div>
86. <div class="box"></div>
87. <div class="box"></div>
88. </div>
89. <div id="message"></div>
90. </body>
91. </html>
14.5 Conclusion
La technologie WebSocket vous permet de concevoir des applications web
facilitant la collaboration en temps réel entre les utilisateurs. Comme vous
avez pu le constater, il n’est pas beaucoup plus difficile de concevoir une
application WebSocket qu’une application Web conventionnelle en
Python.
321