T2.2 Programmation Orientée Objet : TP Morpion

1. Introduction

Le but de ce TP est ici de modéliser le jeu du Morpion, en utilisant le paradigme de la programmation orientée objet, et de mettre en pratique les différents éléments vus dans le cours précédent.

Nous ferons une version sans interface graphique.

Rappel des règles du morpion

Deux joueurs s'affrontent. Ils doivent remplir chacun à leur tour une case de la grille avec le symbole qui leur est attribué : O ou X. Le gagnant est celui qui arrive à aligner trois symboles identiques, horizontalement, verticalement ou en diagonale. Il est coutume de laisser le joueur jouant X effectuer le premier coup de la partie.

Une partie gagnée par le joueur X :

Une partie nulle :

En raison du nombre de combinaisons limité, l'analyse complète du jeu est facile à réaliser : si les deux joueurs jouent chacun de manière optimale, la partie doit toujours se terminer par un match nul.

Visualisez l'extrait vidéo ci-dessous du film Wargames, dans lequel une IA va jouer contre elle même au morpion, pour arriver à la conclusion qu'il n'y a pas de possibilité de gagner et ainsi sauver le monde de la 3e guerre mondiale ...

2. Implémentation, création d'une classe Morpion

Nous allons donc ici créer une classe Morpion, qui contiendra toutes les méthodes et tous les attributs nécessaires à la réalisation du jeu.

