Définir une méthode revient à définir une fonction à l'intérieur de la déclaration de la Classe.
Deux différences avec une fonction "classique" :
il faut la tabuler pour que l'interpréteur Python comprenne l'appartenance de la méthode à la Classe.
la méthode doit nécessairement avoir un premier paramètre qui recevra automatiquement l'adresse de l'objet sur lequel on est en train d'agir. En Python, on le nomme self par convention. Mais le nom n'a pas d'importance en lui-même.
# Déclaration des ClassesclassPersonnage:"""Ceci est une classe permettant de créer un personnage dans mon super RPG"""def__init__(self,nom='Aucun',prenom='Aucun',niveau=0,profession="Humain de base"):self.nom=nomself.prenom=prenomself.niveau=niveauself.profession=professionself.pv_max=self.niveau*10self.pv=self.pv_maxdefsoigner(self:'Personnage',x:int)->None:"""Le personnage regagne x points de vie"""self.pv=self.pv+x# Programme principalheros=Personnage(nom="Snow",prenom="John",niveau=20,profession="Garde de Nuit")heros.soigner(5)
1.2 Le premier paramètre doit-il toujours être nommé self ?
Rien ne vous oblige à le nommer self puisque c'est juste une convention en Python.
Nous aurions pu nommer le premier paramètre adresse_de_l_objet ou n'importe quoi d'autre : valide en terme de syntaxe mais c'est une mauvaise pratique car on s'attend à ce que ce premier paramètre se nomme self.
# Déclaration des ClassesclassPersonnage:"""Ceci est une classe permettant de créer un personnage dans mon super RPG"""def__init__(adresse_de_l_objet,nom='Aucun',prenom='Aucun',niveau=0,profession="Humain de base"):adresse_de_l_objet.nom=nomadresse_de_l_objet.prenom=prenomadresse_de_l_objet.niveau=niveauadresse_de_l_objet.profession=professionadresse_de_l_objet.pv_max=adresse_de_l_objet.niveau*10adresse_de_l_objet.pv=adresse_de_l_objet.pv_maxdefsoigner(adresse_de_l_objet:'Personnage',x:int)->None:"""Le personnage regagne x points de vie"""adresse_de_l_objet.pv=adresse_de_l_objet.pv+x# Programme principalheros=Personnage(nom="Snow",prenom="John",niveau=20,profession="Garde de Nuit")heros.soigner(5)
Documentation rapide des types
Attention, on ne peut pas utiliser le nom de la Classe directement dans la documentation rapide à l'intérieur de la Classe elle-même.
Le plus simple est de simplement fournir un string.
defsoigner(self:'Personnage',x:int)->None:
1.3 Comprendre l'appel à une méthode
Une explication détaillée
La syntaxe spécifique des objets permet d'écrire ceci
21
heros .soigner( 5 )
La sémantique est "Soigne le héros de 5 points".
Voici comment l'interprète Python gère le transfert des informations pendant cet appel :
21(appel)
-
-
-
-
14(déclaration)
15
16
heros .soigner( 5 )
│ │
│ ┴───────┐
┴─────────────┐ │
│ │
defsoigner( self , x ):"""Le personnage regagne x points de vie"""self.pv=self.pv+x
Python affecte self AUTOMATIQUEMENT au paramètre self
L'argument 5 est affecté au paramètre x
On peut donc maintenant comprendre ce que fait cette méthode : la ligne 16 montre incrémente de x l'attribut pv de l'objet transmis : on connaît son adresse car elle a été stockée dans self.
Conclusion : ce qu'il faut savoir faire
Lors d'un appel de méthode, le premier paramètre (souvent nommé self) contient AUTOMATIQUEMENT l'adresse de l'objet qui se trouve devant le point.
Exemple :
>>>heros.soigner(5)
Le paramètre self va contenir heros lors de cet appel.
1.4 Toujours préférer une modification par méthode à une modification directe
Problématique
Pourquoi créer une méthode soigner() si on peut faire pareil avec une instruction ?
>>> heros.soigner(5) # Version méthode vs>>> heros.pv = heros.pv + 5 # Version directe
Question : laisseriez-vous quelqu'un toucher directement au moteur de votre voiture pour augmenter l'attribut accélération de la voiture plutôt que d'appuyer sur la méthode pédale_d_accélération() ?
Non, pas malin de toucher à votre moteur alors que quelqu'un a déjà conçu une pédale qui fait ce que vous voulez mais de façon sécurisée ?
Interface
On nomme INTERFACE :
l'ensemble des fonctions utilisables par un utilisateur d'un module.
l'ensemble des méthodes utilisables par un utilisateur d'une classe.
l'ensemble des boutons utilisables par un utilisateur d'un instrument matériel.
Conclusion
Puisque l'utilisation des méthodes prévues par le concepteur d'une Classe permet de provoquer l'effet voulu sans risquer d'endommager les données internes de l'objet (si le concepteur a bien fait son travail), on ne doit jamais manipuler directement les attributs.
flowchart LR
U([Utilisateur]) <--> I[Interface] <--> M([Modèle : objet])
1.5 Encapsulation
flowchart LR
OB([Instance de Classe B]) <--> IA[Interface Classe A] <--> MA([Modèle A : objet])
OA([Instance de Classe A]) <--> MA
Cette notion d'INTERFACE va même souvent plus loin en programmation orientée objet réelle.
Principe de l'encapsulation
Seule une instance d'une classe A peut accéder directement aux attributs d'une instance d'une Classe A.
Une instance d'une classe B peut uniquement utiliser les méthodes d'interfaces proposées dans l'interface de la Classe A.
Traduction en français : si on veut lire ou modifier un objet, on doit lui demander poliment.
Cette notion d'encapsulation est l'un des principes fondamentaux de la programmation orientée objet (POO) et possède plusieurs niveaux :
aucune encapsulation : l'utilisateur est autorisé à modifier et lire directement les attributs
encapsulation basique : l'utilisateur doit utiliser les méthodes pour modifier les attributs mais peut les lire directement.
encapsulation moyenne : l'utilisateur doit utiliser les méthodes pour modifier et lire les attributs.
encapsulation forte : l'utilisateur n'est même pas autorisé à connaître les noms des attributs.
Analogie avec la voiture
L'utilisateur ne voit de la voiture que les méthodes qui lui permettent d'agir sur la voiture depuis l'habitacle. Il ne connait pas les attributs internes : température de moteur, puissance utilisée par le moteur, vitesse réelle de rotation du moteur...
Exemple de code sans aucune encapsulation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Déclaration des ClassesclassPersonnage:"""Ceci est une classe permettant de créer un personnage dans mon super RPG"""def__init__(self,nom='Aucun',prenom='Aucun',niveau=0,profession="Humain de base"):self.nom=nomself.prenom=prenomself.niveau=niveauself.profession=professionself.pv_max=self.niveau*10self.pv=self.pv_max# Programme principalheros=Personnage(nom="Snow",prenom="John",niveau=20,profession="Garde de Nuit")heros.pv=heros.pv+10etat = heros.pv
AVANTAGE : programmes très courts.
DESAVANTAGE : ne respecte pas le principe d'encapsulation et peut provoquer des problèmes de cohérence interne de l'objet. A partir de la ligne 18, le personnage a plus de pv que les pv max par exemple.
Exemple de code avec encapsulation forte
On a créé une méthode pour modifier les pv et une méthode qui lit cet attribut et renvoie juste un pourcentage lié à l'état du personnage : l'utilisateur ne connait même pas la valeur exacte des points de vie.
à 0, on sait que le personnage n'a plus de point de vie,
# Déclaration des ClassesclassPersonnage:"""Ceci est une classe permettant de créer un personnage dans mon super RPG"""def__init__(self,nom='Aucun',prenom='Aucun',niveau=0,profession="Humain de base"):self.nom=nomself.prenom=prenomself.niveau=niveauself.profession=professionself.pv_max=self.niveau*10self.pv=self.pv_maxdefsoigner(self,x):"""Le personnage regagne x points de vie"""self.pv=self.pv+xdefobtenir_etat(self):"""Renvoie le pourcentage de forme du personnage, 0 si HS et 100 si pleine forme"""returnint(self.pv/self.pv_max*100)
# Programme principalheros=Personnage(nom="Snow",prenom="John",niveau=20,profession="Garde de Nuit")heros.soigner(10)etat = heros.obtenir_etat()
AVANTAGE : respecte le principe d'encapsulation.
DESAVANTAGE : demande de créer beaucoup de méthodes, code plus long à créer.
VOCABULAIRE HORS PROGRAMME : accesseur et mutateur
Remarque préalable
La notion d'encapsulation n'est pas au programme de NSI.
Dans les sujets du BAC, on travaille donc sans encapsulation : on peut lire et modifier directement attributs des objet même depuis l'extérieur. Cela permet d'obtenir des programmes plus courts.
Attention : cela reste une mauvaise pratique de POO.
Complément de vocabulaire
L'encapsulation impose de modifier et lire les attributs via des méthodes. Il existe donc deux grandes familles de méthodes :
Les mutateurs
Ces méthodes permettent de modifier les attributs de l'objet. On leur fait la demande, elles vérifient si la demande est valide et agissent en conséquence.
Leurs noms commencent souvent par modifier_x(), regler_x(), configurer_x(), ou set_x() en anglais.
Dans l'exemple, soigner() est un mutateur.
Les accesseurs
Ces méthodes permettent de lire les attributs de l'objet. On leur fait la demande, elles lisent les attributs et fournissent une réponse.
Leurs noms commencent souvent par lire_x(), obtenir_x(), recuperer_x()., ou get_x() en anglais.
# Déclaration des ClassesclassPersonnage:"""Ceci est une classe permettant de créer un personnage dans mon super RPG"""def__init__(self,nom='Aucun',prenom='Aucun',niveau=0,profession="Humain de base"):self.nom=nomself.prenom=prenomself.niveau=niveauself.profession=professionself.pv_max=self.niveau*10self.pv=self.pv_maxdefmystere(self,degats):"""Mystère mystère"""self.pv=self.pv-degats# Instructions du programme principalheros=Personnage(nom="Snow",prenom="John",niveau=20,profession="Garde de Nuit")
...CORRECTION...
Lors de la création, on envoie un niveau correspondant à 10 (voir la ligne 20).
Du coup, lors de l'initialisation, les points de vie maximum vont être égaux à 10*20, soit 200 (voir ligne 11).
L'attribut pv est alors également initialisé à cette valeur de 200 sur la ligne 12.
La méthode mystere() diminue l'attribut correspondant aux points de vie du nombre correspondant à degats. Par contre, on ne touche pas aux points de vie maximum.
En supposant que degats soit bien un entier car il n'y a aucune documentation.
02° Vérifier vos réponses à l'aide des instructions interactives suivantes :
03° Cette instruction est-elle similaire à l'appel heros.mystere(5) ?
>>> heros.pv = heros.pv - 5
...CORRECTION...
Non : il se passe exactement la même chose (on diminue l'attribut pv de 5 points). Mais avec la méthode, on respecte le principe d'encapsulation (on formule la demande à l'objet) alors qu'ici, on manipule directement le modèle de l'objet.
04° Réaliser la méthode d'interface modifier_pv() : on veut rajouter le modificateur à la valeur courante de l'attribut pv. Ensuite
si ce nombre courant dépasse la valeur maximale pv_max, on le limite à cette valeur maximale.
si ce nombre de pv (points de vie) devient négatif, on le ramène à 0.
On vous fournit un exemple dans la documentation. Le module doctest permettra donc de tester les exemples fournis dans la documentation.
classPersonnage:"""Ceci est une classe permettant de créer un personnage dans mon super RPG"""def__init__(self,nom='Aucun',prenom='Aucun',niveau=0,profession="Humain de base"):""" :: param nom(str) :: le nom du personnage :: param prenom(str) :: le prénom du personnage :: param niveau(int) :: un entier positif (donc 0 autorisé) :: param classe(str) :: l'une des classes autorisées """# Attributs stockant les paramètres reçusself.nom=nomself.prenom=prenomself.niveau=niveauself.profession=profession# Attributs calculés à partir des autres paramètresself.pv_max=self.niveau*10self.pv=self.pv_maxdefmodifier_pv(self,modificateur):"""Modifie l'attribut de pv en lui rajoutant modificateur (qui peut être négatif) :: param self(Personnage) :: l'instance sur laquelle on agit :: param modificateur(int) :: le modificateur, positif ou négatif :: return None :: "procédure" .. effet de bord :: modifie l'objet par effet de bord .. POSTCONDITION :: 0 ≤ self.pv ≤ self.pv_max
.. exemples .. >>> heros_test = Personnage(niveau=10) >>> heros_test.pv 100 >>> heros_test.modifier_pv(50) >>> heros_test.pv 100 >>> heros_test.modifier_pv(-5) >>> heros_test.pv 95 >>> heros_test.modifier_pv(-10) >>> heros_test.pv 85 >>> heros_test.modifier_pv(-100) >>> heros_test.pv 0 """ pass# Programme principalif__name__=='__main__':importdoctestdoctest.testmod()heros=Personnage(nom="Snow",prenom="John",niveau=20,profession="Garde de Nuit")
classPersonnage:"""Ceci est une classe permettant de créer un personnage dans mon super RPG"""def__init__(self,nom='Aucun',prenom='Aucun',niveau=0,profession="Humain de base"):""" :: param nom(str) :: contient le nom du personnage :: param prenom(str) :: contient le prénom du personnage :: param niveau(int) :: un entier positif (donc 0 autorisé) :: param classe(str) :: l'une des classes autorisées """# Attributs stockant les paramètres reçusself.nom=nomself.prenom=prenomself.niveau=niveauself.profession=profession# Attributs calculés à partir des autres paramètresself.pv_max=self.niveau*10self.pv=self.pv_maxdefmodifier_pv(self,modificateur):"""Modifie l'attribut de pv en lui rajoutant modificateur (qui peut être négatif) :: param self(Personnage) :: l'instance sur laquelle on agit :: param modificateur(int) :: le modificateur, positif ou négatif :: return None :: "procédure" .. effet de bord :: modifie l'objet par effet de bord :: exemple pouvant servir à doctest :: >>> heros_test = Personnage(niveau=10) >>> heros_test.pv 100 >>> heros_test.modifier_pv(50) >>> heros_test.pv 100 >>> heros_test.modifier_pv(-5) >>> heros_test.pv 95 >>> heros_test.modifier_pv(-10) >>> heros_test.pv 85 >>> heros_test.modifier_pv(-100) >>> heros_test.pv 0 """pv_modifies=self.pv+modificateurifpv_modifies<0:self.pv=0elifpv_modifies>self.pv_max:self.pv=self.pv_maxelse:self.pv=pv_modifies# Instructions du programme principalif__name__=='__main__':importdoctestdoctest.testmod()heros=Personnage(nom="Snow",prenom="John",niveau=20,profession="Garde de Nuit")
05° Mettre le programme de la correction précédente en mémoire de façon à avoir la variable-objet heros en mémoire.
Taper les instructions ci-dessous de façon à vérifier qu'on parvient bien à agir sur cette variable depuis l'extérieur.
06° Dire si chacune des instructions suivantes est :
Possible avec Python mais pas légitime du point de vue de l'encapsulation
Possible et légitime si on tente de respecter le principe d'encapsulation qu'on vient de voir
Impossible et renvoie une erreur
On considère que la classe Personnage est en mémoire et qu'il existe donc bien une variable heros faisant référence à une instance de la classe Personnage.
Voici les instructions
Instruction 1 : on tape cette instruction dans la console (l'instruction se situe donc à l'extérieur de l'objet) :
>>> heros.pv = 50
Instruction 2 : on place cela dans l'une des méthodes (l'instruction se situe donc à l'intérieur de l'objet) :
self.niveau=50
...CORRECTION...
Instruction 1 : on tape cette instruction dans la console :
>>> heros.pv = 50
Possible en Python.
Par contre, cette instruction ne respecte pas l'encapsulation : on laisse l'utilisateur modifier l'objet sans passer par l'une des méthodes d'inferface créée pour vérifier et gérer sa demande.
Instruction 2 : on place cela dans l'une des méthodes
self.niveau=50
Possible et légitime en POO : on modifie les attributs de l'objet depuis l'intérieur de son propre code.
Continuons à créer nos personnages et leur permettre d'interagir entre eux. Comme il s'agit d'un jeu de combat, cela va principalement consister à se mettre des coups sur la tête.
Les personnages vont devoir avoir 4 nouveaux attributs qu'on veut initialiser de cette façon.
Un attribut attaque qui va servir à savoir si le personnage parvient à porter un coup à un autre personnage.
Un attribut esquive qui va servir à savoir si le personnage parvient à esquiver le coup qu'un autre personnage lui porte.
Un attribut puissance qui va correspondre aux nombres de dégats effectifs si le coup est bien porté.
Un attribut protection qu'on va retrancher aux dégats reçus par le personnage.
Ces attributs vont être calculés une première fois à la création du personnage mais si le personnage évolue, il va falloir les recalculer. On décide donc de créer une méthode mise_a_jour() qui va contenir l'ensemble des calculs et modifications à effectuer.
Voici le programme sur lequel vous allez travailler :
classPersonnage:"""Ceci est une classe permettant de créer un personnage dans mon super RPG"""def__init__(self,nom='Aucun',prenom='Aucun',niveau=0,profession="Humain de base"):""" :: param nom(str) :: contient le nom du personnage :: param prenom(str) :: contient le prénom du personnage :: param niveau(int) :: un entier positif (donc 0 autorisé) :: param classe(str) :: l'une des classes autorisées """# Attributs stockant les paramètres reçusself.nom=nomself.prenom=prenomself.niveau=niveauself.profession=profession# Liste des autres attributs associésself.pv_max=0# Valeur maximale de la vie self.pv=0# Valeur restanteself.attaque=0# Capacité à porter un coupself.esquive=0# Capacité à esquiver un coupself.puissance=0# Force des dégats sur un coup portéself.protection=0# Réduction sur un coup reçu# Initialisation des attributs associésself.mise_a_jour()self.pv=self.pv_maxdefmise_a_jour(self):"""Calcule les valeurs des différents attributs"""self.pv_max=self.niveau*10ifself.profession=='Jedi':self.protection=self.niveauelse:self.protection=self.niveau//2defmodifier_pv(self,modificateur):"""Modifie l'attribut de pv en lui rajoutant modificateur :: param self(Personnage) :: l'instance sur laquelle on agit :: param modificateur(int) :: le modificateur, positif ou négatif :: return None :: "procédure" .. effet de bord :: modifie self par effet de bord .. exemples .. >>> heros_test = Personnage(niveau=10) >>> heros_test.pv 100 >>> heros_test.modifier_pv(50) >>> heros_test.pv 100 >>> heros_test.modifier_pv(-5) >>> heros_test.pv 95 >>> heros_test.modifier_pv(-10) >>> heros_test.pv 85 >>> heros_test.modifier_pv(-100) >>> heros_test.pv 0 """pv_modifies=self.pv+modificateurifpv_modifies<0:self.pv=0elifpv_modifies>self.pv_max:self.pv=self.pv_maxelse:self.pv=pv_modifiesdefsubit_des_degats(self,degats):"""Active modifier_pv de façon à provoquer des dégats"""passdefparvient_a_blesser(self,cible):"""Le personnage lance une attaque contre la cible. Renvoie True si la cible subit des dégâts, False sinon :: param self(Personnage) :: l'instance qui attaque :: param cible(Personnage) :: l'instance qui se défend, différente de la première ! :: return (bool) :: True si cible touchée """passdefobtenir_etat(self):"""Renvoie un string décrivant l'état du personnage"""return''# Instructions du programme principalif__name__=='__main__':importdoctestdoctest.testmod()heros1=Personnage(nom="Snow",prenom="John",niveau=20,profession="Garde de Nuit")heros2=Personnage(nom="Skywalker",prenom="Luke",niveau=8,profession="Jedi")
✎ 07° Expliquer pourquoi on visualise les valeurs suivantes dans la console.
✎ 08° Expliquer pourquoi on doit placer self suivi d'un point sur la ligne 27 pour activer la méthode mise_a_jour() sur l'objet en cours de traitement.
09° Finaliser la méthode mise_a_jour().
Un "Jedi" possède son niveau en attaque, en esquive, en puissance et en protection. Cette classe de personnage est totalement déséquilibrée !
Un "Rebelle" possède
son niveau en attaque et en puissance
la moitié de son niveau en esquive et en protection.
Un "Stormtrooper" possède la moitié de son niveau partout. Ils tombent comme des mouches dans les films !
Pour n'importe quelle autre profession (même "Garde de Nuit"), on prendra 1/3 du niveau dans les 4 attributs. De toutes manières, un gars avec une épée face à une épée laser, ça risque de ne pas durer longtemps.
defmise_a_jour(self):"""Calcule les valeurs des différents attributs"""self.pv_max=self.niveau*10ifself.profession=='Jedi':self.attaque=self.niveauself.esquive=self.niveauself.puissance=self.niveauself.protection=self.niveauelifself.profession=='Rebelle':self.attaque=self.niveauself.esquive=self.niveau//2self.puissance=self.niveauself.protection=self.niveau//2elifself.profession=='Stormtrooper':self.attaque=self.niveau//2self.esquive=self.niveau//2self.puissance=self.niveau//2self.protection=self.niveau//2else:self.attaque=self.niveau//3self.esquive=self.niveau//3self.puissance=self.niveau//3self.protection=self.niveau//3
10° Finaliser la méthode subit_des_degats(). Cette méthode reçoit un paramètre degats. Si degats est positif, on lance alors un appel intelligent à modifier_pv() pour diminuer les pv du personnage.
Sinon, on ne fait rien : on ne peut pas subir de dégâts négatifs. Cela reviendrait à guérir !
Il faudra également finaliser la documentation, très incomplète pour le moment.
Vous pourrez tester de cette façon dans la console :
defsubit_des_degats(self,degats):"""Active modifier_pv de façon à provoquer des dégats"""ifdegats>0:self.modifier_pv(-degats)
11° Rajouter la documentation de la méthode précédente (comprenant un exemple pour le module doctest). Vous pouvez vous inspirer du test de la méthode modifier_pv().
N'oubliez pas la ligne vide après le dernier test. Sinon doctest pensera qu'il y a autre chose à afficher sur la console.
12° Question théorique : que contient le paramètre self lors de cet appel ? Que contient le paramètre cible ?
>>> heros1.parvient_a_blesser(heros2)
...CORRECTION...
>>> heros1.parvient_a_blesser(heros2)
1
defparvient_a_blesser(self,cible):
Cela veut dire : va chercher l'adresse de l'objet heros1 et active sa méthode parvient_a_blesser.
Le paramètre automatique self fait donc référence à heros1.
Le paramètre cible fait donc référence lui à heros2.
C'est comme si nous avions tapé ceci :
>>> Personnage.parvient_a_blesser(heros1, heros2)
Dans la suite des explications, 1d20 fait référence à un dé à 20 faces : il peut donc donner un résultat entier entier dans [1;20].
1
2
importrandomd20=random.randint(1,20)# d20 contient alors une valeur entière dans [1,20]
On considère la règle du jeu suivante :
Si (attaque de l'attaquant + 1d20) est supérieur à (esquive du défenseur + 10) :
on diminue les pv du défenseur de (20 + puissance de l'attaquant * 2 - protection du défenseur // 2)
Sinon si (attaque de l'attaquant + 1d20) est supérieur à esquive du défenseur :
on diminue les pv du défenseur de (10 + puissance de l'attaquant - protection du défenseur)
Sinon :
on ne fait rien, l'attaque rate
On notera que les dégâts pourraient être négatifs dans certains cas. C'est pour cela qu'il est important de ne gérer que les valeurs positives de dégâts.
13° Rajouter l'importation du module en début de programme puis compléter la méthode parvient_a_blesser() pour qu'elle fasse le travail demandé. La perte de pv devra bien entendu être réalisée via la méthode subit_des_degats() qui fait appel elle-même à modifier_pv().
Pour l'instant, la méthode parvient_a_blesser() ne fait rien, à part tirer un nombre aléatoire entre 1 et 20 et placer le résultat dans une variable.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
importrandomclassPersonnage:"""Ceci est une classe permettant de créer un personnage dans mon super RPG""".........defparvient_a_blesser(self,cible):"""Le personnage lance une attaque contre la cible. Renvoie True si la cible subit des dégâts, False sinon :: param self(Personnage) :: l'instance qui attaque :: param cible(Personnage) :: l'instance qui se défend, différente de la première ! :: return (bool) :: True si cible touchée """d20=random.randint(1,20)# d20 contient alors une valeur entière dans [1,20]
Voici un exemple d'utilisation (pensez à utiliser la flèche VERS LE HAUT pour retrouver vos dernières commandes plutôt que de les retaper à la main) :
importrandomclassPersonnage:"""Ceci est une classe permettant de créer un personnage dans mon super RPG"""def__init__(self,nom='Aucun',prenom='Aucun',niveau=0,profession="Humain de base"):""" :: param nom(str) :: contient le nom du personnage :: param prenom(str) :: contient le prénom du personnage :: param niveau(int) :: un entier positif (donc 0 autorisé) :: param classe(str) :: l'une des classes autorisées """# Attributs stockant les paramètres reçusself.nom=nomself.prenom=prenomself.niveau=niveauself.profession=profession# Liste des autres attributs associésself.pv_max=0# Valeur maximale de la vie self.pv=0# Valeur restanteself.attaque=0# Capacité à porter un coupself.esquive=0# Capacité à esquiver un coupself.puissance=0# Force des dégats sur un coup portéself.protection=0# Réduction sur un coup reçu# Initialisation des attributs associésself.mise_a_jour()self.pv=self.pv_maxdefmise_a_jour(self):"""Calcule les valeurs des différents attributs"""self.pv_max=self.niveau*10ifself.profession=='Jedi':self.attaque=self.niveauself.esquive=self.niveauself.puissance=self.niveauself.protection=self.niveauelifself.profession=='Rebelle':self.attaque=self.niveauself.esquive=self.niveau//2self.puissance=self.niveauself.protection=self.niveau//2elifself.profession=='Stormtrooper':self.attaque=self.niveau//2self.esquive=self.niveau//2self.puissance=self.niveau//2self.protection=self.niveau//2else:self.attaque=self.niveau//3self.esquive=self.niveau//3self.puissance=self.niveau//3self.protection=self.niveau//3defmodifier_pv(self,modificateur):"""Modifie l'attribut de pv en lui rajoutant modificateur (qui peut être négatif) :: param self(Personnage) :: l'instance sur laquelle on agit :: param modificateur(int) :: le modificateur, positif ou négatif :: return None :: "procédure" .. effet de bord :: modifie l'objet par effet de bord :: exemple pouvant servir à doctest :: >>> heros_test = Personnage(niveau=10) >>> heros_test.pv 100 >>> heros_test.modifier_pv(50) >>> heros_test.pv 100 >>> heros_test.modifier_pv(-5) >>> heros_test.pv 95 >>> heros_test.modifier_pv(-10) >>> heros_test.pv 85 >>> heros_test.modifier_pv(-100) >>> heros_test.pv 0 """pv_modifies=self.pv+modificateurifpv_modifies<0:self.pv=0elifpv_modifies>self.pv_max:self.pv=self.pv_maxelse:self.pv=pv_modifiesdefsubit_des_degats(self,degats):"""Active modifier_pv de façon à provoquer des dégats :: param self(Personnage) :: l'instance sur laquelle on agit :: param degats(int) :: les dégats qu'on veut infliger, sans précondition de signe :: return None :: "procédure" .. effet de bord :: modifie self par effet de bord .. exemples .. >>> h = Personnage(niveau=20) >>> h.pv 200 >>> h.subit_des_degats(10) >>> h.pv 190 >>> h.subit_des_degats(-20) >>> h.pv 190"""ifdegats>0:self.modifier_pv(-degats)defparvient_a_blesser(self,cible):"""Le personnage lance une attaque contre la cible. Renvoie True si la cible subit des dégâts, False sinon :: param self(Personnage) :: l'instance qui attaque :: param cible(Personnage) :: l'instance qui se défend, différente de la première ! :: return (bool) :: True si cible touchée """degats=0d20=random.randint(1,20)# d20 contient alors une valeur entière dans [1,20]if(d20+self.attaque)>(10+cible.esquive):degats=20+self.puissance*2-cible.protection//2cible.subit_des_degats(degats)elif(d20+self.attaque)>cible.esquive:degats=10+self.puissance-cible.protectioncible.subit_des_degats(degats)returndegats>0defobtenir_etat(self):"""Renvoie un string décrivant l'état du personnage"""return''# Instructions du programme principalif__name__=='__main__':importdoctestdoctest.testmod()heros1=Personnage(nom="Snow",prenom="John",niveau=20,profession="Garde de Nuit")heros2=Personnage(nom="Skywalker",prenom="Luke",niveau=8,profession="Jedi")
Nous avons déjà vu des mutateurs dans les questions précédentes. Ce qui nous manque, ce sont des exemples d'accesseurs. Pour l'instant, le seul moyen de connaître le nombre de PV est d'interroger directement l'objet :
>>> heros2.pv
80
Voyons comment réaliser une méthode-accesseur facilement avec Python.
14° Reprendre si besoin la correction de la classe fournie sur la question précédente. Compléter la méthode obtenir_etat() pour qu'elle renvoie un string correspondant à la valeur de l'attribut pv de l'objet.
...CORRECTION...
1
2
3
4
5
defobtenir_etat(self):"""Renvoie un string décrivant l'état du personnage"""returnstr(self.pv)
L'intérêt de faire cela ?
Déjà, cela permet de lire le contenu d'un attribut sans connaitre son nom : c'est donc très utile pour l'encapsulation.
Ensuite, on peut modifier la valeur : pas besoin de dire la vérité. On pourrait par exemple renvoyer un pourcentage des pv restants plutôt que la valeur brute.
Par exemple, on pourrait dire que :
De 0 à 25 % des points de vie : le personnage est "Blessé".
De 25 à 75 % des points de vie : le personnage est "Fatigué".
A plus de 75%, le personnage est "En forme".
✎ 15° On veut maintenant que notre méthode obtenir_etat() renvoie un string informatif plutôt que la valeur des points de vie :
"HS" si les PV sont à 0
"Pleine forme" si les PV dépassent 75% du maximum possible
"Blessé" si les PV sont inférieurs à 25% du maximum possible
Sinon "Fatigué"
De cette façon, le joueur n'a plus aucune connaissance de la valeur exacte de ses points de vie. Il obtient juste une indication à travers la méthode accesseur.
✎ 16° Un élève présente cette séquence et affirme que c'est équivalent au travail que vous avez fourni sur la question précédente. Il se trompe. Sa version n'est pas valide car elle ne fonctionne pas toujours. Mais pourquoi ?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
defobtenir_etat(self):"""Renvoie un string décrivant l'état du personnage"""p=self.pv/self.pv_maxifself.pv==0:reponse="HS"elifp>0.25:reponse="Fatigué"elifp>0.75:reponse="En forme"else:reponse="Blessé"returnreponse
Rappel : tester le type d'un objet
>>> type(heros1)
<class '__main__.Personnage'>
Pour rappel, voilà comment tester le type d'une variable faisant référence (ou pas) à un objet.
Dans le cadre de la NSI, il est préférable d'utiliser simplement la fonction native type() puisque vous la connaissez déjà. isinstance() fait exactement la même chose en l'état actuel de vos connaissances.
Maintenant que nous avons notre Classe, nous allons pouvoir l'utiliser pour réaliser des programmes les utilisant. Par exemple, nous pourrions réaliser un programme où on désigne deux combattants et qui décrit le combat.
17° Enregistrer le programme sous le nom mes_classes.py de façon à en faire un module.
importrandomclassPersonnage:"""Ceci est une classe permettant de créer un personnage dans mon super RPG"""def__init__(self,nom='Aucun',prenom='Aucun',niveau=0,profession="Humain de base"):""" :: param nom(str) :: contient le nom du personnage :: param prenom(str) :: contient le prénom du personnage :: param niveau(int) :: un entier positif (donc 0 autorisé) :: param classe(str) :: l'une des classes autorisées """# Attributs stockant les paramètres reçusself.nom=nomself.prenom=prenomself.niveau=niveauself.profession=profession# Liste des autres attributs associésself.pv_max=0# Valeur maximale de la vie self.pv=0# Valeur restanteself.attaque=0# Capacité à porter un coupself.esquive=0# Capacité à esquiver un coupself.puissance=0# Force des dégats sur un coup portéself.protection=0# Réduction sur un coup reçu# Initialisation des attributs associésself.mise_a_jour()self.pv=self.pv_maxdefmise_a_jour(self):"""Calcule les valeurs des différents attributs"""self.pv_max=self.niveau*10ifself.profession=='Jedi':self.attaque=self.niveauself.esquive=self.niveauself.puissance=self.niveauself.protection=self.niveauelifself.profession=='Rebelle':self.attaque=self.niveauself.esquive=self.niveau//2self.puissance=self.niveauself.protection=self.niveau//2elifself.profession=='Stormtrooper':self.attaque=self.niveau//2self.esquive=self.niveau//2self.puissance=self.niveau//2self.protection=self.niveau//2else:self.attaque=self.niveau//3self.esquive=self.niveau//3self.puissance=self.niveau//3self.protection=self.niveau//3defmodifier_pv(self,modificateur):"""Modifie l'attribut de pv en lui rajoutant modificateur (qui peut être négatif) :: param self(Personnage) :: l'instance sur laquelle on agit :: param modificateur(int) :: le modificateur, positif ou négatif :: return None :: "procédure" .. effet de bord :: modifie l'objet par effet de bord :: exemple pouvant servir à doctest :: >>> heros_test = Personnage(niveau=10) >>> heros_test.pv 100 >>> heros_test.modifier_pv(50) >>> heros_test.pv 100 >>> heros_test.modifier_pv(-5) >>> heros_test.pv 95 >>> heros_test.modifier_pv(-10) >>> heros_test.pv 85 >>> heros_test.modifier_pv(-100) >>> heros_test.pv 0 """pv_modifies=self.pv+modificateurifpv_modifies<0:self.pv=0elifpv_modifies>self.pv_max:self.pv=self.pv_maxelse:self.pv=pv_modifiesdefsubit_des_degats(self,degats):"""Active modifier_pv de façon à provoquer des dégats :: param self(Personnage) :: l'instance sur laquelle on agit :: param degats(int) :: les dégats qu'on veut infliger, sans précondition de signe :: return None :: "procédure" .. effet de bord :: modifie self par effet de bord .. exemples .. >>> h = Personnage(niveau=20) >>> h.pv 200 >>> h.subit_des_degats(10) >>> h.pv 190 >>> h.subit_des_degats(-20) >>> h.pv 190"""ifdegats>0:self.modifier_pv(-degats)defparvient_a_blesser(self,cible):"""Le personnage lance une attaque contre la cible. Renvoie True si la cible subit des dégâts, False sinon :: param self(Personnage) :: l'instance qui attaque :: param cible(Personnage) :: l'instance qui se défend, différente de la première ! :: return (bool) :: True si cible touchée """degats=0d20=random.randint(1,20)# d20 contient alors une valeur entière dans [1,20]if(d20+self.attaque)>(10+cible.esquive):degats=20+self.puissance*2-cible.protection//2cible.subit_des_degats(degats)elif(d20+self.attaque)>cible.esquive:degats=10+self.puissance-cible.protectioncible.subit_des_degats(degats)returndegats>0defobtenir_etat(self):"""Renvoie un string décrivant l'état du personnage"""p=self.pv/self.pv_maxifself.pv==0:reponse="HS"elifp>0.75:reponse="En forme"elifp>0.25:reponse="Fatigué"else:reponse="Blessé"returnreponsedefobtenir_nom(self):returnself.nom# Instructions du programme principalif__name__=='__main__':importdoctestdoctest.testmod()heros1=Personnage(nom="Snow",prenom="John",niveau=20,profession="Garde de Nuit")heros2=Personnage(nom="Skywalker",prenom="Luke",niveau=8,profession="Jedi")
18° Enregistrer le programme suivant dans le même répertoire que le module précédent.
Lancer et expliquer le fonctionnement de ce programme.
Si la moindre ligne pose problème, pensez à faire appel à l'enseignant.
frommes_classesimportPersonnagedefdecrire_debut(c1,c2):print(f"Le combat commence entre {c1.obtenir_nom()} et {c2.obtenir_nom()}")defdecrire_attaque(a,b):print(f"{a.obtenir_nom()} parvient à toucher {b.obtenir_nom()} dont l'état est maintenant {b.obtenir_etat()}")defdecrire_conclusion(a):print(f"\n{a.obtenir_nom()} a gagné le duel et est maintenant {a.obtenir_etat()}.")defduel(adv1,adv2):"""Décrit le duel entre adv1 et adv2. Attention, cela modifie les combattants."""decrire_debut(adv1,adv2)attaquant=adv1defenseur=adv2whileadv1.obtenir_etat()!="HS"andadv2.obtenir_etat()!="HS":ifattaquant.parvient_a_blesser(defenseur):decrire_attaque(attaquant,defenseur)temp=attaquantattaquant=defenseurdefenseur=tempifadv1.obtenir_etat()=="HS":gagnant=adv2else:gagnant=adv1decrire_conclusion(gagnant)heros1=Personnage(nom="Snow",prenom="John",niveau=15,profession="Garde de Nuit")heros2=Personnage(nom="Skywalker",prenom="Luke",niveau=8,profession="Jedi")duel(heros1,heros2)
Le programme précédent respecte l'encapsulation et la distinction entre fonction d'affichage et fonctions de gestion des données. Voici un programme qui fait la même chose mais sans respect réel de l'encapsulation, ni de la distinction entre affichage et données.
19° Enregistrer le nouveau programme dans le même répertoire que le module précédent.
Lancer et vérifier qu'il provoque le même comportement externe que le programme de la question précédente.
frommes_classesimportPersonnagedefduel(adv1,adv2):"""Décrit le duel entre adv1 et adv2. Attention, cela modifie les combattants."""print(f"Le combat commence entre {adv1.nom} et {adv2.nom}")attaquant=adv1defenseur=adv2whileadv1.pv>0andadv2.pv>0:ifattaquant.parvient_a_blesser(defenseur):print(f"{attaquant.nom} parvient à toucher {defenseur.nom} dont l'état est maintenant {defenseur.obtenir_etat()}")temp=attaquantattaquant=defenseurdefenseur=tempifadv1.pv==0:print(f"\n{adv2.nom} a gagné le duel et est maintenant {adv2.obtenir_etat()}.")else:print(f"\n{adv1.nom} a gagné le duel et est maintenant {adv1.obtenir_etat()}.")heros1=Personnage(nom="Snow",prenom="John",niveau=15,profession="Garde de Nuit")heros2=Personnage(nom="Skywalker",prenom="Luke",niveau=8,profession="Jedi")duel(heros1,heros2)
🏠 Exercices supplémentaires° Réaliser les exercices supplémentaires fournis.
Aucune connaissance exigible à ce sujet pour le BAC. Seule __init__() est clairement à connaître.
Par contre, savoir qu'elles existent peu être bien pratiques lors des projets.
Nous allons voir ici deux nouvelles méthodes spéciales.
Méthode_add__()
La méthode _add__() permet de définir ce qu'on doit faire lorsqu'on tente de faire instance + ?.
Imaginons qu'on veuille créer la signature suivante :
Personnage + Personnage -> bool
La sémantique correspondrait au fait que le premier personnage tente d'attaquer le second.
Il suffit alors de rajouter les lignes dans la Classe :
1
2
3
4
5
6
7
classPersonnage:"""Ceci est une classe permettant de créer un personnage dans mon super RPG"""...def__add__(self,cible):returnself.parvient_a_blesser(cible)
Une fois en mémoire, on pourra alors utiliser l'opérateur + pour signaler qu'un personnage veut en attaquer un autre.
>>> heros1 + heros2
True
Comme vous le voyez, cette méthode est spéciale car on ne l'active pas à l'appellant directement : c'est l'interpréteur Python qui active __add__() lorsqu'on veut utiliser + avec une instance de Personnage.
Méthode_str__()
Avec cette méthode spéciale, nous allons pouvoir donner l'affichage qu'on désire obtenir lorsqu'on utilise la fonction native str().
1
2
3
4
5
6
7
classPersonnage:"""Ceci est une classe permettant de créer un personnage dans mon super RPG"""...def__str__(self):returnf"{self.prenom}{self.nom} est {self.profession} niveau {self.niveau}"
Une fois en mémoire, on pourra alors utiliser la fonction native str() puisqu'on vient d'expliciter le string à renvoyer dans ce cas..
>>> str(heros1)
'John Snow est Garde de Nuit niveau 20'
Comme vous le voyez, cette méthode est spéciale car on ne l'active pas à l'appellant directement : c'est l'interpréteur Python qui active __str__() lorsqu'on veut utiliser str() avec une instance de Personnage.
D'ailleurs, que fait l'interpréteur Python lorsqu'on utilise print() ? Il demande d'abord à str() de lui fournir la représentation string de l'argument qu'il a reçu. En conséquence, si on fournit un Personnage à la fonction native print(), elle va faire appel à str() qui va faire appel à __str__() si on l'a configuré.
>>> heros1
<__main__.Personnage object at 0x7ff00c5b7828>>>> print(heros1)
John Snow est Garde de Nuit niveau 20
20 - optionnelle° Rajouter ces deux méthodes dans votre classe Personnage. Plus qu'à tester pour voir si cela fonctionne bien comme prévu. Normalement oui.
5 - Partie très très optionelle : Privé-Public avec Python
Une partie à ne faire que si vous avez vraiment le temps en étant vraiment très en avance. Ce n'est pas au programme puisqu'on rentre dans des détails de Python.
Dans Python, il n'y a pas vraiment possibilité de créer des attributs privés ou des méthodes privées (inaccessibles à l'utilisateur). Ce langage prend le parti que quelqu'un qui a accès au code source peut de toutes manières casser le fonctionnement du code s'il le désire.
D'autres langages (Java ou C++ par exemple) permettent de définir clairement certains attributs et méthodes comme étant privés.
Faire semblant d'être privé dans Python
Semblant car on peut détourner la protection sans problème si on a accés au code. Voir la FAQ pour plus de détails.
Pour "interdire" l'accès aux attributs ou aux méthodes qu'on estime être privés, il suffit de changer leurs noms : il faut placer deux underscores devant. Pas devant et derrière. Juste devant.
Exemple :
1
2
3
4
5
6
7
8
9
10
11
12
13
classInvisible:def__init__(self):self.__invisible="Je suis invisible de l'extérieur"self.visible="Moi, tout le monde me voit"defaffiche_toi(self):print(self.__invisible)def__paspossible(self):print("On ne peut m'activer que de l'intérieur !")defpossible(self):print("On peut m'activer de l'extérieur")
21 (optionnelle)° Utiliser les instructions suivantes dans la console pourvoir la différence entre l'utilisation de deux underscores dans le nom d'un attribut ou d'une méthode.
Question : expliquer pourquoi on parvient à avoir un affichage lorsqu'on utilise la méthode affiche_toi() ?
>>> toto = Invisible()
>>> toto.visible
'Moi, tout le monde me voit'>>> toto.possible()
On peut m'activer de l'extérieur>>> toto.__invisible
AttributeError: 'Invisible' object has no attribute '__invisible'>>> toto.affiche_toi()
Je suis invisible de l'extérieur>>> toto.__paspossible()
AttributeError: 'Invisible' object has no attribute '__paspossible'
En réalité, cette pratique du préfixe __ est très rare en Python. Pourquoi ?
Ca ne rend pas vraiment pas les variables privées (voir FAQ pour si vous voulez savoir pourquoi)
Ca provoque une erreur lorsqu'on veut accéder à de telles variables
La vraie solution Python aux variables privées
Plutôt que de réellement créer un mécanisme de variables privées, Python a choisi de faire confiance aux gens qui interviennent sur le code.
Convention Python sur les attributs et méthodes "privées"
Lorsqu'un développeur veut signaler la présence d'attributs "privés" ou de méthodes "privées", il veut simplement dire qu'il ne faut pas utiliser ces attributs et ces méthodes depuis l'extérieur de la Classe.
On signale cela en nommant ces attributs et ces méthodes en rajoutant un simple underscore.
Exemple 1: méthode publique (c'est à dire qu'on peut l'utiliser depuis l'extérieur de la Classe, comme méthode d'interface par exemple)
defmodifier_pv(self,modificateur):
Exemple 2: méthode privée (c'est à dire : ne l'utilisez pas en tant que méthode-interface)
def_modifier_pv(self,modificateur):
Exemple 3 : attribut public (c'est à dire qu'on peut lire et modifier cet attribut depuis l'extérieur de la Classe)
pv
Exemple 4 : attribut privé (c'est à dire qu'on vous demande de ne pas lire ni modifier cet attribut depuis l'extérieur de la Classe)
_pv
Comme vous le voyez, en Python, "on vous demande de".
D'autres langages permettent d'imposer ce type de comportement.
Attention : ne confondez pas avec les attributs et les méthodes qui ont un double underscore au début et à la fin.
__init__() est une méthode spéciale. Elle n'est pas "privée".
__name__ est un attribut de Classe spécial. Il n'est pas "privé". Il contient le nom de la Classe.
__dict__ est un attribut d'instance spécial. Il n'est "privé" du tout. Il contient un dictionnaire dont les clés sont les noms des attributs et les valeurs le contenu des attributs.
22 (optionnelle)° Appliquer l'attribut spécial __dict__ à une instance de Personnage.
C'est notamment à cause de cet attribut spécial qu'il n'est pas possible de vraiment obtenir des attributs privés dans Python : on peut toujours trouver le nom d'un attribut et tenter d'y accéder grace à ce dictionnaire. Voir FAQ si vous voulez des détails.
J'ai lu qu'on parle de variables-membres. Qu'est-ce que c'est ?
Le vocabulaire de la POO est vaste. Cette année, on vous demande de savoir définir classe, objet, attribut et méthode. C'est déjà pas mal.
Mais il existe d'autres mots de vocabulaire qui sont parfois des synonymes, parfois des généralisations.
Pour votre culture générale :
Membre désigne l'ensemble des attributs et méthodes contenus dans une classe et ses instances.
Variable-membre est donc un synonyme d'attribut.
Fonction-membre est donc un synonyme de méthode.
les méthodes accesseurs sont les méthodes permettant à l'utilisateur d'accéder à certaines valeurs : on pourrait ainsi, non pas voir les vrais pv d'un personnage, mais avoir simplement un compteur ROUGE - ORANGE - VERT en fonction de son état.
les méthodes mutateurs sont les méthodes permettant de modifier les attributs des objets **après validation et vérification** de la demande. Ainsi, on garantit l'intégrité de l'objet.
Les membres privés sont les attributs et les méthodes qu'un utilisateur n'a pas le droit d'utiliser. Ces membres vont partie de la mécanique interne, mécanique qu'il faut préserver et surveiller.
Les membres publics forment l'interface : l'utilisateur a le droit de les utiliser. Concrétement, il s'agit donc uniquement des méthodes d'interface. Lorsqu'on veut faire un bel objet, les attributs devraient tous être privés et ne devraient pas pouvoir être modifiés directement par l'utilisateur. Il devrait devoir utiilser un accesseur pour le lire et un mutateur pour le modifier.
Des méthodes avec un double underscores devant et derrière ?
Les méthodes dont le nom commençent par deux underscores sont des méthodes spéciales. Elles jouent un rôle particulier dans le sens où l'interpréteur Python les recherche automatiquement.
Si il les trouve dans le code, il va appliquer ce qu'elles préconisent dans certaines conditions.
Quelques exemples :
__new__() : on la recherche à la construction d'un nouvel objet. Elle peut alors remplacer le code habituel de construction.
__init__() : on la recherche après la construction d'un nouvel objet. Elle peut alors de configurer les valeurs des attributs...
__add__() : on la recherche lorsqu'on tape une instruction correspondant à l'addition de deux objets. Exemple : heros1+heros2. On pourrait ainsi transformer cela en demande de combat par exemple. Nous verrons son utilisation dans le mini-projet sur le jeu de dés.
Des attributs avec un double underscores devant et derrière ?
Eux sont également spéciaux dans le sens où ils sont affectés automatiquement.
Quelques exemples :
__name__ : contient le nom de la Classe. Comme tout est objet en Python, cette variable va contenir "__main__" s'il s'agit du fichier comportant le code Python que vous venez de lancer. C'est un objet aussi à l'intérieur de Python.
__doc__ : contient la documentation de la Classe; si elle existe
__dict__ : contient le dictionnaire des attributs. Les clés sont les noms des attributs et les valeusr le contenu des attributs.
Pourquoi le double underscore ne crée pas vraiment d'attribut privé ?
Pour rappel, nommer un attribut ou une méthode en le faisant commencer par deux underscores permet de refuser son accès direct. C'est donc une sorte de grandeur privée, non ?
C'est vrai, mais c'est faux aussi :o)
Un exemple permettant de comprendre pourquoi c'est un peu vrai :
Voyons pourquoi l'attribut __visible peut être vu comme privé :
>>> toto = Invisible(50)
>>> toto.visible
100>>> toto.__visible
AttributeError: 'Invisible' object has no attribute '__visible'>>> toto.affiche_toi()
50
Comme on peut le voir, on parvient à accéder à l'attribut depuis une méthode de la Classe mais pas directement depuis l'extérieur. On peut donc considérer qu'elle est privée.
C'est vrai. Sauf si on sait qu'il existe un dictionnaire qui regroupe les noms et les valeurs des attributs des objets.
Il s'agit de l'attribut spécial __dict__.
>>> toto = Invisible(50)
>>> toto
<__main__.Invisible object at 0x7fb7d6541400>>>> toto.__dict__
{'_Invisible__visible': 50, 'visible': 100}>>> toto.__dict__['visible']
100>>> toto.__dict__['_Invisible__visible']
50
Comme vous pouvez le voir : on parvient au final à accéder au contenu sans trop de problème.
Est-ce plus sécurisé dans les autres langages ? Cela en a l'apparence car on peut créer des attributs et des méthodes réellement privées. Mais au final ,on ne peut pas empêcher quelqu'un de modifier le code source si vous lui en donnez l'accès.
Activité publiée le 07 09 2020
Dernière modification : 01 09 2022
Auteur : ows. h.