Infoforall

Python 26 : Méthodes spéciales assesseurs et mutateurs

Attention : cette activité est très très optionnelle, voir carrément hors programme : je suis donc très directif de façon à ne pas perdre trop de temps. C'est néanmoins très intéressant pour ceux qui voudront continuer en informatique.

1 - Méthode __add__

Qu'est-ce qu'une méthode spéciale en Python ?

Nous avons vu qu'il s'agit d'une méthode qui peut se lancer seule : l'interpréteur Python réagit à certains événements et va automatiquement y faire appel lorsque l'événement déclencheur survient.

Ainsi __init__ est déclenchée lors de l'exécution de la méthode spéciale __new__.

La méthode __new__ se déclenche automatiquement lorsqu'on crée une nouvelle instance (par exemple x = Personnage().

Nous avions vu qu'il existe beaucoup d'autres méthodes spéciales :

Nous allons ici voir comment gèrer l'addition pour qu'elle se transforme en lutte acharné entre les deux personnages.

Ainsi, je veux pouvoir écrire perdant = perso1+perso2 et récupérer le nom du perdant dans perdant.

Et ça tombe bien : la méthode spéciale __add__(self,perso2) se lance automatiquement lorsqu'on tente d'additionner quelque chose à un objet de la classe.

Commençons par reprendre le code basique de la classe :

#!/usr/bin/env python

# -*- coding: utf-8 -*-

import random


#########################################################

# Déclarations des classes

#########################################################

class Arme:

    "Cette classe contient les infos sur une arme"


    def __init__(self):

        self.nom='poings'

        self.degats=1

        self.type='[arme]'

        self.distance='contact'


    def presentation(self):

        "Affiche quelques unes des valeurs de l'arme"

        print('Arme : ', self.nom, ' qui fait ',self.degats, 'dégats à la distance :',self.distance)


class Armure:

    "Cette classe contient les infos sur une armure"


    def __init__(self):

        self.nom='aucune'

        self.protection=0

        self.type='[armure]'


    def presentation(self):

        "Affiche quelques unes des valeurs stockées de l'armure"

        print('Armure :', self.nom, ' qui protège de ',self.protection, 'dégats')


class Personnage:

    """Ceci est une classe permettant de créer un personnage dans mon super RPG

    Vous pouvez accéder aux propriétés suivantes :

    name (lecture et écriture) : contient le nom du personnage

    first_name (lecture) : contient le prénom du personnage

    level (lecture) : contient le niveau du personnage

    class_per (lecture) : contient la classe du personnage

    

    Vous avez également accès aux attributs suivants :

    arme : contient la référence vers une instance de la classe Arme.

    armure : contient la référence vers une instance de la classe Armure."""


    # - - - - - - - - - - - #

    # Méthodes spéciales

    # - - - - - - - - - - - #


    def __init__(self):

        self._nom='nobody'

        self._prenom='nobody'

        self._niveau=0

        self._classe='aucune classe'

        self.arme = Arme()

        self.armure = Armure()


    # - - - - - - - - - - - #

    # Propriétés

    # - - - - - - - - - - - #


    @property

    def name(self):

        "Renvoie une variable contenant le nom du personnage"

        return(self._nom)


    @name.setter

    def name(self,x):

        "Modifie le nom du personnage si le paramètre est bien un string"

        try :

            x = str(x)

            self._nom = x

        except:

            return(False)

        return(True)


    @property

    def first_name(self):

        "Renvoie une variable contenant le prenom du personnage"

        return(self._prenom)


    @property

    def level(self):

        "Renvoie une variable contenant le niveau du personnage"

        return(self._niveau)


    @property

    def class_per(self):

        "Renvoie une variable contenant la classe du personnage"

        return(self._classe)


    # - - - - - - - - - - - #

    # Méthodes

    # - - - - - - - - - - - #


    def presentation(self):

        "Affiche quelques unes des valeurs stockées dans le personnage"

        print(self._nom, ' ',self._prenom)

        print(self._classe, ' de niveau ',self._niveau)

        self.arme.presentation()

        self.armure.presentation()


    def perdre_un_niveau(self):

        "Cette méthode permet de réduire le niveau de 1"

        self._niveau +=-1


    def combat(self,adversaire):

        "Ceci est une méthode qui renvoie le nom du combattant qui perd"

        perdant =''

        test = True

        while test:

            valeur1 = random.randint(1,6)+self._niveau

            valeur2 = random.randint(1,6)+adversaire._niveau

            if valeur1>valeur2:

                perdant = adversaire._nom

                adversaire.perdre_un_niveau()

                test = False

            elif valeur2>valeur1:

                perdant = self._nom

                self.perdre_un_niveau()

                test = False

        return(perdant)