Cette classe contiendra les méthodes suivantes (en dehors de la fonction d'initialisation) :

  1. Fonction jouer, qui permet à un joueur de jouer son tour. Cette fonction prend 2 arguments en entrée :
    • le joueur
    • la case jouée.
  2. Fonction afficher_plateau, qui comme son nom l'indique affichera le plateau du jeu (qui ne prend aucun argument en entrée).
  3. Fonction test_fin_jeu, qui teste si un joueur est vainqueur, ou s'il n'y a plus de mouvement possible (qui prend en argument le joueur en entrée).
  4. Fonction jeu, qui contiendra le code qui assure le bon déroulement du jeu en lui-même (qui ne prend aucun argument en entrée).
1. Créer la classe Morpion

Coding Exercise: Créez votre classe morpion

(n'oubliez pas la fonction d'initialisation **__init__** )


**Rappel:** les fonctions qui n'ont pas d'argument en entrée ne doivent pas s'écrire () mais (self)

Créez simplement les fonctions avec les bons arguments, et dans le corps de la fonction mettez seulement l'instruction pass, nous allons les remplir après.

Quand vous allez exécuter ce code, il ne se passera rien, ce qui est normal. Mais si lors de l'exécution, votre programme ne passe pas les tests, c'est qu'il y a des erreurs de syntaxe, et vous devez les corriger.

3. Implémentation, création des différentes fonctions de la classe Morpion

Maintenant, nous allons devoir écrire le contenu de chacune de ces fonctions.

2. Créer la Fonction __init__

Dans la fonction __init__, le plateau de jeu est initialisé avec des “-”, le joueur 1 est initialisé avec une croix X et le joueur 2 est initialisé avec un rond O.

Cela signifie que partout sur le plateau ou il y a des tirets, les joueurs n'ont pas encore choisi ces emplacements.

Partout ou il y a des croix cela signifie que le joueur 1 a joué et enfin, partout où il y a des ronds, cela signifie que le joueur 2 a joué.

Nous allons donc déclarer 3 attributs :

  • un attribut nommé plateau, qui contiendra le plateau du jeu. Comme vu ci-dessus, il sera rempli de tirets. Donc ici, nous initialiserons notre plateau avec des '-', de façon à afficher un plateau de 3*3 tirets .
    (Pensez à une liste de listes, avec une liste comprenant 3 listes qui contiennent chacun les 3 tirets).
    Voici ci-dessous le plateau au début du jeu :
                   ---
                   ---
                   ---
  • un attribut J1, qui contiendra un 'X'
  • un attribut J2, qui contiendra un 'O'

Avant d'implementer cette fonction , recopier et coller ci-dessous le code de la classe morpion

Coding Exercise: Créez le constructeur
complétez la fonction __init__ Aide 1
3. Créer la fonction jouer

Avant d'appeler la fonction jouer, on commence (dans le programme principal) par lire au clavier 2 nombres (séparés par une virgule) qui sont les n° de ligne, de colonne et de la position où le joueur courant souhaite jouer (1 à 3 en abscisse, puis 1 à 3 en ordonnée). Le joueur entrera par exemple 2,2 pour jouer la case au centre du plateau.

(attention, s'il joue la case 1,1, en python il faudra transcrire : ligne = 0, colonne = 0).

La méthode jouer prend deux arguments :

joueur : str : "X" ou "O"

case : une chaine lue au clavier telle que décrit ci-dessus

Le signe (X ou O) du joueur courant est par la suite écrit sur le plateau à la position précédemment indiquée.

Danc cette fonction vous devrez donc affecter la marque du joueur à la position x et y du plateau (attention aux indices). joueur et case_jouee sont 2 arguments de la fonction. Il faut décoder la case, par exemple "1,1" doit être compris comme ligne=0 et colonne=0.

Ecrivez bien les spécification de la fonction.

Implémentez, ci-dessous, cette fonction (qui prend en argument le joueur en entrée 'X' ou 'O')


(Vous recopierez la classe définie précédemment dans cet exercice (classe + fonction init + les autres fonctions non complétée sauf la fonction jouer).

Coding Exercise: Implémenter la méthode jouer
complétez la fonction jouer Aide2 Aide1
4. Créer la Fonction afficher_plateau

La fonction afficher_plateau parcourt le plateau et l’affiche en veillant à bien séparer chaque signe par deux barres “|” pour que cela soit plus lisible

Voilà comment le plateau sera affiché au début du jeu :

|-|-|-|
|-|-|-|
|-|-|-|
Comme le plateau est stocké dans une liste de listes, il faudra utiliser 2 boucles imbriquées. Dans le premier exercice vous avez un exemple, qui donne un résultat assez proche de ce qui est demandé ici. Inspirez vous de cet exemple pour implementer la fonction afficher_plateau

N'oubliez pas de recopier la classe et les fonctions déjà définies.
Coding Exercise: Implémenter afficher_plateau
Complétez la fonction afficher_plateau Aide
5. Créer la Fonction test_fin_jeu

test_fin_jeu va vérifier si l’un des deux joueurs a gagné ou s’il n’y a pas égalité.

Pour cela elle va vérifier qu’il n’y a pas 3 fois le même signe aligné ni en horizontal, ni en vertical, ni en diagonal.

Elle va également vérifier qu'il reste de la place pour jouer en vérifiant qu’il reste des signes “-” sur le plateau.

Il y aura 3 cas possibles :

  • Soit le joueur passé en argument aura gagné, et dans ce cas là cette fonction renverra ce joueur.
  • Soit il n'y a plus de place sur le plateau, et dans ce cas on renverra True
  • Soit le jeu n'est pas encore fini, et dans ce cas on retournera False

Ces valeurs de retour seront utilisées dans la fonction principale, jeu, que nous implémenterons en dernier.

Cas où un joueur gagne :

  • Un joueur gagne s'il a aligné 3 fois son signe en horizontal
  • Un joueur gagne s'il a aligné 3 fois son signe en vertical
  • Un joueur gagne s'il a aligné 3 fois son signe en diagonale Pour tester ce dernier cas, vous pourrez si vous voulez faire 2 tests : 1 pour la première diagonale, et un pour la seconde (attention aux indices de vos boucles imbriquées).

A vous de jouer ...
Pour la mise au point, n'hésitez pas à insérer des print dans votre fonction pour voir les résultats intermédiaires, vous les enlèverez ensuite....

Coding Exercise: Implémenter la méthode test_fin_jeu
Comme pour les autres exercices, recopiez votre classe, puis ajoutez les instructions pour la fonction test_fin_jeu(). Aide Diagonales Aide Diagonales++ Aide lignes Aide colonnes Aide jeu terminé ? Aide rien ne marche
6. Créer la Fonction jeu

La fonction jeu va lancer le jeu en affichant premièrement le tableau, puis en appelant les fonctions définies plus haut.

Tant que l'un des deux joueurs n’aura pas gagné, elle va les faire jouer chacun leur tour (vous pouvez commencer par faire jouer J1, puis ensuite J2, et ainsi que suite), puis afficher le nouveau plateau et tester que le jeu n’est pas fini.

Vous pourrez utiliser une boucle infinie, en utilisant un booléen qui changera de valeur quand la partie sera terminée.

Si le jeu est fini on sort de la fonction en renvoyant le signe du joueur gagnant (X ou O) ou en renvoyant égalité si c’est une égalité.

Coding Exercise: Implémenter la méthode jeu qui lance le jeu
Ajoutez les instructions pour la fonction jeu(). Aide

Améliorations possibles

  • Quand le joueur entre son abscisse et son ordonnée, il faut vérifier que:
    • L'abscisse et l'ordonnée entrés par le joueur sont bien des nombres compris entre 1 et 3, sans quoi il faut leur redemander.
    • Qu'il n'y a pas déjà de X ou de O à l'abscisse et l'ordonnée entrés par le joueur, sans quoi il faut leur redemander.
  • Peut être mettre un menu, puis un choix de niveau de difficulté qui pourrait mettre un temps limite pour répondre.
  • Utiliser la librairie Pygame pour améliorer l'aspect graphique (demander au professeur de vérifier votre travail avant d'attaquer la partie graphique avec Pygame).

Interface graphique

4. Partie graphique avec PyGame

Le but est ici de retranscrire notre travail effectué ici, pour l'afficher proprement avec Pygame.

Nous améliorerons également l'ergonomie du logiciel : ici plus rien à taper au clavier, on pourra directement cliquer sur la case choisie avec la souris pour choisir où mettre son X ou son O

4.1 Changements à apporter dans la structure du programme

  • La première chose à faire sera de copier votre code (fonctionnel) dans edupython, ce sera votre base de départ. Nous ne continuons pas à coder dans Google Coolab car la librairie Pygame n'est pas compatible (l'environnement Python est sur une machine distante, et Pygame a besin d'accéder à la carte graphique de votre ordinateur, ce qui est impossible).
  • Nous allons également faire des changements dans la structure du programme. Nous allons garder notre classe Morpion, mais nous allons créer une 2e classe, intitulée Grille, qui s'occupera uniquement de gérer l'affichage. Et nous créerons une instance de cette classe à l'initilisation de la classe Morpion.
  • Dernière chose, n'oubliez pas d'importer la librairire pygame (évidemment) mais également la librairie sys.

Commençons par le commencement ...

4.2 Création de la fenêtre et de la grille

Pour commencer, nous allons donc créer une nouvelle classe nommée Grille

La fonction d'initialisation prendra un argument en entrée (nommé ecran). Nous verrons son utilité plus tard.

Cettte fonction comprendra :

  • Un attribut ecran que l'on initialisera avec la valeur ecran passée en paramètre.
  • Un attribut lignes qui sera une liste qui contient 4 tuples, avec des coordonnées de points pour tracer les 4 lignes de la grille. Utilisez les valeurs suivantes :[( (200,0),(200,600)), ((400,0),(400,600)), ((0,200),(600,200)), ((0,400),(600,400))]
  • Un attribut grille qui sera un tableau de tableau, qui correspond à notre plateau créé ci-dessus. On l'initialisera avec les valeurs None cette fois-ci (et non plus avec les tirets)

On ajoutera également une fonction afficher pour afficher la grille (qui ne prendra pas d'arguments).

On parcourera juste notre attribut lignes créé ci-dessus, pour tracer des lignes en utilisant la méthode draw.line de pygame :

for ligne in self.lignes :

            pygame.draw.line(self.ecran,(0,0,0),ligne[0],ligne[1],2)
Maintenant, intéressons nous à notre classe Morpion

Nous allons modifier la fonction d'initialisation, en créant la fenêtre Pygame (rappelez vous son fonctionnement)

Dans cette fonction (sans argument ), ajouter :

  • Un attribut ecran, qui créera la fenêtre Pygame:
pygame.display.set_mode((600,600))
  • Un attribut grille, qui sera une instance de la classe Grille (avec en paramètre l'attribut ecran)

Nous allons également modifier la fonction jeu;

Rappelez vous, Pygame fonctionne avec des événements. Il faut donc rajouter une boucle For sur ces événements, et prévoir la sortie :

for event in pygame.event.get():

                if event.type == pygame.QUIT:
                    sys.exit()
Rajouter également ces 2 instructions à la suite de la boucle for, pour ajouter une couleur et rafraichir l'affichage :

self.ecran.fill((240,240,240))
pygame.display.flip()
Enfin, à la suite de ces 2 lignes, nous allons appeler la méthode afficher de l'attribut grille (qui est une instance de la classe Grille, dans laquelle nous avons une méthode afficher)

4.3 Création et affichage des X/O

Nous allons utiliser la souris pour placer nos X et nos O Pour cela, nous utiliserons la méthode mouse.getpos() de pygame. Cette fonction nous renvoie les coordonnées de l'endroit où l'on clique. Or, nos cases font 200 pixels de large et 200 pixels de long.

Comment faire pour convertir facilement ces coordonnées sous forme d'indices qui seront plus facilement utilisables ? Nous allons utiliser la division entière par 200 : nous aurons alors des indices de position.

Rajoutez ces lignes dans la boucle principale de la fonction jeu (nous testons si on a cliqué avec les bouton gauche de la souris):

if event.type == pygame.MOUSEBUTTONDOWN and pygame.mouse.get_pressed()[0]:                 
                    position = pygame.mouse.get_pos() 
                    position_x ,position_y = position[0]//200 ,position[1]//200 

                    if self.compteur % 2 == 0 :
                                  self.grille.fixer_la_valeur(position_x, position_y, self.J1)
                    else:
                                  self.grille.fixer_la_valeur(position_x, position_y, self.J2)
Si vous regardez les dernières lignes, nous testons la parité d'un attribut compteur. Quand il est pair, c'est J1 qui joue, sinon c'est l'inverse.

Nous allons définir la méthode fixer_la_valeur de notre attribut grille juste après.

N'oubliez pas de rajouter également dans la fonction d'initialisation de la classe Morpion l'attribut compteur, qui sera initialisé à 0.

Retour à la classe Grille :

  • Rajouter une nouvelle méthode : fixer_la_valeur.

Cette méthode sera utilisée pour modifier une valeur de la grille, avec X ou O. Elle prendra 3 paramètres : x,y, et la valeur

Testez si la valeur de la grille d'abscisse x et d'ordonnée y est égale à None. Si c'est le cas, on modifie cette valeur de la grille avec valeur

  • Rajoutez un attribut compteur_on, qui sera initialisé à False, qui va nous permettre de définir si le compteur est actif ou pas
    • Mettez le à True quand vous avez modifié une valeur dans fixer_le_valeur
    • Mettez le à False dans la boucle principale, en testant si le compteur est actif, à savoir juste après avoir testé la parité du compteur :
if self.compteur % 2 == 0 :#1
                    self.grille.fixer_la_valeur(position_x, position_y, self.J1)
else:
                    self.grille.fixer_la_valeur(position_x, position_y, self.J2)

if self.grille.compteur_on: #1
                    self.compteur += 1
                    self.grille.compteur_on = False
Enfin, dernière chose avant de tester :

Dans la méthode afficher de la classe Grille, nous affichons déjà les 4 lignes qui font la grille.

Mais il serait bien d'afficher les X et les O également, suivant les valeur de la grille ?

Pour cela, nous allons parcourir les éléments de la grille (double boucle), puis quand nous allons trouver un 'X', nous allons dessiner un X avec la méthode draw.line, et idem pour O avec la méthode draw.circle. Nous utiliserons les fonctions de dessins de Pygame.

Je vous épargne cette recherche fastidieuse qui n'est pas le but du TP ici, et je vous donne les instructions à rajouter :

for y in range(0,len(self.grille)):
            for x in range(0,len(self.grille)):


                    if self.grille[y][x] == 'X' :

                        pygame.draw.line(self.ecran, (0, 0, 0), (x * 200, y * 200), (200 + (x * 200), 200 + (y * 200)), 3) 
                        pygame.draw.line(self.ecran, (0, 0, 0), ((x * 200), 200 + (y * 200)), (200 + (x * 200), (y * 200)),
                                     3)

                    elif self.grille[y][x] == 'O' :

                        pygame.draw.circle(self.ecran, (0, 0, 0), (100 + (x * 200), 100 + (y * 200)), 100, 3)

4.4 Vainqueur et fin de partie

Nous y sommes presque. Il vous reste à réutiliser la méthode test_fin_jeu définie ci-dessus, qui nous permettait de savoir qui était vainqueur.

En effet, nous ne parcourons plus le plateau, mais la grille

Attention cependant si vous remplacez juste plateau par grille, cela risque de ne pas fonctionner et de générer le message d'erreur suivant :

TypeError: 'Grille' object does not support indexing

A vous de voir pourquoi il y a ce message d'erreur, et quelle modification, liée à la nature de l'attribut grille il faut effectuer

Un peu de ménage de code :

N'oubliez pas de supprimer certaines fonctions qui ne sont plus utiles, comme afficher_plateau et jouer de la classe Morpion qui ne sont plus utilisées.

BON JEU !!!

4.5 Aller plus loin

Pour ceux qui ont une version fonctionnelle, vous pouvez faire un menu de démarrage, avec la possiblité de rentrer des noms pour les joueurs, et de recommencer une partie.

Et également, pour ceux qui le souhaitent, programmer une IA qui pourrait jouer contre vous.

Mais ceci est une autre histoire ...

t1.2 révisions : les dictionnaires

Découverte

Nous souhaitons lire avec un code écrit en python des données dans un fichier csv. Nous allons avoir besoin d'un nouveau type de variable : les dictionnaires.

Vous connaissez déjà les listes. Par exemple lst=[1,2,4,1,0] est une liste de nombres, et on sait accéder aux valeur de chaque élément :

Example

Finalement, chaque élément de la liste est associé à un indice.

  • 1 est à l'indice 0
  • 2 à l'ndice 1
  • etc...

Dans un dictionnaire, les indices sont remplacés par des descripteurs aussi appelés les clés du dictionnaire. Voila comment pourrait être écrite la liste précédente sous forme d'un dictionnaire :

Example

Bon, ça ne sert pas à grand chose vu comme ça ! car ici on a utilisé des clés qui sont strictement identiques aux indices de la liste. Mais ça devient beaucoup plus intéressant si ces clés sont des mots décrivant le contenu :

Example
Un dictionnaire permet de décrire les valeurs

Exercice

Coding Exercise: afficher les valeurs des clés
Dans cet exercice, vous disposez d'un dictionnaire déjà créé. Faire afficher successivement le nom, le prenom et l'année de naissance.

Modifier un dictionnaire

Example
Un dictionnaire permet de décrire les valeurs

Nous venons de voir comment modifier la valeur d'une clé existante. Mais comme pour les listes, on peut bien sur aussi ajouter des éléments :

Example
Un dictionnaire permet de décrire les valeurs

Dans le cas d'une liste, les indices sont toujours les mêmes : 0, 1, 2 ...

Dans un dictionnaire en revanche, les descripteurs sont spécifiques, et on peut avoir besoin de les connaitre. On utilise pour cela la méthode keys() :

parcourir un dictionnaire

Rappel parcours de listes

Example
rappel : parcours de liste (avec les indices)

Parcours d'un dictionnaire avec keys

On peut parcourir de la même manière un dictionnaire, mais comme les indices sont remplacé par des clés, il faut itérer sur les clés. Pour cela on utilise la méthodé keys() qui nous donne la liste des clés.

Example
Un dictionnaire permet de décrire les valeurs

On peux également utiliser les méthodes values et items

Example: La méthode Values
Example: La méthode items
items renvoi un objet dict_items qui ressemble beaucoup à une liste de tuples.

Exercice

Coding Exercise: implementez et afficher un dictionnaire
Voici ci dessous un court extrait de la liste des 72 noms de scientifiques, gravés sur la tour Eiffel. Il s'agit d'une petit partie de ceux gravé sur la face Trocadéro.
Représentez l'information de ce tableau dans trois dictionnaire avec les descripteurs suivants :
  • numéro
  • nom
  • prenom
  • naissance
  • deces
  • metier
(vous n'incluerez pas le Titre dans les dictionnaires).
Le code, à partir de la ligne 6, vous est donné : on va regrouper les 3 dictionnaires dans une liste et afficher cette liste, d'abord dans un parcours de liste, puis d'un seul bloc.
FACE TROCADÉRO
No  Nom en abrégé inscrit sur la tour Eiffel Prénom Dates de naissance et de mort Métier(s) Titres de gloire
1 SEGUIN Marc 1786-1875 Mécanicien Constructeur de ponts suspendus - Inventeur de la chaudière tubulaire
2 LALANDE Joseph 1732-1807 Astronome Nombreux travaux d'astronomie et d'hydrologie
3 TRESCA Henri 1814-1885 Ingénieur et mécanicien Auteur du critère de Tresca (résistance des matériaux)

T2.1 Programmation orientée objet

Créer une classe pas à pas

créer une Classe

Un exemple pas à pas

Nous allons commencer par écrire une classe Personnage (qui sera dans un premier temps une coquille vide) et, à partir de cette classe créer 2 instances : bilbo et gollum.

Example: création d'une classe

Exécutez ce code. Observez le type des variables gollum et bilbo

Les classe (ou objets) sont des types personnalisés


Pour l'instant, notre classe ne sert à rien et nos instances d'objet ne peuvent rien faire. Comme il n'est pas possible de créer une classe totalement vide, nous avons utilisé l'instruction pass qui ne fait rien. Ensuite nous avons créé 2 instances de la classe Personnage : gollum et bilbo.

Le constructeur : la méthode __init__

La méthode __init__

Les attributs de l'objet doivent être définis dans la classe, à l'aide d'une méthode d'initialisation des attributs.

Une méthode particulière, nommée LE CONSTRUCTEUR, permet de définir les attribut dès l'instanciation d'un objet.

Cette méthode est définie dans le code source par la ligne :

def __init__ (self) :
Rappel : La méthode __init__ est automatiquement exécutée au moment de la création d'une instance. Le mot self est obligatoirement le premier argument d'une méthode.

Le mot self représente l'instance. Quand vous définissez une instance de classe (bilbo ou gollum) le nom de votre instance va remplacer le mot self.

Dans le code source, nous allons avoir :

class Personnage:
def __init__ (self):
    self.vie = 20
Ensuite lors de la création de l'instance gollum, python va créer un attribut vie de la variable gollum.

Cet attribut aura aura pour valeur de départ la valeur donnée à self.vie dans la méthode __init__

Il se passera exactement la même chose au moment de la création de l'instance bilbo, on aura automatiquement la création de l'attribut vie de la variable bilbo.

Exécutez ce code, et dans la console, faites afficher les valeurs de gollum.vie et bilbo.vie

Coding Exercise: Encapsuler les attributs
Ajoutez les instruction pour afficher les vies de Bilbo puis de Gollum.

Imaginons que nos 2 personnages n'aient pas au départ les mêmes points de vie ! Pour l'instant, impossible d'introduire cette contrainte (self.vie = 20)

Une méthode, comme une fonction, peut prendre des paramètres (des arguments).

Le passage de paramètres se fait au moment de la création de l'instance. Modifiez le code pour que le nombre de vies soit un paramètre dont la valeur sera fixée lors de l'instanciation.

Coding Exercise: Passer des arguments aux méthodes
Votre code doit :
  • Permettre de passer un argument nbVies
  • Crée une instance Personnage nommée bilbo initialisée avec 20 vies.
  • Créer une autre instance, gollum, avec 15 vies.
  • Afficher les valeurs de l'attribut vie pour bilbo
  • Afficher les valeurs de l'attribut vie pour gollum

Au moment de la création de l'instance gollum, on passe comme argument le nombre de vies (gollum = Personnage (15)).

Nous pouvons passer plusieurs arguments à la méthode __init__ (comme pour n'importe quelle fonction).

Coding Exercise: Passer 2 arguments
Votre code doit :
  • Permettre de passez deux paramètres au constructeur.
  • Utiliser les paramètres pour initialiser les attibuts vie et age.
  • Créer une instance, gollum, avec 20 vies et 127 ans.
  • Afficher ceci (en utilisant les attributs) : gollum a 127 ans et 20 vies

Améliorons en ajoutant d'autres méthodes

utiliser une classe avec plusieurs méthodes
Coding Exercise: plus de méthodes...
Votre code doit :
  • Ajouter une méthode perdVie qui modifie l'attibut vie (retire une vie)
  • Ajouter une méthode donneEtat qui renvoie la valeur de l'attibut vie
  • Créez 1 seul personnage, gollum, avec 20 vies. puis vous définirez
  • créer 1 variable etat1 égale à son nombre de vie.
  • Modifier le nombre de vie avec la méthode perdVie
  • créer 1 variable etat2 égale à son nombre de vie.

Vous avez sans doute remarqué que lors de "l'utilisation" des instances bilbo et gollum, nous avons uniquement utilisé des méthodes et nous n'avons plus directement utilisé des attributs (plus de "gollum.vie"). Il est important de savoir qu'en dehors de la classe l'utilisation des attributs est une mauvaise pratique en programmation orientée objet : les attributs doivent rester "à l'intérieur" de la classe, l'utilisateur de la classe ne doit pas les utiliser directement. Il peut les manipuler, mais uniquement par l'intermédiaire d'une méthode (la méthode self.perdVie() permet de manipuler l'attribut self.vie)

Ajouter une méthode puis utliser la classe
Coding Exercise
Nos personnages peuvent boire une potion qui leur ajoute un point de vie. Vous devez :
  • Modifiez la classe Personnage en ajoutant une méthode boirePotion.
  • créer une fonction simul(n) qui crée un personnage avec n vies, lui fait boire une potion, et renvoi le nombre de vie.
Passer des arguments à une méthode

Passer des arguments à nos méthodes

Selon le type d'attaque subit, le personnage peut perdre plus ou moins de points de vie. Pour tenir compte de cet élément, ajoutez un paramètre à la méthode perdVie.

Coding Exercise
Vous devez :
  • Modifier la méthode perdVie pour que le nombre de vies perdues soit un paramètre.
  • instancier gollum avec 15 vies
  • Lui faire perdre 2 vies avec la méthode perdVie
  • Faire afficher le nombre de vies avec la méthode donneEtat
CONTRAINTE : vous ne pouvez pas utiliser la notation pointée gollum.vie
De l'impératif dans les méthodes

Il est bien entendu possible d'utiliser la librairie random pour ajouter une part d'aléatoire dans la méthode perdVie.

Coding Exercise: Un peu de hasard ne nuira pas...
Modifiez le code précédent, de façon que perdVie(nbreDeVie) retire entre 1 et un paramère nbreDeVie vies au personnage attaqué. Vous devez :
  1. Modifier la class Personnage
  2. Completer le code de façon à créer un personnage ayant 100 vies puis qui subit 3 attaques consécutives, lui infligeant chacune 1 à 10 vies perdues...

Dans une méthode vous pouvez utiliser toutes les instructions que vous connaissez déjà en paradigme impératif. Modifiez de nouveau le code, cette fois la méthode perdVie doit être écrite de façon à ne jamais renvoyer un nombre de vie négatif. Si le nombre de vie est négatif, nombre de vie sera mis à 0.

Coding Exercise: plus mort que mort ?
Modifiez de nouveau le code, cette fois la méthode perdVie doit être écrite de façon à ne jamais renvoyer un nombre de vie négatif. Si le nombre de vie est négatif, nombre de vie sera mis à 0.. Vous devez :
  1. Modifier la class Personnage
  2. Completer le code de façon à créer un personnage ayant 100 vies puis qui subit des attaques consécutives, lui infligeant chacune 1 à 10 vies perdues, jusqu'à ce qu'il n'ai plus aucune vie. Le code affichera le nombre d'attaques subies.

T2.1 Effets de bord

Variables locales et variables globales

Portée des variables

Premier exemple :

Example: premier exemple
Avant d'exécutez le programme ci-dessus, interrogez vous sur la valeur de la variable i à l'issue de l’exécution, puis lancer le code et vérifiez

Comme vous avez pu le constater, nous avons eu droit à une erreur : "NameError: name 'i' is not defined".

Pourquoi cette erreur, la variable i est bien définie dans la fonction fct() et la fonction fct() est bien exécutée, où est donc le problème ?

En fait, la variable i est une variable dite locale : elle a été définie dans une fonction et elle "restera" dans cette fonction. Une fois que l'exécution de la fonction sera terminée, la variable i sera "détruite" (supprimée de la mémoire). Elle n'est donc pas accessible depuis "l'extérieur" de la fonction (ce qui explique le message d'erreur que nous obtenons).

Une variable définie dans une fonction est locale.
Elle ne peut pas être utilisée en dehors de la fonction.

Deuxième exemple :

Example: Deuxième exemple
Avant d'exécutez le programme ci-dessus, interrogez vous sur la valeur de la variable i à l'issue de l’exécution, puis lancer le code et vérifiez

Cette fois pas d'erreur, mais à la fin de l'exécution de ce programme, la variable i référence la valeur 3.

En fait, dans cet exemple nous avons 2 variables i différentes : la variable i "globale" (celle qui a été définie en dehors de toute fonction) et la variable i "locale" (celle qui a été définie dans la fonction). Ces 2 variables portent le même nom, mais sont différentes.

Une variable définie localement dans une fonction peut porter le même nom qu'une variable globale. Dans ce cas, ce sont deux variable distinctes mais de même nom (ce qui n'est pas une bonne pratique).

Troisième exemple :

Exécutez le programme suivant :

Example: Troisième exemple
Avant d'exécutez le programme ci-dessus, interrogez vous sur la valeur de la variable i à l'issue de l’exécution, puis lancer le code et vérifiez

La variable i est définie en dehors de la fonction, elle est globale.

Une variable globale peut être "utilisée" à l'intérieur d'une fonction.

Quatrième exemple :

Avant d'exécutez le programme ci-dessus, déterminez la valeur référencée par la variable i en fin d'exécution

Example: Quatrième exemple
Avant d'exécutez le programme ci-dessus, interrogez vous sur la valeur de la variable i à l'issue de l’exécution, puis lancer le code et vérifiez

Pour le "print(i)" situé dans la fonction le système trouve une variable i dans l'espace local de la fonction "fct", et il utilise donc celle ci, même s'il existe une variable globale i. Il est important de bien comprendre que le lorsque système trouve une variable i dans l'espace local de la fonction, la "recherche" de la variable i s'arrête là. D'autre part, ici nous avons deux variable i : l'une globale, définie en dehors de fct(), une autre locale, définie dans fct(). Lorsque la fonction s'arrête, la variable locale est détruite, la variable globale demeure et n'a pas été modifiée.

Cliquez Visualiser et observez l'état de la mémoire au cours de l'exécution.

Cinquième exemple :

Exécutez le programme ci-dessus, déterminez la valeur référencée par la variable i en fin de programme.

Example: Cinquième exemple (version 1)
Avant d'exécutez le programme ci-dessus, interrogez vous sur la valeur de la variable i à l'issue de l’exécution, puis lancer le code et vérifiez

Nous avons une erreur "UnboundLocalError: local variable 'i' referenced before assignment"

Cinquième exemple (avec global)

Exécutez maintenant cette seconde version de l'exemple 5 :


Example: Cinquième exemple (version 2)
Avant d'exécutez le programme ci-dessus, interrogez vous sur la valeur de la variable i à l'issue de l’exécution, puis lancer le code et vérifiez

Pour pouvoir modifier une variable globale dans une fonction, il faut utiliser l'instruction "global"

En fait, l'utilisation de "global" est une (très) mauvaise pratique, car cette utilisation peut entraîner des "effets de bord".

Effet de bord

Effet de bord (secondaire)

On parle d'effet de bord (c'est une bien mauvaise traduction de side effet, on devrait dire effets secondaires) quand une fonction modifie l'état d'une variable globale. Dans notre exemple ci-dessus la fonction fct modifie bien la valeur référencée par la variable i : avant l'exécution de fct, la variable i référence la valeur 5, après l'exécution de la fonction fct la variable i référence la valeur 6. Nous avons donc bien un effet de bord.

Les effets de bord c'est "mal" ! Mais pourquoi est-ce "mal" ?

Les effets de bords provoquent parfois des comportements non désirés par le programmeur (évidemment dans des programmes très complexes, pas dans des cas simplistes comme celui que nous venons de voir). Ils rendent aussi parfois les programmes difficilement lisibles (difficilement compréhensibles). À cause des effets de bord, on risque de se retrouver avec des variables qui référenceront des valeurs qui n'étaient pas prévues par le programmeur. On dit aussi qu'à un instant donné, l'état futur des variables est difficilement prévisible à cause des effets de bord.

En résumé, on évitera autant que possible l'utilisation du "global".

Un paradigme de programmation se propose d'éviter au maximum les effets de bords : la programmation fonctionnelle. Nous étudierons ce paradigme de programmation très prochainement.

T - 4.3 Files

II. Les Files

Vous allez vous-même compléter ci-dessous une possible implémentation de ces fonctions primaire, en utilisant le vocabulaire de la programmation orientée objet que nous avons déjà abordée. Dans toute la suite les files seront affichées entre crochets, comme des list python. Le coté du défilage est l'élément écrit le plus à droite. Ainsi, si l'on part d'une file vide, et que l'on enfile successivement les entiers 1, puis 2, puis 3, on obtiendra une file qui s'affichera de la façon suivante : [3, 2, 1]. Le sommet de cette file est l'entier 1.

Pour ne pas perdre de vue le sens de la File, la méthode str permettra d'afficher de quel coté on enfile et de quel coté on défile. Ce choix est arbitraire.

Implementer les fonctions primaires
Coding Exercise

Maintenant que vos primitives sont définies vous allez pouvoir les tester

Example

A vous de jouer !

Écrire ci dessous les instructions qui permettent d'obtenir successivement les affichages suivants :

enfilage -> [8] -> défilage
enfilage -> [14, 8] -> défilage
enfilage -> [12, 14, 8] -> défilage
enfilage -> [12, 14] -> défilage
enfilage -> [12] -> défilage
enfilage -> [] -> défilage

Coding Exercise

T4.3 dictionnaire versus listes

Dans une bibliothèque, nous avons 10000 livres. Pour chacun un certain nombre de caractéristiques : auteur, date de parution, nombre de pages etc...

Nous allons nous limiter à une seule caractéristique, par exemple le nombre de pages. Chaque livre à donc un nombre de page (remarquons qu'inversement, plusieurs livres peuvent avoir le même nombre de pages).

Nous aimerions trouver le plus rapidement possible le nombre de pages d'un livre. Nous devons décider comment représenter la situation. Nous pouvons utiliser des tuples (livre, nbDePages), on construira alors une liste de tuples, ou nous pouvons utiliser un dictionnaire : {livre1: nbDePages1, livre2: nbDePages2 etc...}

A première vue, laquelle des deux représentations vous semble la plus adaptée (laquelle vous semble plus naturelle) ?

Par ailleurs, ce choix aura-t-il un impact en terme d'efficacité dans la recherche ?

C'est ce que nous allons explorer dans ce TP.

👉 Nous utilisons ici le type : list de python

Implémenter avec une liste ou un dictionnaire

Implémentation avec liste ou dictionnaire

Coding Exercise: représenter le problème avec une liste de tuples
Vous disposez de 2 listes : une liste de livres, et une liste de nombre de pages. Les deux listes sont en correspondance : nbPages[i] est le nombre de pages de livre[i].
En utilisant ces deux listes vous devez en construire une nouvelle, avec des tuples (livre, nombre de pages).
Ensuite, en repartant de cette liste, construire un dictionnaire comme décrit plus haut.
Rechercher l'information souhaitée

Fonctions de recherche de l'information

Bravo ! Vous avez bien implémenté les deux modèles. Maintenant, créez deux fonctions pour trouver le nombre de pages d'un livre.

Coding Exercise: Chercher l'information

Le correcteur automatique va maintenant générer pour vous une liste et un dictionnaire, comme vous l'aviez fait dans l'exercice précédent.
Vous devez coder deux fonctions de recherche, l'une dans la liste, l'autre dans le dictionnaire, renvoyant toute deux le nombre de page d'un livre.
Votre code ne doit rien afficher, le correcteur définira des arguments pour tester vos deux fonctions.
Mesure d'efficacité

Mesure de l'efficacité

La liste et le dictionnaire générés sont de taille très limitée, aussi la mesure de l'efficacité de l'un et l'autre n'est pas aussi probante que si nous travaillions avec des listes de 10000 livres.

Néanmoins nous pouvons comparer la recherche dans la liste et la recherche dans le dictionnaire.

Coding Exercise: Chercher l'information

Le correcteur automatique va maintenant générer pour vous
  • une liste et un dictionnaire, comme précédemment, nommées liste et dico
  • les deux fonctions de recherche findInlist et findInDico
  • une variable livre1.
Tout ce que vous avez à faire est d'inclure des appels répétés (disons 10000 fois) à chacune de vos fonction, pour mesurer les temps des recherches.

Vous rechercherez livre1 dans la liste, ou dans le dico.

Pour une fois, le correcteur ne tiendra pas compte des affichage en console pour évaluer votre code, mais il évaluera quand même le résultat important : quelle méthode est la plus efficace ?

Mesure plus fine de la complexité

Bon, le dictionnaire est plus efficace. Mais ici on travaille sur une bibliothèque de 5 livres.... passons à quelques chose de plus réaliste. Dans l'exemple ci-dessous vous n'avez rien à coder, mais observer et conclure.

On génère pour vous une bibliothèque (c'est à dire, une liste et un dictionnaire) de 10000 livres (bon, les noms des livres ici sont des entiers, mais ça importe peu..). Le code ci-dessous va utiliser les deux fonctions de recherche pour déterminer le nombre de pages d'un livre. On va prendre des livres du début de liste, puis de plus en plus loin dans la liste.... on devrait donc mettre de plus en plus longtemps pour les trouver.

Pour chaque livre la recherche est répétée 100 fois afin d'estimer un temps moyen a peu près stable.

Example: Complexités
Multiple Choice Exercise
La complexité de la recherche dans la liste vous semble :
Correct!
Multiple Choice Exercise
La complexité de la recherche dans le dictionnaire vous semble :
Correct!

T - 4.2 Piles

I. Les piles

Exemple de primitive

On donne ci-dessous une possible implémentation de ces fonctions primaire, en utilisant le vocabulaire de la programmation orientée objet que nous avons déjà abordée.
Il existe bien d'autres possibilités pour implémenter ces fonctions primaires.
Dans toute la suite les piles seront affichées entre crochets, comme des list python. Le sommet de la pile est l'élément écrit le plus à droite. Ainsi, si l'on part d'une pile vide, et que l'on empile successivement les entiers 1, puis 2, puis 3, on obtiendra une pile qui s'affichera de la façon suivante : [1, 2, 3]. Le sommet de cette pile est l'entier 3.

Example
Une interface de pile en POO

A vous de jouer !

Ecrire ci dessous les instructions qui permettent d'obtenir successivement les affichages suivants :

[12] <- sommet
[12, 14] <- sommet
[12, 14, 8] <- sommet
[12, 14] <- sommet
[12] <- sommet
[] <- sommet

Coding Exercise

Autre fonction pour les piles

Alice désire écrire une fonction, qui doit retourner la taille de la pile. Attention, une fois que sa taille a été déterminée, la pile ne doit pas avoir été modifiée...
Elle propose le code suivant :

Le code d'Alice
Example

A vous de résoudre ce problème
Ecrire ci-dessous une fonction taille qui remédie à ce problème Aide1
Coding Exercise

Applications

Vérifier les parenthèses d'une expression mathématique

Le but de cet exercice est d'écrire une fonction qui contrôle si une expression mathématique, donnée sous forme d'une chaîne de caractères, est bien parenthésée, c'est-à-dire s'il y a autant de parenthèses ouvrantes que de fermantes. On s'interdit d'utiliser des variables qui "comptent" les parenthèses ouvrantes ou fermantes.

Par exemple :

  • (..(..)..) est bien parenthésée.
  • (...(..(..)...) ne l'est pas .

L'algorithme :

On crée une pile. On parcourt l'expression de gauche à droite.
Si on rencontre une parenthèse fermante ")", alors :

  • Si la pile n'est pas vide, on dépile
  • Sinon on renvoie Faux"

À la fin la pile doit être vide...

Source : Stéphan Van Zuijlen

Compléter la fonction suivante :

Coding Exercise

Paradigmes de programmation

Les différents paradigmes de programmation

Toutes les situations de ton quotidien peuvent être abordées d’une manière différente. Tu peux choisir de les aborder positivement ou négativement. En programmation, c’est pareil. Il existe plusieurs façons d’approcher la programmation informatique et de traiter les solutions aux problèmes. Ce sont les paradigmes de programmation. Nous allons décrire ici 3 paradigmes de programmation. Un que vous connaissez très bien puisque vous l'utilisez depuis l'année dernière quand vous programmez en Python : la programmation impérative.

Mais il existe d'autres types de paradigme de programmation, et nous en décrirons 2 : la programmation fonctionnelle, et la programmation objet.

1. Programmation impérative

source :ionos.fr

Qu'est ce que la programmation impérative ?

La programmation impérative (du latin imperare = ordonner) est le paradigme de programmation le plus ancien. Ce paradigme définit un programme comme une séquence clairement définie d’instructions informatiques.

Le code source des langages impératifs énonce donc des séquences d’ordres, déterminant quand l’ordinateur doit exécuter quelle action pour atteindre le résultat souhaité. Les valeurs utilisées dans les variables sont alors transformées en durée d’exécution du programme. Pour piloter les ordres, des structures de contrôle, telles que des boucles ou embranchements, sont intégrées au code.

Le paradigme de programmation impérative est très concret et fonctionne en étroite collaboration avec le système. Le code est aisément compréhensible, mais il nécessite l'écriture de nombreuses ligne de code source.

De nos jours, de nombreux langages de programmation se fondent sur le paradigme de programmation impérative.

Cela tient, d’une part, au fait que cette approche correspond au mode de programmation initial. De l’autre, au fait que le paradigme impératif présente des avantages pratiques par rapport aux autres modèles.

Comparativement, ces langages sont faciles à apprendre dans la mesure où le code se lit comme un mode d’emploi, étape par étape. En ce sens, au cours de leur formation professionnelle, les programmeurs commencent généralement leur apprentissage par un langage impératif.In [1]:1

Example: Recherche du minimum d une liste en Python
Recherche du minimum en impératif

C'est un code assez explicite, car nous en avons l'habitude.

On commence par évaluer le nombre d'éléments de notre liste, que l'on stocke dans une variable longueur

Puis on teste si la liste est vide, et si c'est le cas on affiche le message correspondant

Sinon, on affecte à la variable mini le premier élément de la liste, puis on teste pour tous les éléments de la liste si cet élément est plus petit que mini.

Si c'est le cas, la variable mini prend comme nouvelle valeur cet élément.

Et une fois la boucle terminée, le programme affiche la valeur de la variable mini.

Exemples de langages de programmation impérative :

C, Pascal, Python, COBOL, etc ...

Attention :

En programmation impérative, nous utilisons des fonctions, qui ne sont que des suites d'instructions informatiques regroupées dans un bloc. Mais cela ne suffit pour prétendre utiliser le paradigme fonctionnel que nous allons décrire ci-dessous :

2. Programmation fonctionnelle

Ce paradigme prend son origine dans le langage mathématique traitant des fonctions. Une fonction, dans son incarnation informatique, accepte des données et produit des données. Une fonction peut être soit primitive, soit formée par une composition de fonctions.

Il n'y a pas de séparation entre données et programmes : une fonction est un objet de première classe sur lequel d'autres fonctions peuvent opérer. Nous ne rentrerons pas plus en détail dans les caractéristiques précise de la programmation fonctionnelle. Pour ceux qui veulent s'initier à la programmation fonctionnelle sur Python, vous pouvez consulter ce lien :

Tutoriel pour apprendre la programmation fonctionnelle en Python

Si l'on reprend notre exemple de recherche du minimum dans une liste, nous aurions ceci en programmation fonctionnelle sur Python :

Example: Recherche du minimum d une liste en Python
Recherche du minimum (impératif)
Example: Recherche du minimum d une liste en Python
Recherche du minimum (fonctionnel)

Explications :

Dans la version impérative, la fonction incrémente une variable globale (a). La valeur de la variable est donc modifiée quand nous exécutons la fonction.

Dans la version fonctionnelle, la fonction prend un argument (a), puis renvoie une valeur (a+1). Le code ne dépend pas de données se trouvant à l'extérieur de la fonction courante et il ne modifie pas des données à l'extérieur de cette fonction.

3. Programmation Orientée Objet

Programmer avec un langage oreitné-objet amène le programmeur à identifier les acteurs qui composent un problème, puis à déterminer ce qu'est et ce que doit savoir chaque acteur. En regroupant les aspects communs puis en les spécialisatn, le programmeur établit une hiérarchie de classe. Nous détaillerons en détail le fonctionnement inhérent à la programmation orientée objet dans le chapitre suivant.

Exemples de langages de programmation orientée objet :

C++, C#, Java, Python, etc ...

Nous pouvons en Python utiliser les classes pour répondre à notre exemple de recherche de minimum dans une liste :

Example: Recherche du minimum d une liste en Python
Recherche du minimum en python

En effet, lors de la création de notre liste, on crée en fait un objet qui est une instance de la classe liste. Et cette classe possède de nombreuses méthodes publiques dont la méthode sort() qui ordonne (de manière croissante) les éléments de la liste. Le minimum de cette liste est donc forcément le premier élément.

Nous allons maintenant revenir sur ce vocabulaire, et expliciter davantage cette manière de programmer en objet ...

S8.1 DS les dictionnaires

Découverte

Nous souhaitons lire avec un code écrit en python des données dans un fichier csv. Nous allons avoir besoin d'un nouveau type de variable : les dictionnaires.

Vous connaissez déjà les listes. Par exemple lst=[1,2,4,1,0] est une liste de nombres, et on sait accéder aux valeur de chaque élément :

Example

Finalement, chaque élément de la liste est associé à un indice.

  • 1 est à l'indice 0
  • 2 à l'ndice 1
  • etc...

Dans un dictionnaire, les indices sont remplacés par des descripteurs aussi appelés les clés du dictionnaire. Voila comment pourrait être écrite la liste précédente sous forme d'un dictionnaire :

Example

Bon, ça ne sert pas à grand chose vu comme ça ! car ici on a utilisé des clés qui sont strictement identiques aux indices de la liste. Mais supposons qu'on veuille stocker en mémoire une information telle que :

Nombre d'Elèves :34
Numéro de la Classe : 12
PP : M. Le Sann

ça devient beaucoup plus intéressant si ces clés sont des mots décrivant le contenu :

Example
Un dictionnaire permet de décrire les valeurs

Exercice

Coding Exercise: afficher les valeurs des clés
Dans cet exercice, vous disposez d'un dictionnaire déjà créé. Faire afficher successivement le nom, le prenom et l'année de naissance.

Modifier un dictionnaire

Example
Un dictionnaire permet de décrire les valeurs

Nous venons de voir comment modifier la valeur d'une clé existante. Mais comme pour les listes, on peut bien sur aussi ajouter des éléments :

Example
Un dictionnaire permet de décrire les valeurs

Dans le cas d'une liste, les indices sont toujours les mêmes : 0, 1, 2 ...

Dans un dictionnaire en revanche, les descripteurs sont spécifiques, et on peut avoir besoin de les connaitre. On utilise pour cela la méthode keys() :

parcourir un dictionnaire

Example
rappel : parcours de liste

On peut parcourir de la même manière un dictionnaire, mais comme les indices sont remplacé par des clés, il faut itérer sur les clés. Pour cela on utilise la méthodé keys() qui nous donne la liste des clés.

Example
Un dictionnaire permet de décrire les valeurs

Exercice

Coding Exercise: implementez et afficher un dictionnaire
Voici ci dessous un court extrait de la liste des 72 noms de scientifiques, gravés sur la tour Eiffel. Il s'agit d'une petit partie de ceux gravés sur la face Trocadéro.
Représentez l'information de ce tableau dans trois dictionnaire avec les descripteurs suivants :
  • numéro
  • nom
  • prenom
  • naissance
  • deces
  • metier
(vous n'incluerez pas le Titre dans les dictionnaires).
Le code, à partir de la ligne 6, vous est donné : on va regrouper les 3 dictionnaires dans une liste et afficher cette liste, d'abord dans un parcours de liste, puis d'un seul bloc.
FACE TROCADÉRO
No  Nom en abrégé inscrit sur la tour Eiffel Prénom Dates de naissance et de mort Métier(s) Titres de gloire
1 SEGUIN Marc 1786-1875 Mécanicien Constructeur de ponts suspendus - Inventeur de la chaudière tubulaire
2 LALANDE Joseph 1732-1807 Astronome Nombreux travaux d'astronomie et d'hydrologie
3 TRESCA Henri 1814-1885 Ingénieur et mécanicien Auteur du critère de Tresca (résistance des matériaux)
: Le jardin coloré

photo librairie PIL

Nous allons maintenant utliser la libraire PIL de python qui permet de traiter les images.

Qu'est ce qu'une librairie

C'est un ensemble de fonctions qu'on peut utiliser, on peut dire une extension de python qui lui rajoute des instructions.

Concrètement on écrit :

import PIL

et on peut dès lors utiliser les fonction de la librairie. Par exemple l'instruction :

img = PIL.Image.open('pomme.jpg')

crée une variable img qui contient les données de l'image.

Cette variable est un objet python qu'on peut manipuler avec des instructions comme :

img.save('nom_du_fichier','JPG')

pour sauvegarder l'image au format JPG
Ou encore :
img.size qui nous donnera par exemple (300,500) si l'image fait 300 pixels de larges et 500 pixels de hauteur.

Les tuples : un nouveau type de variable

En Python, (300,500) est appelé un tuple, c'est un type de variable. Nous pouvons l'utiliser par exemple ainsi :
Example

Nous aurons également besoin de manipuler les valeurs des 3 couleurs rouge, vert et bleu, d'un pixel.

La couleur d'un pixel est un tuple de 3 valeurs, comme par exemple : (120, 30, 200). Ce tuple indique la couleur :

rouge=120 vert=20 bleu=200

Pour récupérer les valeurs des 3 couleurs nous feront comme pour la taille :

Example

Boucles imbriquées

Enfin une dernière chose dont nous allons avoir besoin : une image est un ensemble de pixels de coordonnées (x,y). Pour lire tous les pixels nous allons utiliser 2 boucles imbriquées. Examinons un cas simple, d'une image constituée de 6 pixels dont les couleurs sont indiquées ci-dessous :

(100,10,10)(200,40,10)(100,200,100)
(10,10,10)(20,40,60)(10,200,30)

si on veux afficher les 2 pixels de coordonnées (0,0) et (0,1) ;

Example

Exercice

Coding Exercise: utiliser getpixel
Comme dans l'exemple précédent, on a défini une image de 6 pixels (légèrement différente de la première) avec encore 2 lignes et 3 colonnes
Ajoute une ligne de code pour afficher la couleur du pixel situé en bas à droite de l'image.

Mais si on veux afficher tous les pixels ?

C'est là qu'on va faire 2 boucles imbriquées :

On commence par récuppérer la taille de l'image, avec la fonction image size(). size renvoie la taille en nombre de pixels, largeur x hauteur.

ATTENTION : la largeur est le nombre de colonnes, et la hauteur est le nombre de lignes !

Ensuite on lit tout les pixels, lignes par ligne, et pour chaque ligne toutes les colonnes.

x (numéro de ligne) varie de 0 à 1

pour chaque valeur de x, y (numéro de colonne) varie de 0 à 2

Example

Exercice

Coding Exercise: lire toute l'image
Tel quel, ce code ne fonctionnera pas. En effet nous avons modifié l'image qui ne fait plus 2 pixels de large et 3 pixels de haut.
Dans le code, on utilise img.size() qui permet de connaitre la largeur l et la hauteur h (en nombre de pixels) de l'image. Il faut utiliser l et h dans la boucle pour pouvoir afficher tous les pixels....

Et pour modifier l'image ?

C'est possible, bien entendu. Au lieu de getpixel, qui permet de "lire" la couleur d'un pixel, on va utiliser putpixel, qui permet d'écrire :

Example