(Rappel) 1.1 Type simple : principe
On a une liaison entre un NOM de variable et un CONTENU en mémoire :
Nous allons aujourd'hui voir comment utiliser les classes d'objets dans le cadre de la programmation. Vous les avez déjà rencontrées plus d'une fois sans forcément vous en rendre compte. En réalité, dans Python, tout est objet ou presque.
Nous allons donc définir aujourd'hui les termes suivants : objet, Classe, attribut, méthode...
Prérequis : savoir utiliser Python :o)
Logiciel nécessaire pour l'activité : Python 3 : Thonny, IDLE ...
Evaluation : 8 questions
✌ question 02
✎ question 03-04-07-09-12-13-15
Exercices supplémentaires 🏠 : oui
Documents de cours : open document ou pdf
Documents uniquement : open document ou pdf
Résumons les variables et types de données associés que nous avons vu pour le moment :
Rappelons qu'on peut localiser une zone mémoire de plusieurs façons :
Dans le cadre de Python, les contenus sont caractérisés par un identifiant. Néanmoins, j'utiliserai "adresse", plus simple et explicite. Mais en Python, il s'agit bien d'identifiants en réalité.
On a une liaison entre un NOM de variable et un CONTENU en mémoire :
On a une liaison entre un NOM de variable et un CONTENEUR en mémoire.
A partir de ce CONTENEUR, on peut ensuite localiser différents contenus.
Version naîve d'un tableau t1 qui "contient" 10, 100 et 1000 :
Important : en Python, la variable t1 désigne donc l'adresse du conteneur-tableau-armoire et pas le contenu du tableau.
Lors d'une affectation, Python crée une association entre le nom de la variable et un contenu en mémoire.
L'espace des noms correspond au mécanisme qui lie le nom de la variable à un contenu mémoire, représenté ici par le simple trait entre les deux.
Les associations entre nom et contenu sont mémorisées dans une table qu'on nomme l'espace des noms.
Imaginons qu'on dispose de ce programme :
1
2
3 |
|
Voici l'image simpliste qu'on peut se faire de la liaison entre l'espace des noms et l'espace mémoire :
Cette liaison est réalisée en associant dans une table chaque nom de variable à une zone mémoire.
En Python, on peut récupérer l'adresse / référence / identifiant à l'aide de la fonction native id().
>>> a = 10
>>> b = 20
>>> id(a)
108
>>> id(b)
524
Il existe en réalité plusieurs espaces des noms pouvant référencer l'espace mémoire :
On peut voir visuellement ces zones dans Python Tutor. La zone grise est l'espace des noms actuellement utilisé par Python.
Exemple avec ce court programme :
1
2
3
4
5 |
|
Lorsque Python doit évaluer une variable dans une fonction :
Traduit en Python, cela donnerait quelque chose comme ceci :
1
2
3
4
5
6
7
8
9
10
11
12
13 |
|
Dans d'autres langages (comme le C par exemple), l'adresse d'une variable ne change pas après déclaration. C'est bien le contenu de la case mémoire qui change.
Le type list ou le type dict de Python sont des types construits, ok.
Mais, les lists Python sont plus que cela car elles contiennent :
On accède aux données de la liste en utilisant l'opérateur "CROCHETS" : t[i].
On accède aux fonctions internes (qu'on nomme méthodes) en utilisant l'opérateur "POINT" : t.nom().
La réprésentation symbolique de list ressemble donc à cela :
Voici comment on trace un trait rouge avec le module de dessin Turtle :
1
2
3
4
5 | import turtle as trt
crayon = trt.Turtle() # Crée l'objet et stocke son adresse dans crayon
crayon.pencolor("red") # Va à cette adresse et tu y trouvera une fonction pencolor()
crayon.forward(200) # Va à cette adresse et tu y trouvera une fonction forward()
|
En ligne 2, on crée un nouveau objet de classe Turtle en utilisant le constructeur nommée Turtle() contenu dans le module connu sous le nom trt.
Les "fonctions" utilisées sont particulières puisqu'elles sont directement présentes dans l'objet et qu'on y accède avec la syntaxe objet.methode().
Voici comment on ouvre un fichier pour afficher son contenu ligne par ligne :
1
2
3
4
5 | obj_fichier = open('le_fichier_voulu.txt', 'r', encoding="utf-8")
for ligne in obj_fichier:
print(ligne)
obj_fichier.close()
|
En ligne 1, on crée un nouveau objet-fichier en utilisant la fonction native nommée open(). La variable obj_fichier contient alors l'adresse de notre nouvel objet.
En ligne 3, on crée une boucle en générant une variable de boucle ligne sur tous les éléments qu'on va trouver à l'adresse de notre objet obj_fichier.
En ligne 5, on ferme notre objet-fichier en allant cherche à son adresse la méthode se nommant close().
Encore une fois, on retrouve la syntaxe objet.methode().
Exemple
>>> t = ['a', 'b', 'c']
>>> t
['a', 'b', 'c']
>>> id(t)
1485
C'est un tableau de caractères, tous encodés sur 1 octet. L'adresse d'une case correspond donc à l'adresse du tableau plus la valeur de l'indice :
adr = A + i*1.
t[0] mène vers l'adresse 1485 qui mène à l'adresse 68 qui fait référence à 'a'
t[1] mène vers l'adresse 1486 qui mène à l'adresse 48 qui fait référence à 'b'
t[2] mène vers l'adresse 1487 qui mène à l'adresse 24 qui fait référence à 'c'.
Ce que nous avons vu plus haut correspond aux vrais tableaux (comme en C par exemple). Or, le type list est en réalité un objet : c'est un conteneur à données mais également à méthodes.
L'adresse de la structure n'est donc pas nécessairement pas identique à l'adresse de sa première case.
En réalité, sa structure mène tout simplement à une zone mémoire qui contient une table d'espace des noms, l'espace des noms de ce tableau précis !
Si on veut résumer cela de façon très synthétique (et un peu fausse)
Cette année, nous allons voir comment créer nos propres objets.
En réalité, en Python, tout n'est qu'objet. Même les integers sont des objets car on peut utiliser des méthodes sur les integers !
A titre d'exemple, appliquons la méthode bit_length() qui permet d'obtenir le nombre de bits nécessaires pour encoder un entier. Pour 5, c'est 3 puisqu'on a 5 10 = 101 2.
>>> n = 5
>>> n.bit_length()
3
Comme quoi, même les choses qu'on pouvait croire simples, peuvent être complexes sous le capot.
Lorsqu'on veut créer un objet dans la vraie vie, on a besoin d'une notice, d'un plan, d'une recette.
Si vous avez la Recette pour faire un gâteau, vous serez capable d'en créer autant que vous le voulez.
C'est pareil en informatique.
Les instructions d'une Recette permettent de réaliser de vrais gâteaux, tous un peu similaires mais potentiellement différents : tous les gâteaux n'auront pas les mêmes caractéristiques (forme, couleur, taille, goût...).
Les instructions d'une Classe permettent de réaliser de vrais objets qu'on nommera également instances de cette classe : ils auront au départ des attributs (des variables) dont les contenus sont identiques mais pourront être personnalisés.
Résumé de l'analogie :
Créer une Classe en Python nécessite l'utilisation du mot-clé class suivi du nom de la Classe qui commence par une Majuscule par convention. Comme toute déclaration, on finit par le caractère ':'.
1
2
3
4
5
6
7 |
|
On place une docstring d'une ligne décrivant la Classe.
Les instructions à réaliser sont décalées de 4 espaces. Ici, l'instruction en ligne 3 est juste pass : ne rien faire de particulier.
Une fois la classe déclarée, on peut l'utiliser pour créer de vrais objets basés sur elle. La création d'une instance se nomme une instanciation.
On peut voir 4 instanciations ci-dessous, sur les lignes 10-11-12-13.
Pour cela, on utiliser la fonction Constructeur : une fonction qui porte le même nom que la Classe. Comme c'est une fonction, il y a des parenthèses.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 |
|
Ce programme affiche les messages suivants à cause des différents print() :
INFOS sur la variable alice
<__main__.Personnage object at 0x79dd7bc0f670> <-- résultat de L16 : print(alice)
INFOS sur la variable vlad
<__main__.Monstre object at 0x79dd7bc0e5c0> <-- résultat de L19 : print(vlad)
Par défaut, l'affichage d'un objet comporte :
On peut récupérer le type d'un objet en utilisant simplement la fonction native type() :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 |
|
Voici l'affichage obtenu :
INFOS sur la variable alice
<class '__main__.Personnage'> <-- résultat de print(type(alice)) <-- résultat de L16 : print(type(alice))
INFOS sur la variable vlad
<class '__main__.Monstre'> <-- résultat de L19 : print(type(vlad))
Combat ou pas combat ?
Fight ! <-- résultat de L23
En lignes 22-23, on demande d'afficher "Fight !" uniquement si alice est bien une instance de Personnage et vlad une instance de Monstre.
Nous venons de voir qu'on crée un objet "vide".
En réalité, cet objet n'est pas si vide que cela. C'est un conteneur qui contient déjà quelques variables et quelques fonctions qui lui sont automatiquement attribués par Python.
Les variables contenues dans un objet seront nommés ses attributs. La syntaxe pointée permet d'accéder à un attribut : objet.attribut
Les fonctions contenues dans un objet seront nommées ses méthodes. La syntaxe pointée permet de lancer l'appel à une méthode : objet.methode()
Pour observer le contenu d'un objet (ses attributs et se méthodes), on peut utiliser la fonction native de Python nommée dir() qui renvoie un tableau de strings qui contient les noms des attributs et des méthodes.
>>> dir(alice)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
>>> for v in dir(alice): print(v)
__class__
__delattr__
__dict__
__dir__
__doc__
__eq__
__format__
__ge__
__getattribute__
__gt__
__hash__
__init__
__init_subclass__
__le__
__lt__
__module__
__ne__
__new__
__reduce__
__reduce_ex__
__repr__
__setattr__
__sizeof__
__str__
__subclasshook__
__weakref__
On voit que notre objet alice contient déjà de nombreuses choses. D'ailleurs, tous les noms commencent par deux underscores et finissent par deux underscopres pour signaler que ce sont des méthodes spéciales : l'utilisateur n'est pas censé faire appel lui-même à ces méthodes, c'est Python qui va leur appeler lorsqu'il en a besoin.
Quelques exemples
Lorsque vous avez fait appel à la fonction native type() celle-ci va juste lire le nom de la classe dans la propriété spéciale alice.__class__
>>> type(alice)
<class '__main__.Personnage'>
>>> alice.__class__
<class '__main__.Personnage'>
Utilisateur ⇒ type() ⇒ __class__
Lorsque vous avez fait appel à la fonction native dir() celle-ci va juste faire appel à la méthode spéciale alice.__dir__()
>>> dir(alice)
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__']
>>> alice.__dir__()
['__module__', '__doc__', '__dict__', '__weakref__', '__new__', '__repr__', '__hash__', '__str__', '__getattribute__', '__setattr__', '__delattr__', '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', '__init__', '__reduce_ex__', '__reduce__', '__subclasshook__', '__init_subclass__', '__format__', '__sizeof__', '__dir__', '__class__']
Utilisateur ⇒ dir() ⇒ __dir__()
On peut également aller récupérer la documentation d'un objet ou sa taille en octets (bytes en anglais) en utilisant la propriété spéciale __doc__ et la méthode spéciale __sizeof__() :
>>> alice.__doc__
'Ceci est une classe permettant de créer un personnage dans mon super RPG'
>>> alice.__sizeof__()
32
Pour l'instant, la classe Personnage est un moule vide et nos objets sont donc des conteneurs vides.
Voyons comment stocker des attributs, des variables rattachées à notre objet.
Pour l'instant, toutes les instances de la classe Personne sont strictement identiques si ce n'est qu'elles ne sont pas stockées au même endroit.
Un attribut est une sorte de variable placée à l'intérieur d'un objet.
On utilise la syntaxe pointée : objet.attribut = valeur.
On notera que l'affectation se fait sur l'attribut, pas sur l'objet lui-même.
>>> alice.agilite = 18
Traduction en français :
On agit sur alice en lui rajoutant un nouvel attribut agilite référençant 18.
Effet réel :
Va à l'adresse d'alice puis crée et stocke sur place un attribut agilite référençant 18.
Si on veut créer un cousin éloigné de Luke Skywalker dans Star Wars, on pourrait faire ceci :
1
2
3
4
5
6
7
8
9
10 |
|
Que fait ce programme ?
Ligne 7 : il crée un objet bob, une instance de la Classe Personnage.
Lignes 8-9-10 : on ajoute des attributs en utilisant la syntaxe pointée.
On peut accéder à la valeur d'un attribut avec la syntaxe pointée : objet.attribut.
>>> bob.nom
'Skywalker'
>>> bob.prenom
'Bob'
Les objets sont mutables : on peut modifier leur contenu sans modifier l'adresse de l'objet lui-même.
On utilise la syntaxe suivante : objet.attribut = nouvelle_valeur
>>> bob.nom
'Skywalker'
>>> bob.nom = "Bond"
>>> bob.nom
'Bond'
01° Mettre le programme suivant en mémoire : il déclare deux classes Personnage et Monstre puis crée deux instances de Personnage nommées alice et bob.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 |
|
Questions
...CORRECTION...
02° A la suite du programme, rajouter deux attributs à la volée à chacune des instances :
...CORRECTION...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 |
|
03° Rajouter les instructions de façon à
...CORRECTION...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 |
|
Exemple d'affichage obtenu
INFOS sur la variable alice
<__main__.Personnage object at 0x74c934eeb670>
INFOS sur la variable bob
<__main__.Personnage object at 0x74c934eea7a0>
3
128407525308016
4
128407525308016
04° Expliquer à partir des résultats précédents si un objet est une donnée muable/mutable ou immuable.
...CORRECTION...
INFOS sur la variable alice
<__main__.Personnage object at 0x74c934eeb670>
INFOS sur la variable bob
<__main__.Personnage object at 0x74c934eea7a0>
3
128407525308016 <--- adresse d'alice avant la modification
4
128407525308016 <--- adresse d'alice après la modification
On voit donc clairement qu'avoir modifié le contenu du conteneur alice n'a pas modifié l'adresse du conteneur lui-même.
C'est la définition même de mutable ou muable.
05° Dans l'exemple donné ci-dessous,
>>> r2d2 = Personnage()
>>> r2d2.profession = "robot"
>>> r2d2.niveau = 5
>>> r2d2.couleur = "blanc"
...CORRECTION...
r2d2 est un objet et une instance de la classe Personnage.
profession est un attribut.
niveau est un attribut.
Les deux problèmes du rajout d'attributs à la volée sont liés :
Conclusion : même si Python permet de rajouter des attributs à la volée n'importe où dans le code, on veillera à ne pas le faire.
On utilise le rajout à la volée lorsqu'on réalise des petits tests ou un prototype. Jamais dans un programme réel.
On peut obtenir la liste des attributs disponibles dans un objet en utilisant la fonction native vars() qui vous renvoie un dictionnaire dont les clés sont les noms des attributs et les valeurs sont les valeurs des attributs.
>>> bob = Personnage()
>>> bob.nom = "Skywalker"
>>> bob.prenom = "Bob"
>>> bob.age = 25
>>> vars(bob)
{'nom': 'Skywalker', 'prenom': 'Bob', 'age': 25}
Les méthodes spéciales sont des méthodes qui sont appelées directement par Python.
C'est le cas de la méthode __init__() :
Utilisateur ⇒ Personnage() ⇒ __init__()
La méthode spéciale __init__() va alors générer certains attributs voulus par la conceptrice de la Classe. Cette méthode a pour but de centraliser la création et l'initialisation des attributs.
En Python, les méthodes spéciales sont faciles à repérer : leurs noms commencent et finissent par deux underscores.
Il existe deux différences entre une fonction classique et une méthode :
1
2
3
4
5
6
7
8 |
|
Lignes 5-6-7-8 : on comprend qu'on déclare et affecte 4 attributs nommés nom, prenom, niveau et profession.
Cette fois, les instances de Personnage ne sont plus vides après avoir utilisé le constructeur Personnage().
Ils ont 4 attributs et ces attributs possèdent les valeurs visibles sur les lignes 5 à 8.
>>> p = Personnage()
>>> p.nom
'aucun'
>>> p.prenom
'aucun'
>>> p.niveau
0
>>> p.profession
'aucune'
>>> p2 = Personnage()
>>> p2.nom
'aucun'
>>> p2.prenom
'aucun'
>>> p2.niveau
0
>>> p2.profession
'aucune'
Pour l'instant, à centraliser et initialiser les attributs disponibles. Si vous désirez créer un nouvel attribut, il vous suffit d'aller voir dans cette méthode pour avoir si vous pouvez utiliser tel ou tel nom ou s'il est déjà pris.
La méthode spéciale __init__() est donc nommée la méthode-initialisateur.
La déclaration de __init__() possède un paramètre nommé self.
Le paramètre self est particulier puisque ce n'est pas vous qui le transmettez au constructeur Personnage() pour qu'il le transmette à __init__().
1
2
3
4
5
6
7
8 |
|
Voici la création d'alice :
>>> alice = Personnage()
Que contient self alors ?
Voici un code imaginaire simulant les trois étapes de travail du constructeur Personnage() :
Instructions de Personnage():
adresse = reserver_adresse_pour_un_objet() # Réservation (ETAPE 1)
Personnage.__init__(adresse) # Initialisation (ETAPE 2)
return adresse # Renvoi (ETAPE 3)
A l'étape 2, Personnage() lance automatiquement un appel à __init__() qui se trouve dans la classe Personnage en lui envoyant la nouvelle adresse qu'il a obtenu lors de l'étape 1.
CONCLUSION : self référence AUTOMATIQUEMENT l'adresse de l'objet en cours d'utilisation.
Le paramètre self :
06° Mettre la classe ci-dessous en mémoire puis utiliser les instructions dans la console pour visualiser cette initialisation.
1
2
3
4
5
6
7
8 |
|
>>> bob = Personnage()
>>> bob.nom
'aucun'
>>> bob.prenom
'aucun'
>>> bob.niveau
0
>>> bob.profession
'aucune'
>>> bob.prenom = 'bob'
>>> bob.prenom
'bob'
Question et réponse : en regardant les attributs déjà créés, serait-il intelligent de vouloir créer un attribut contenant le niveau de magie en le nommant niveau ? Réponse, non : ce nom est déjà porté par un attribut. Il suffit de lire les lignes 4 à 8 pour d'en rendre compte.
✎ 07° Que va être le nom du personnage zzz suivant si on exécute cette instanciation ? Que contient la réponse du Constructeur Personnage() qu'on affecte à la variable zzz ?
>>> zzz = Personnage()
>>> zzz.nom
08° QCM : que contient le paramètre self de la méthode spéciale __init__() ?
Utiliser la nouvelle classe ci-dessous pour le découvrir ou avoir confirmation de votre avis.
1
2
3
4
5
6
7
8
9 |
|
>>> a = Personnage()
Depuis la fonction __init__, le paramètre self contient
<__main__.Personnage object at 0x7fc9c389aba8>
>>> hex(id(a))
'0x7fc9c389aba8'
Rappel : la fonction native hex() permet d'exprimer un nombre entier en hexadécimal plutôt qu'en décimal.
✎ 09° Lors de l'appel automatique à __init__() sur la première instruction ci-dessous (LA), que va contenir le paramètre self de la méthode __init__() ?
LA >>> a = Personnage()
LB >>> b = Personnage()
Même question avec l'instruction en ligne LB.
1
2
3
4
5
6
7
8
9 |
|
Les valeurs de base, c'est bien beau mais il faudrait ensuite placer dans les attributs les bonnes valeurs. C'est pénible de faire la création de l'objet en deux fois.
Heureusement, le Constructeur fonctionne comme n'importe quelle autre fonction : il peut lui envoyer des arguments qu'il placera dans ces paramètres. On va donc rajouter de nouveaux paramètres.
Tous les arguments envoyés au constructeur sont envoyés automatiquent également à la méthode __init__().
Un exemple :
1
2
3
4
5
6
7
8
9
10 |
|
Regardons ce que contient notre objet :
>>> p.prenom
'Alice'
>>> p.niveau
20
Pour comprendre la transmission d'informations, il faut comparer appel au contructeur et déclaration de la méthode __init__() :
04 Décla
10 Appel |
|
On voit alors visuellement quels sont les transferts effectués.
Que fait-on ensuite des paramètres ?
On voit ligne 6 que le paramètre pre sert à initialiser l'attribut prenom par exemple.
1
2
3
4
5
6
7
8 |
|
C'est un peu pénible de ne pas nommer le paramètre de la même façon que l'attribut. Habituellement, on utilise plutôt un code qui ressemble à cela :
1
2
3
4
5
6
7
8 |
|
Avantage : on comprend facilement dans quel attribut sera stocké chacun des paramètres.
Désavantage : on pourrait croire que les lignes 5-6-7-8 ne servent à rien, ce qui est totalement faux. Ce sont bien ces lignes qui font le lien entre les paramètres reçus et les attributs de l'objet.
Parfois, la majorité des arguments envoyés au constructeur seront toujours les mêmes. Par exemple, le niveau de départ des personnages à 1. Dans ce cas, on peut donner des valeurs par défaut à certains paramètres.
1
2
3
4
5
6
7
8 |
|
Ici, tous les paramètres ont une valeur par défaut. On peut donc lancer des appels de ce type :
>>> alice = Personnage()
>>> alice.niveau
1
>>> alice.profession
'Humain de base'
Remarque importante : si vous avez des paramètres sans valeur par défaut et d'autres avec valeur par défaut, il faut impérativement
4 |
|
On peut transmettre les paramètres dans n'importe quel ordre pourvu qu'on nomme tous les paramètres qui n'ont pas de valeur par défaut.
>>> bob = Personnage(prenom="Luke", nom="Skywalker", niveau=5, profession="Jedi")
>>> jim = Personnage(profession="Capitaine de l'Enterprise", nom="Kirk", niveau=6, prenom="James")
10° Utiliser la classe ci-dessous qui permet de créer une liaison entre le Constructeur et la méthode spéciale __init__().
Donner le contenu des différents attributs en faisant la comparaison entre appel et déclaration.
1
2
3
4
5
6
7
8 |
|
>>> bob = Personnage("Luke", "Skywalker", 5, "Jedi")
>>> bob.nom
'Luke'
...CORRECTION...
def __init__(self, a b c d )
| | | |
| | | |
| | | |
>>> bob = Personnage("Luke", "Skywalker", 5, "Jedi")
>>> bob.nom
'Luke'
On voit que :
11° Expliquer pourquoi lorsqu'on tape bob.nom, on obtient Luke (son prénom) et pas Skywalker (qui est normalement son nom).
...CORRECTION...
Au vu de l'appel, on stocke "Luke"
dans le paramètre a. Or a sert ensuite à initialiser l'attribut nom...
Le problème vient donc paramètre a contient un prénom envoyé par l'utilisateur, et pas le nom.
12° Corriger l'appel puis le programme en donnant aux paramètres des noms EXPLICITES qui devraient nous simplifier la vie.
...CORRECTION...
Pour l'appel, il suffit d'inverser le nom et le prénom :
>>> bob = Personnage("Skywalker", "Luke", 5, "Jedi")
>>> bob.nom
'Luke'
Dans le programme, pour éviter les confusions, le plus simple est de donner des noms explicites aux paramètres de réception :
1
2
3
4
5
6
7
8 |
|
✎ 13° Fournir l'instruction à écrire pour créer un personnage nommé Loulou Skydroper, un Technicien niveau 12. Attention, Loulou, c'est le prénom. On considère que la classe Personnage est l'une des deux précédentes.
On considère qu'on dispose de cette version de la classe :
1
2
3
4
5
6
7
8 |
|
14° En utilisant les paramètres nommés, réaliser un appel où on fournit d'abord le niveau, la profession, le nom puis le prénom.
...CORRECTION...
15° Cette dernière version utilise la notion de paramètres par défaut.
1
2
3
4
5
6
7
8 |
|
Donner le contenu des attributs des objets alice puis bob créés avec ces deux lignes de code :
>>> alice = Personnage(prenom="Alina", nom="Cloud", profession="Jedi")
>>> bob = Personnage(prenom="Bob", nom="Sky", niveau=5)
Mais... il n'y a pas d'espace autour du =
lorsqu'on envoie nos paramètres nommés !
C'est la convention sur les paramètres contrairement aux déclarations de variables habituelles. C'est comme ça.
Ici, on met un espace autour du = qui déclare la variable bob mais pas autour des = qui donnent les valeurs par défauts des paramètres :
>>> bob = Personnage(prenom="Luke", nom="Skywalker", niveau=5, profession="Jedi")
Un objet possède une adresse.
L'espace des noms fait le lien entre le nom de la variable et l'adresse.
A cette adresse se trouve un espace des noms interne à l'objet lui-même. Nous n'allons pas étudier l'implémentation réelle des objets dans Python, mais vous pouvez travailler avec cette représentation :
Dans l'espace des noms, on voit que bob mène à l'adresse finissant ...680.
A cette adresse ...680, on peut considérer qu'on va trouver un espace local des noms pour cet objet.
La codification bob.niveau peut se traduire ainsi en Français : va à l'adresse de cet objet bob et utilise son espace local des noms pour trouver le contenu de l'attribut se nommant niveau.
La différence avec l'espace local des noms d'une fonction est qu'il n'est pas détruit avant la fin du programme.
Les objets sont donc des conteneurs qui référencent un ESPACE DES NOMS INTERNES. Sur place, on y trouve :
Classe : Personnage
Constructeur : Personnage()
Objet ou Instance de Personnage : x = Personnage()
Cette fonction Constructeur renvoie l'adresse du nouvel objet.
Pas d'entrée particulière | ⇒ | Constructeur | ⇒ | Adresse d'un nouvel objet |
Plutôt que méthode-initialisateur ou méthode-initialisateur, on utilise parfois également :
On peut savoir si un objet est issu d'une classe particulière de deux façons.
>>> type(alice) == Personnage
True
>>> isinstance(alice, Personnage)
True
>>> isinstance(alice, list)
False
Dans le cadre réduit des connaissances vues en NSI, elles auront toujours un comportement identique. Mais elles ne testent pas vraiment la même chose. Utilisez juste type() cette année, mais si vous voyez isinstance() dans un sujet du BAC vous savez maintenant que ce prédicat teste la classe d'un objet.
Les méthodes dont le nom commence et finit par deux tirets sont nommées des méthodes spéciales. Ce sont des méthodes que Python utilise pour jouer un rôle particulier. On peut les redéfinir à la main de façon à modifier le comportement par défaut du système.
Quelques exemples :
Seule la méthode __init__() est au programme de NSI mais sachez qu'il en existe d'autres si on veut aller plus loin dans la gestion des objets.
Le programme de cette partie n'est pas un exemple des bonnes pratiques lorsqu'on utilise Pyxel avec des objets.
Pourquoi ?
Vous n'avez pas encore vu comment créer des méthodes, donc pour l'instant les objets ne sont finalement que des conteneurs de données.
Un programme utilisant le module pyxel est basé sur une boucle infinie décrite ci-dessous.
1 - La fonction init() crée la fenêtre graphique.
2 - La fonction run() active l'alternance infinie ci dessous :
3 - TANT QUE le programme est actif :
On mémorise les actions de l'utilisateur
SI la dernière mise à jour date de 1/30e de seconde ou plus
On active la fonction controler() qui mettra les données à jour en fonction des événements détéctés.
On active la fonction afficher() qui effacera l'écran avec cls() puis générera le nouvel affichage.
Fin du SI
Fin TANT QUE
Au vu de la condition sur l'activation des deux fonctions principales (nommées controler et afficher ici), on voit qu'il va y avoir 30 images par seconde.
1
2
3
4
5
6
7
8
9
10
11
12
13 |
|
Le point (0,0) est le bord haut gauche.
L'abscisse va bien vers la gauche.
Par contre, l'ordonnée est orientée vers le bas, attention.
16 (pyxel)° Vérifier si le module pyxel est installé :
>>> import pyxel
Si cela ne fonctionne pas, installer la bibliothèque pyxel dans Thonny :
17 (pyxel)° Enregistrer dans le répertoire Pong ce programme Python en le nommant modele.py :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157 |
|
Questions
18 (pyxel)° Passons maintenant à vue.py que vous enregistrerez également dans le répertoire Pong.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47 |
|
Questions et actions à réaliser
19 (pyxel)° Reste à définir le coeur de notre Pong : le fichier controleur.py contient les instructions qui vont faire la liaison entre les données du Modèle et l'affichage de la Vue.
Enregistrez ce nouveau programme au même endroit que les deux autres et testez que les deux raquettes peuvent effectivement bouger en utilisant :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58 |
|
Questions
20 (pyxel)° Dans cette question, nous allons nous focaliser sur controle() et la balle.
🏠 Exercices supplémentaires° Faire ces exercices en rédigeant correctement vos réponses.
Exos à réaliser (20 questions)
Cette partie est destinée à ceux qui ne parviennent pas lancer directement le module Pyxel depuis leur éditeur de texte local.
Un programme utilisant le module pyxel est basé sur une boucle infinie décrite ci-dessous.
1 - La fonction init() crée la fenêtre graphique.
2 - La fonction run() active l'alternance infinie ci dessous :
3 - TANT QUE le programme est actif :
On mémorise les actions de l'utilisateur
SI la dernière mise à jour date de 1/30e de seconde ou plus
On active la fonction controler() qui mettra les données à jour en fonction des événements détéctés.
On active la fonction afficher() qui effacera l'écran avec cls() puis générera le nouvel affichage.
Fin du SI
Fin TANT QUE
Au vu de la condition sur l'activation des deux fonctions principales (nommées controler et afficher ici), on voit qu'il va y avoir 30 images par seconde.
1
2
3
4
5
6
7
8
9
10
11
12
13 |
|
Le point (0,0) est le bord haut gauche.
L'abscisse va bien vers la gauche.
Par contre, l'ordonnée est orientée vers le bas, attention.
16 (pyxelstudio)° Aller sur le site pyxelstudio et ouvrir un nouveau projet en cliquant sur Créer.
Regardons ce que contient votre écran.
A FAIRE
17 (pyxel studio)° Enregistrer ce programme dans app.py :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159 |
|
Questions
18 (pyxel studio)° Passons maintenant à la partie vue que vous enregistrerez dans le même fichier : le code est situé sous celui du modèle, à partir de la ligne 144. Le programme de test est modifié et va maintenant afficher des éléments en fonction des valeurs fournies par le modèle.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191 |
|
Questions et actions à réaliser
19 (pyxel studio)° Reste à définir le coeur de notre Pong : le controleur regroupe les instructions qui vont faire la liaison entre les données du Modèle et l'affichage de la Vue.
Enregistrez ce nouveau programme et testez que les deux raquettes peuvent effectivement bouger en utilisant :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213 |
|
Questions
20 (pyxel)° Dans cette question, nous allons nous focaliser sur controle() et la balle.
🏠 Exercices supplémentaires° Faire ces exercices en rédigeant correctement vos réponses.
Exos à réaliser (20 questions)
La méthode __new__() est une méthode permettant de modifier le comportement de la création initiale, celle qui attribue l'adresse de l'objet.
Elle intervient donc avant la méthode __init__().
Voici une classe qui permet de demander d'afficher un message lors de l'appel de la méthode __new__() et de la méthode __init__(). Il vous permettra de voir que la première méthode est bien activée avant la deuxième.
1
2
3
4
5
6
7
8
9 |
|
Voici le résultat d'une des utilisations :
>>> a = Personnage()
On est dans __new__ : on va créer l'objet
On est donc __init__' : on va initialiser les attributs de l'objet
Voilà, on voit bien que la méthode __new__() est activée d'abord lorsqu'on crée un objet.
Si la ligne 6 ne vous a pas calmé, vous pouvez me poser des questions pour savoir ce qu'elle veut dire :o)
Sinon, sachez simplement que l'explication sur cette méthode n'est vraiment pas au programme. Pour comprendre son intéret, vous auriez besion de connaissances sur les objets qui sont hors programme en NSI. Vous verrez cela l'année prochaine dans le supérieur. Pour l'instant, vous pouvez donc oublier __new__(). La méthode de création par défaut suffira bien pour nos projets.
Cette notion n'est pas au programme du BAC. A ne lire que si vous voulez en savoir plus.
1
2
3
4
5
6
7
8
9
10
11 |
|
Comme vous pouvez le voir, on peut aussi créer des variables qui sont stockées directement dans les informations de la Classe et pas dans chacune des instantes créées à partir de cette classe. Utiliser cette variable de classe permet d'uniformiser certaines valeurs : vous êtes ainsi certain que toutes les classes utiliseront les mêmes valeurs pour certains calculs.
Activité publiée le 01 09 2020
Dernière modification : 07 09 2022
Auteur : ows. h.