01° Rajouter la méthode suivante dans la classe Personnage :

    def __add__(self,adversaire):

        try:

            x = self.combat(adversaire)

        except:

            x = False

        return(x)

02° Rajoutons ce code sous les classes de façon à créer un code de test :

#########################################################

# Corps du programme

#########################################################


if __name__ == "__main__":

    print("** Code de test du fichier mes_classes.py **")

    perso1 = Personnage()

    perso1.name = "Solo"

    perso2 = Personnage()

    perso2.name = "Stormtrooper"

    perdant = perso1+perso2

    print("Lors du combat, {} s'est pris un mauvais coup".format(perdant))

    input("Appuyer sur ENTREE")

Et voilà. Vous venez d'implanter l'addition pour votre classe. Désormais, l'addition de deux instantes de Personnage est vu comme un combat entre les deux personnages.

2 - Méthode spéciale "Assesseur" : __getattr__

Que se passe-t-il lorsqu'on tape perso.truc ?

L'interpréteur Python fait le tour des attributs et des propriétés et s'il trouve truc, il va simplement fournir la réponse attendue.

S'il ne le trouve pas, il commence par tenter automatiquement la méthode spéciale __getattr__(self,x) où x est le nom de l'attribut recherché lorsqu'on a tapé objet.height. Ici x contiendrait donc height.

recherche d'attribut

Attention : le bout important est "s'il ne trouve pas". Cela veut dire que Python n'ira exécuter __getatttr__ que si l'attribut x n'existe pas ou qu'il s'agit d'un attribut interdit en accès direct.

Ainsi, ici, on cherche truc. On a accès à name (une propriété) ou _nom (un attribut, public pour Python, l'underscore simple c'est pour avertir le programmeur. Bref, il ne trouve pas truc. La méthode spéciale va alors se lancer automatiquement.

Imaginons que nous voulions par exemple ne plus déclencher d'erreur sur les attributs privés avec deux underscores. Nous pourrions utiliser __getattr__ pour fournir autre chose qu'une erreur !

03° Rajouter un attribut privé __surnom lors de l'initialisation via __init__.

    def __init__(self):

        self._nom='nobody'

        self._prenom='nobody'

        self._niveau=0

        self._classe='aucune classe'

        self.arme = Arme()

        self.armure = Armure()

        self.__surnom = 'newbie'

04° Lancer le code via IDLE puis utiliser le shell pour créer un nouveau personnage (perso=Personnage() et tenter d'accéder à son nom via perso.__surnom.

Vous devriez obtenir ceci une erreur puisqu'on tente d'accéder à un attribut privé depuis l'extérieur du code de la classe.

AttributeError: 'Personnage' object has no attribute '__surnom'

05° Rajouter la méthode spéciale suivante dans la défintion de Personnage() :

def __getattr__(self,x):

    print("On rentre dans la méthode speciale : Python ne reconnait pas l'attribut ",x)

06° Lancer le nouveau code via IDLE puis utiliser le shell pour créer un nouveau personnage (perso=Personnage() et tenter d'accéder à son nom via perso.__surnom.

Vous devriez obtenir ceci :

>>> perso.__surnom

On rentre dans la méthode speciale : Python ne reconnait pas l'attribut __surnom

Déjà, c'est mieux : ça ne déclenche pas d'erreur.

Et comment parvenir donc à lui faire renvoyer la bonne valeur si on tape perso.nickname alors que le vrai attribut se nomme __surnom ?

07° Tentez de modifier la méthode spéciale __getattr__ pour qu'elle puisse répondre à la problématique précédente.

Voici une solution possible :

def __getattr__(self,x):

    if x=="nickname":

        return(self.__surnom)

    else:

        print("On rentre dans la méthode speciale : Python ne reconnait pas l'attribut ",x)

On notera qu'on aurait pu faire la même chose avec une simple propriété.

Si on tape ceci :

>>> perso=Personnage()

>>> perso.__surnom

On rentre dans la fonction special : Python ne reconnait pas l'attribut __nom

>>> perso.nickname

'newbie'

Et voilà : ça fonctionne. On a à la fois de l'encapsulation et du masquage mais on fait semblant que nom. Bon, ça n'a aucun intéret en réalité mais c'est pour vous montrer la puissance des méthodes spéciales : nous avons à la fois l'avantage de la simplicité de l'accès direct avec la robustesse du masquage.

En gros, nous avons réussi à recréer une propriété : le vrai attribut se nomme __surnom mais l'utilisateur pourrait croire qu'il se nomme nickname.

L'intérêt fondamental par rapport à une propriété est que si on arrive dans __getattr__, c'est que quelque chose a peut-être merdé quelque part. Et cela vous permettra de récupérer le code et éviter qu'il ne s'arrête. Peut-être que l'arme du personnage n'existe plus et que le code n'est pas encore passé par la case "création d'une nouvelle arme" ? Au pire, cela vous permet aussi de garder en mémoire tout les accès qui ont causé des problèmes dans un fichier log. C'est important pour la maintenance.

Résumé :

On peut ainsi lire un attribut de 4 façons différentes :

  1. Un accés direct tant que l'attribut n'est pas privé (double underscore).
  2. Un accés indirect à l'aide d'une méthode assesseur de type get_x().
  3. Un accès indirect à l'aide d'une propriété (property).
  4. Un accès vraiment indirect avec la méthode spéciale __getattr__ en interceptant le fait qu'on ne trouve pas l'attribut.

3 - Méthode spéciale "Mutateur" : __setattr__

Il existe également une méthode spéciale liée à la modification d'attribut : __setattr__.

La méthode spéciale de lecture de la partie précédente, __getattr__, n'était activée que si Python ne trouvait pas l'attribut.

Ici, c'est différent : la méthode spéciale d'écriture __setattr__ est activée dès qu'on veut modifier un attribut.

De base, votre classe ne possédait pas cette méthode mais allait chercher object.__setattr__, celle de la classe basique object dans le cas de la classe Personnage.

Il faudrait donc y faire référence dans votre propre méthode, sinon plus aucune modification ne sera possible : la gestion des modifications se fait bien dans cette méthode d'object.

Autre cas problèmatique à éviter : il ne faudra jamais modifier d'attribut dans votre méthode __setattr__. Pourquoi ? Et bien, puisqu'il y a modification, elle va s'autoactiver et cela va créer une boucle de récursivité : votre __setattr__ va modifier un attribut, ce qui va lancer l'appel à votre __setattr__ qui va modifier l'attribut, ce qui va lancer l'apppel à votre __setattr__ qui va modifier l'attribut ... Vous avez compris le principe.

Nous allons travailller avec le code issu des deux parties précédentes :

#!/usr/bin/env python

# -*- coding: utf-8 -*-

import random


#########################################################

# Déclarations des classes

#########################################################

class Arme:

    "Cette classe contient les infos sur une arme"


    def __init__(self):

        self.nom='poings'

        self.degats=1

        self.type='[arme]'

        self.distance='contact'


    def presentation(self):

        "Affiche quelques unes des valeurs de l'arme"

        print('Arme : ', self.nom, ' qui fait ',self.degats, 'dégats à la distance :',self.distance)


class Armure:

    "Cette classe contient les infos sur une armure"


    def __init__(self):

        self.nom='aucune'

        self.protection=0

        self.type='[armure]'


    def presentation(self):

        "Affiche quelques unes des valeurs stockées de l'armure"

        print('Armure :', self.nom, ' qui protège de ',self.protection, 'dégats')


class Personnage:

    """Ceci est une classe permettant de créer un personnage dans mon super RPG

    Vous pouvez accéder aux propriétés suivantes :

    name (lecture et écriture) : contient le nom du personnage

    first_name (lecture) : contient le prénom du personnage

    level (lecture) : contient le niveau du personnage

    class_per (lecture) : contient la classe du personnage

    

    Vous avez également accès aux attributs suivants :

    arme : contient la référence vers une instance de la classe Arme.

    armure : contient la référence vers une instance de la classe Armure."""


    # - - - - - - - - - - - #

    # Méthodes spéciales

    # - - - - - - - - - - - #


    def __init__(self):

        self._nom='nobody'

        self._prenom='nobody'

        self._niveau=0

        self._classe='aucune classe'

        self.arme = Arme()

        self.armure = Armure()

        self.__surnom = 'newbie'


    def __getattr__(self,x):

        if x=="nickname":

            return(self.__surnom)

        else:

            print("On rentre dans la méthode speciale : Python ne reconnait pas l'attribut ",x)


    def __add__(self,adversaire):

        try:

            x = self.combat(adversaire)

        except:

            x = False

        return(x)


    # - - - - - - - - - - - #

    # Propriétés

    # - - - - - - - - - - - #


    @property

    def name(self):

        "Renvoie une variable contenant le nom du personnage"

        return(self._nom)


    @name.setter

    def name(self,x):

        "Modifie le nom du personnage si le paramètre est bien un string"

        try :

            x = str(x)

            self._nom = x

        except:

            return(False)

        return(True)


    @property

    def first_name(self):

        "Renvoie une variable contenant le prenom du personnage"

        return(self._prenom)


    @property

    def level(self):

        "Renvoie une variable contenant le niveau du personnage"

        return(self._niveau)


    @property

    def class_per(self):

        "Renvoie une variable contenant la classe du personnage"

        return(self._classe)


    # - - - - - - - - - - - #

    # Méthodes

    # - - - - - - - - - - - #


    def presentation(self):

        "Affiche quelques unes des valeurs stockées dans le personnage"

        print(self._nom, ' ',self._prenom)

        print(self._classe, ' de niveau ',self._niveau)

        self.arme.presentation()

        self.armure.presentation()


    def perdre_un_niveau(self):

        "Cette méthode permet de réduire le niveau de 1"

        self._niveau +=-1


    def combat(self,adversaire):

        "Ceci est une méthode qui renvoie le nom du combattant qui perd"

        perdant =''

        test = True

        while test:

            valeur1 = random.randint(1,6)+self._niveau

            valeur2 = random.randint(1,6)+adversaire._niveau

            if valeur1>valeur2:

                perdant = adversaire._nom

                adversaire.perdre_un_niveau()

                test = False

            elif valeur2>valeur1:

                perdant = self._nom

                self.perdre_un_niveau()

                test = False

        return(perdant)


#########################################################

# Corps du programme

#########################################################


if __name__ == "__main__":

    print("** Code de test du fichier mes_classes.py **")

    perso1 = Personnage()

    perso1.name = "Solo"

    perso2 = Personnage()

    perso2.name = "Stormtrooper"

    perdant = perso1+perso2

    print("Lors du combat, {} s'est pris un mauvais coup".format(perdant))

    input("Appuyer sur ENTREE")

08° Rajouter la méthode spéciale dans votre code :

def __setattr__(self,x,valeur):

    print("On rentre dans la méthode speciale __setattr__ : Python veut modifier {0} et y placer {1}".format(x,valeur))

    # super().__setattr__(self,x,valeur)

09° Lancer le code qui crée deux instances dans son code de test. Que se passe-t-il ? Pourquoi a-t-on une sorte d'erreur lors du combat ?

Voilà ce qu'on peut obtenir :

** Code de test du fichier mes_classes.py **

On rentre dans la méthode speciale __setattr__ : Python veut modifier _nom et y placer nobody

On rentre dans la méthode speciale __setattr__ : Python veut modifier _prenom et y placer nobody

On rentre dans la méthode speciale __setattr__ : Python veut modifier _niveau et y placer 0

On rentre dans la méthode speciale __setattr__ : Python veut modifier _classe et y placer aucune classe

On rentre dans la méthode speciale __setattr__ : Python veut modifier arme et y placer <_main__.Arme object at 0x0000023E90E91B70>

On rentre dans la méthode speciale __setattr__ : Python veut modifier armure et y placer <__main__.Armure object at 0x0000023E90E91B70>

On rentre dans la méthode speciale __setattr__ : Python veut modifier _Personnage__surnom et y placer newbie

On rentre dans la méthode speciale __setattr__ : Python veut modifier name et y placer Solo

On rentre dans la méthode speciale __setattr__ : Python veut modifier _nom et y placer nobody

On rentre dans la méthode speciale __setattr__ : Python veut modifier _prenom et y placer nobody

On rentre dans la méthode speciale __setattr__ : Python veut modifier _niveau et y placer 0

On rentre dans la méthode speciale __setattr__ : Python veut modifier _classe et y placer aucune classe

On rentre dans la méthode speciale __setattr__ : Python veut modifier arme et y placer <__main__.Arme object at 0x0000023E90F13E48>

On rentre dans la méthode speciale __setattr__ : Python veut modifier armure et y placer <__main__.Armure object at 0x0000023E90F13E48>

On rentre dans la méthode speciale __setattr__ : Python veut modifier _Personnage__surnom et y placer newbie

On rentre dans la méthode speciale __setattr__ : Python veut modifier name et y placer Stormtrooper

Lors du combat, False s'est pris un mauvais coup

Appuyer sur ENTREE

Pourquoi ? Regardons étape par étape :

perso1 = Personnage()

Cette ligne de code engendre l'initialisation des 7 attributs suivants et déclenche 7 fois de suite la méthode spéciale __setattr__ :

On rentre dans la méthode speciale __setattr__ : Python veut modifier _nom et y placer nobody

On rentre dans la méthode speciale __setattr__ : Python veut modifier _prenom et y placer nobody

On rentre dans la méthode speciale __setattr__ : Python veut modifier _niveau et y placer 0

On rentre dans la méthode speciale __setattr__ : Python veut modifier _classe et y placer aucune classe

On rentre dans la méthode speciale __setattr__ : Python veut modifier arme et y placer <_main__.Arme object at 0x0000023E90E91B70>

On rentre dans la méthode speciale __setattr__ : Python veut modifier armure et y placer <__main__.Armure object at 0x0000023E90E91B70>

On rentre dans la méthode speciale __setattr__ : Python veut modifier _Personnage__surnom et y placer newbie

perso1.name = "Solo"

Cette fois, on ne rentre qu'une seule fois dans __setattr__ car on ne tente de modifier que _nom via name.

On rentre dans la méthode speciale __setattr__ : Python veut modifier name et y placer Solo

perso2 = Personnage()

Encore une fois, nous allons entrer 7 fois dans __setattr__ pour initialiser les 7 attributs :

On rentre dans la méthode speciale __setattr__ : Python veut modifier _nom et y placer nobody

On rentre dans la méthode speciale __setattr__ : Python veut modifier _prenom et y placer nobody

On rentre dans la méthode speciale __setattr__ : Python veut modifier _niveau et y placer 0

On rentre dans la méthode speciale __setattr__ : Python veut modifier _classe et y placer aucune classe

On rentre dans la méthode speciale __setattr__ : Python veut modifier arme et y placer <__main__.Arme object at 0x0000023E90F13E48>

On rentre dans la méthode speciale __setattr__ : Python veut modifier armure et y placer <__main__.Armure object at 0x0000023E90F13E48>

On rentre dans la méthode speciale __setattr__ : Python veut modifier _Personnage__surnom et y placer newbie

perso2.name = "Stormtrooper"

Cn ne rentre qu'une seule fois dans __setattr__ car on ne tente de modifier que _nom via name.

On rentre dans la méthode speciale __setattr__ : Python veut modifier name et y placer Stormtrooper

Bon, et pourquoi on obtient False si tout marche bien ?

En réalité, ça ne provoque pas d'erreur mais ça ne fonctionne pas : je ne fais rien dans ma méthode __setattr__ : j'affiche juste le nom de l'attribut à changer et la valeur a y placer.

Donc perso1 et perso2 sont totalement vide en réalité. C'est pour cela que combat ne fonctionne pas et perdant contient donc la valeur False, signifiant une erreur.

Comment faire alors ?

Il faut enlever le commentaire devant l'appel de la méthode __setattr__ d'object !

10° Supprimer le commentaire et vérifier que tout fonctionne correctement ensuite en terme d'affectation.

11° Modifier la méthode spéciale à l'aide d'un IF de façon à remplacer le paramètre x valant 'truc' par '_nom'. Vérifier que votre programme fonctionne à l'aide du test suivant :

>>> perso1.truc = "Bob"

On rentre dans la méthode speciale __setattr__ : Python veut modifier truc et y placer Bob

>>> perso1.name

'Bob'

Il ne nous reste qu'à agir sur les attributs privés, ceux avec deux underscores.

12° Rajouter d'autres tests au code proposé de façon à gérer également la modification de l'attribut privé __surnom à l'aide de code de type perso.nickname = "Jaba".

def __getattr__(self,x):

    if x=="nickname":

        return(self.__surnom)

    else:

        print("On rentre dans la méthode speciale : Python ne reconnait pas l'attribut ",x)


    def __setattr__(self,x,valeur):

        print("On rentre dans la méthode speciale __setattr__ : Python veut modifier {0} et y placer {1}".format(x,valeur))

        if x == 'truc':

            x = '_nom'

        super().__setattr__(x,valeur)

Pour voir si votre modification fonctionne, il faut tenter de modifier __surnom en passant par nickname ET vérifier ensuite que la modification a bien été faite. Pour cela, il suffit de taper perso.nickname.

Vous devriez avoir quelque chose de proche de cela, sauf si vous avez été particulièrement attentif :

    def __setattr__(self,x,valeur):

        print("On rentre dans la méthode speciale __setattr__ : Python veut modifier {0} et y placer {1}".format(x,valeur))

        if x == 'truc':

            x = '_nom'

        elif x == 'nickname':

            x = '__surnom'

        super().__setattr__(x,valeur)

Alors pourquoi ça ne fonctionne pas ? C'est lié à la façon dont Python se crée ses fameux attributs privés : vous n'arrivez pas à les trouver car il rajoute des choses devant leur nom !

Ainsi, il ne faut pas chercher __surnom mais _Personnage__surnom : Python rajoute un underscore et le nom de la classe.

Il faut donc tenter en réalité :

    def __setattr__(self,x,valeur):

        print("On rentre dans la méthode speciale __setattr__ : Python veut modifier {0} et y placer {1}".format(x,valeur))

        if x == 'truc':

            x = '_nom'

        elif x == 'nickname':

            x = '_Personnage__surnom'

        super().__setattr__(x,valeur)

13° Utiliser ce nouveau code et tenter les instructions suivantes :

>>> perso1.nickname

'newbie'

>>> perso1.nickname = "Bob"

On rentre dans la méthode speciale __setattr__ : Python veut modifier nickname et y placer Bob

>>> perso1.nickname

'Bob'

Moralité : à moins d'avoir une très bonne raison, éviter les attributs avec deux underscores.

Bien. Voilà. Nous avons bien fait le tour des classes. Si vous êtes arrivé jusqu'ici, vous allez pouvoir comprendre la grande majorité des codes des modules distribués. N'oubliez pas que Python est conçu de façon à être distribué et partagé. Vous avez donc toujours accès au code. Allez fouiller dans votre disque dur pour y trouver les modules Pillow, matplotlib ou même pip... Vous y trouverez toujours quelques bonnes idées (qui fonctionnent en plus !).

Le code complet en conclusion :

#!/usr/bin/env python

# -*- coding: utf-8 -*-

import random


#########################################################

# Déclarations des classes

#########################################################

class Arme:

    "Cette classe contient les infos sur une arme"


    def __init__(self):

        self.nom='poings'

        self.degats=1

        self.type='[arme]'

        self.distance='contact'


    def presentation(self):

        "Affiche quelques unes des valeurs de l'arme"

        print('Arme : ', self.nom, ' qui fait ',self.degats, 'dégats à la distance :',self.distance)


class Armure:

    "Cette classe contient les infos sur une armure"


    def __init__(self):

        self.nom='aucune'

        self.protection=0

        self.type='[armure]'


    def presentation(self):

        "Affiche quelques unes des valeurs stockées de l'armure"

        print('Armure :', self.nom, ' qui protège de ',self.protection, 'dégats')


class Personnage:

    """Ceci est une classe permettant de créer un personnage dans mon super RPG

    Vous pouvez accéder aux propriétés suivantes :

    name (lecture et écriture) : contient le nom du personnage

    first_name (lecture) : contient le prénom du personnage

    level (lecture) : contient le niveau du personnage

    class_per (lecture) : contient la classe du personnage

    

    Vous avez également accès aux attributs suivants :

    arme : contient la référence vers une instance de la classe Arme.

    armure : contient la référence vers une instance de la classe Armure."""


    # - - - - - - - - - - - #

    # Méthodes spéciales

    # - - - - - - - - - - - #


    def __init__(self):

        self._nom='nobody'

        self._prenom='nobody'

        self._niveau=0

        self._classe='aucune classe'

        self.arme = Arme()

        self.armure = Armure()

        self.__surnom = 'newbie'


    def __getattr__(self,x):

        if x=="nickname":

            return(self.__surnom)

        else:

            print("On rentre dans la méthode speciale : Python ne reconnait pas l'attribut ",x)


    def __setattr__(self,x,valeur):

        print("On rentre dans la méthode speciale __setattr__ : Python veut modifier {0} et y placer {1}".format(x,valeur))

        if x == 'truc':

            x = '_nom'

        elif x == 'nickname':

            x = '_Personnage__surnom'

        super().__setattr__(x,valeur)


    def __add__(self,adversaire):

        try:

            x = self.combat(adversaire)

        except:

            x = False

        return(x)


    # - - - - - - - - - - - #

    # Propriétés

    # - - - - - - - - - - - #


    @property

    def name(self):

        "Renvoie une variable contenant le nom du personnage"

        return(self._nom)


    @name.setter

    def name(self,x):

        "Modifie le nom du personnage si le paramètre est bien un string"

        try :

            x = str(x)

            self._nom = x

        except:

            return(False)

        return(True)


    @property

    def first_name(self):

        "Renvoie une variable contenant le prenom du personnage"

        return(self._prenom)


    @property

    def level(self):

        "Renvoie une variable contenant le niveau du personnage"

        return(self._niveau)


    @property

    def class_per(self):

        "Renvoie une variable contenant la classe du personnage"

        return(self._classe)


    # - - - - - - - - - - - #

    # Méthodes

    # - - - - - - - - - - - #


    def presentation(self):

        "Affiche quelques unes des valeurs stockées dans le personnage"

        print(self._nom, ' ',self._prenom)

        print(self._classe, ' de niveau ',self._niveau)

        self.arme.presentation()

        self.armure.presentation()


    def perdre_un_niveau(self):

        "Cette méthode permet de réduire le niveau de 1"

        self._niveau +=-1


    def combat(self,adversaire):

        "Ceci est une méthode qui renvoie le nom du combattant qui perd"

        perdant =''

        test = True

        while test:

            valeur1 = random.randint(1,6)+self._niveau

            valeur2 = random.randint(1,6)+adversaire._niveau

            if valeur1>valeur2:

                perdant = adversaire._nom

                adversaire.perdre_un_niveau()

                test = False

            elif valeur2>valeur1:

                perdant = self._nom

                self.perdre_un_niveau()

                test = False

        return(perdant)


#########################################################

# Corps du programme

#########################################################


if __name__ == "__main__":

    print("** Code de test du fichier mes_classes.py **")

    perso1 = Personnage()

    perso1.name = "Solo"

    perso2 = Personnage()

    perso2.name = "Stormtrooper"

    perdant = perso1+perso2

    print("Lors du combat, {} s'est pris un mauvais coup".format(perdant))

    input("Appuyer sur ENTREE")