Infoforall

Python 23 : Les objets - compléments d'informations

Nous allons continuer à parler des objets et des Classes. Voici d'abord une correction possible des questions de l'activité précédente :

Rappel : Python 3 vs Python 2

class Personne(object): ou class Personnage ?

Si vous cherchez des codes dans une documentation, vous trouverez surement des classes définies à l'aide de class Personnage(object):. Il s'agit d'une ancienne façon de déclarer les classes en Python 2. Ce terme object fut rajouté suite à une mise à jour de façon à permettre aux classes de Python 2 de se comporter réellement comme de vraies classes. Comme cette déclaration est rétrocompatible en Python 3, on trouve beaucoup de codes qui incluent cette façon de faire. Mais en Python 3, si vous ne notez rien entre les parenthèses, Python réagira comme si vous aviez rajouté object.

En résumé, en Python 3, que vous notiez class Personnage: ou class Personnage(object): vous obtiendrez la même chose.

Nous expliquerons d'où vient cet object dans la troisième activité sur les objets.

Pour rappel, ce code avait été initié pour créer une sorte de jeu où deux personnages pouvaient se battre entre eux, le perdant du combat perdant un niveau.

On y trouve donc une classe Personnage qui possède :

#!/usr/bin/env python

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


import random


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

# Déclarations des classes

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


class Personnage:

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


    def __init__(self) :

        self.nom = 'nobody'

        self.prenom = 'nobody'

        self.niveau = 0

        self.classe = 'aucune classe'


    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)


    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

                test = False

            elif valeur2>valeur1 :

                perdant = self

                test = False

        perdant.perdre_un_niveau()

        return(perdant)


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

# Déclarations des fonctions

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


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

# Corps du programme

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


bob_skywalker = Personnage()

bob_skywalker.nom = "Skywalker"

bob_skywalker.prenom = "Bob"

bob_skywalker.niveau = 1

bob_skywalker.classe = "Jedi raté"


perso2 = Personnage()

perso2.nom = "De nom je n'ai pas"

perso2.prenom = "Yoda"

perso2.niveau = 3

perso2.classe = "Vieux Jedi"


print(bob_skywalker.combat(perso2).nom, " vient de perdre un combat")

print(bob_skywalker.combat(perso2).nom, " vient de perdre un combat")

Nous avions bien progressé puisque maintenant nous n'avons plus à créer de fonctions externes pour gérer un combat entre deux objets de cette classe Personnage : toute la gestion est intégrée dans la définition de la Classe. Si quelqu'un importe cette Classe, les personnages pourront se battre puisque le code gérant l'interaction est déjà inclus. C'est plutôt bien.

Que fait le corps du programme sous ma Classe ? C'est simple :

  1. Il crée un objet-Personnage sous l'identifiant bob_skywalker puis lui affecte des valeurs différentes des valeurs par défaut qu'on trouve dans __init__.
  2. bob_skywalker = Personnage()

    bob_skywalker.nom = "Skywalker"

    bob_skywalker.prenom = "Bob"

    bob_skywalker.niveau = 1

    bob_skywalker.classe = "Jedi raté"

  3. Idem avec un objet-Personnage sous l'identifiant perso2.
  4. perso2 = Personnage()

    perso2.nom = "De nom je n'ai pas"

    perso2.prenom = "Yoda"

    perso2.niveau = 3

    perso2.classe = "Vieux Jedi"

  5. Il utilise deux fois la méthode combat sur bob_skywalker. L'adversaire placé en argument est à chaque fois perso2.
  6. print(bob_skywalker.combat(perso2).nom, " vient de perdre un combat")

    print(bob_skywalker.combat(perso2).nom, " vient de perdre un combat")

1 - Module

Nous allons commencer par voir comment cacher le code de la Classe. Ainsi la plupart des utilisateurs iront simplement lire la documentation après avoir importer la Classe plutôt que de modifier directement le code de la Classe.

Nous allons donc créer un fichier Python nommé mes_classess.py qui va contenir la Classe :

01° Créer et enregistrer un fichier mes_classes.py en utilisant le code suivant :

#!/usr/bin/env python

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


import random


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

# Déclarations des classes

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


class Personnage:

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


    def __init__(self) :

        self.nom = 'nobody'

        self.prenom = 'nobody'

        self.niveau = 0

        self.classe = 'aucune classe'


    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)


    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

                test = False

            elif valeur2>valeur1 :

                perdant = self

                test = False

        perdant.perdre_un_niveau()

        return(perdant)

02° Créer et enregistrer au même endroit le fichier prog_princ.py contenant les lignes suivantes :

#!/usr/bin/env python

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


import mes_classes

# mes classes car le fichier externe se nomme mes_classes.py


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

# Corps du programme

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


bob_skywalker = Personnage()

bob_skywalker.nom = "Skywalker"

bob_skywalker.prenom = "Bob"

bob_skywalker.niveau = 1

bob_skywalker.classe = "Jedi raté"


perso2 = Personnage()

perso2.nom = "De nom je n'ai pas"

perso2.prenom = "Yoda"

perso2.niveau = 3

perso2.classe = "Vieux Jedi"


print(bob_skywalker.combat(perso2).nom, " vient de perdre un combat")

print(bob_skywalker.combat(perso2).nom, " vient de perdre un combat")

03° Executer prog_princ.py pour voir si ça fonctionne. Alors ? Tentez de réparer si vous le pouvez.

...CORRECTION...

Le problème vient des noms : le module se nomme mes_classes (puisqu'on a créé un fichier mes_classes.py)

Or, la classe Personnage se trouve dans le module mes_classes. Il faut donc utiliser bob_skywalker = mes_classes.Personnage() pour atteindre la méthode CONSTRUCTEUR.

Il faudra faire la même chose pour créer perso2.

La version définitive est donc :

#!/usr/bin/env python

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


import mes_classes

# mes classes car le fichier externe se nomme mes_classes.py


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

# Corps du programme

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


bob_skywalker = mes_classes.Personnage()

bob_skywalker.nom = "Skywalker"

bob_skywalker.prenom = "Bob"

bob_skywalker.niveau = 1

bob_skywalker.classe = "Jedi raté"


perso2 = mes_classes.Personnage()

perso2.nom = "De nom je n'ai pas"

perso2.prenom = "Yoda"

perso2.niveau = 3

perso2.classe = "Vieux Jedi"


print(bob_skywalker.combat(perso2).nom, " vient de perdre un combat")

print(bob_skywalker.combat(perso2).nom, " vient de perdre un combat")

Il faudra donc modifier vos codes en conséquence.

Sinon, il y a la méthode brute qui permet d'importer le module et de pouvoir l'utiliser directement. Attention néanmoins : il est possible que l'une de vos classes porte le même nom qu'une classe native de Python. A utiliser avec précaution donc :

import mes_classes


A remplacer par


from mes_classes import *

On pourra alors utiliser le module sans précéder ses élements par son nom :

bob_skywalker = Personnage()

2 - Comparaison d'objet

Nouvel élément d'étude : comment fonctionne la comparaison et la copie d'un objet par rapport à la copie d'une variable.

04° Regarder le code suivant et réflechir à ce qu'il peut donner comme réponse. Créer ensuite un fichier mon_programme_2.py pour vérifier le résultat.

#!/usr/bin/env python

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


a = 45

print("On crée a avec a=45 et l'identifiant de la variable a est ",id(a))

b = 45

print("On crée b avec b=45 et l'identifiant de la variable b est ",id(b))

print(a==b)


input("Appuyer sur ENTREE")

...CORRECTION...

Le programme crée une variable a et y place 45 ( a = 45).

Le programme crée une variable b et y place 45 ( b = 45).

On constate que les deux variables pointent vers la même id : Python pointe vers la même case mémoire pour a et b (case qui contient 45) plutôt que de prendre une case propre pour b. Le test a==b affiche donc True.

On crée a avec a =45 et l'identifiant de la variable a est 1752283584

On crée b avec b =45 et l'identifiant de la variable b est 1752283584

True

05° Faisons la même chose avec deux objets-Personnage (c'est à dire instanciation de la classe Personnage) : on les remplit de la même façon et on tente de les comparer :

#!/usr/bin/env python

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


import mes_classes


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

# Corps du programme

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


bob_skywalker = mes_classes.Personnage()

bob_skywalker.nom = "Skywalker"

bob_skywalker.prenom = "Bob"

bob_skywalker.niveau = 1

bob_skywalker.classe = "Jedi raté"


print("On crée bob_skywalker et l'identifiant de l'objet est ",id(bob_skywalker))


perso2 = mes_classes.Personnage()

perso2.nom = "Skywalker"

perso2.prenom = "Bob"

perso2.niveau = 1

perso2.classe = "Jedi raté"


print("On crée perso2 et l'identifiant de l'objet est ",id(perso2))


print(bob_skywalker==perso_2)


input("Appuyer sur ENTREE")

...CORRECTION...

Le programme crée un objet nommé bob_skywalker.

Le programme crée un objet nommé perso_2 et le remplit à l'identique du premier.

On constate pourtant que les id sont différentes : Python pointe vers deux adresses différentes. bob_skywalker==perso_2 affiche donc False.

On crée bob_skywalker et l'identifiant de l'objet est 2595884309808

On crée perso_2 et l'identifiant de l'objet est 2595884309304

False

Similitude

On parle de similitude si deux entités possèdent le même contenu.

Deux objets similaires ne pointent pas vers la même adresse : il ne s'agit pas de la même entité.

similitude

Si on teste bob_skywalker == perso2, on obtient donc False.

Pourquoi ? Parce qu'on ne teste pas les contenus : on teste les adresses !

Ce n'est pas le cas avec les variables qui pointe vers des contenus typés (integer, float ou string). Dans le cas d'un test a == b, on aura True.

Même si ce n'est pas vraiment aussi simple, retenez que :

  • a == b teste les id si a et b pointent des "objets"
  • a == b teste les contenus si a et b pointent des types simples.

06° Faisons la même chose mais l'objet perso_2 est obtenu en utilisant perso_2 = bob_skywalker :

#!/usr/bin/env python

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


import mes_classes


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

# Corps du programme

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


bob_skywalker = mes_classes.Personnage()

bob_skywalker.nom = "Skywalker"

bob_skywalker.prenom = "Bob"

bob_skywalker.niveau = 1

bob_skywalker.classe = "Jedi raté"


print("On crée bob_skywalker et l'identifiant de l'objet est ",id(bob_skywalker))


perso2 = bob_skywalker

print("On crée perso2 et l'identifiant de l'objet est ",id(perso2))


print(bob_skywalker==perso_2)


input("Appuyer sur ENTREE")

...CORRECTION...

On crée bob_skywalker avec une nouvelle instanciation de la classe Personnage. bob_skywalker contient donc la référence du nouvel objet.

On crée perso_2 en stockant la référence précédente dans la nouvelle variable aussi. perso_2 pointe donc vers la même référence, elle pointe le même objet que bob_skywalker

On constate cette fois que bob_skywalker == perso_2 affiche True.

On crée bob_skywalker et l'identifiant de l'objet est 2251891350832

On crée perso_2 et l'identifiant de l'objet est 2251891350832

True

Unicité

On parle d'unicité si deux entités possèdent le même identifiant ou la même adresse mémoire.

unicité

Il s'agit de la même entité qu'on peut nommer avec plusieurs alias (ou plusieurs noms).

C'est la même chose avec vous : vous possèdez un nom et très certainement un surnom. Mais qu'on fasse référence à l'un ou l'autre, on pointe bien vers vous.

Si on teste a == b, on obtient donc True car a et b ont le même id.

Pour créer un objet disposant de plusieurs alias, il suffit d'affecter le contenu de la variable pointe l'objet dans une nouvelle variable : perso_2 = bob_skywalker.

Et maintenant, tour de magie !

Oui, enfin ... un petit :

Voici un code qui crée perso_2 à partir de perso_1 : perso_2 = perso_1.

Il modifie ensuite l'attribut nom de perso_1 puis l'attribut prenom de perso_2.

Enfin, il affiche les caractéristiques de perso_1 et de perso_2.

#!/usr/bin/env python

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


import mes_classes


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

# Corps du programme

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


perso_1 = mes_classes.Personnage()

perso_1.nom = "Skywalker"

perso_1.prenom = "Anakin"

perso_1.niveau = 1

perso_1.classe = "Jedi noir"

print("On crée perso_1 et l'identifiant de l'objet est ",id(perso_1))


perso_2 = perso_1

print("On crée perso_2 et l'identifiant de l'objet est ",id(perso_2))


perso_1.nom = "Darth"

perso_2.prenom = "Vador"

print("\nContenu de perso_1")

perso_1.presentation()

print("\nContenu de perso_2")

perso_2.presentation()


input("Appuyer sur ENTREE")

07° Avant d'essayer le programme, tentez de deviner ce qu'il devrait afficher.

...CORRECTION...

perso_1 et perso_2 sont deux alias du même objet.

Modifier l'attribut nom à travers perso_1 va également changer l'affichage sur perso_2 puisque dans les deux cas on modifie la même zone mémoire.

Modifier l'attribut prenom à travers perso_2 va changer l'affichage sur perso_1.

On crée perso_1 et l'identifiant de l'objet est 1812704861432

On crée perso_2 et l'identifiant de l'objet est 1812704861432


Contenu de perso_1

Darth Vador

Jedi noir de niveau 1


Contenu de perso_2

Darth Vador

Jedi noir de niveau 1

Tout ça à cause de l'unicité : les noms de vos variables-objets sont différents mais en réalité, elles pointent bien les mêmes données. Si vous modifiez les attributs d'un des alias, vous modifiez nécessairement l'autre alias aussi puisque c'est le même objet avec deux noms.

Remarque : référence vers l'objet et objet lui-même

En réalité, on parle souvent de l'objet perso_1 et de l'objet perso_2.

Dans la plupart des programmes simples, chaque variable-objet fait référence à un objet différent. On a donc l'habitude de faire cet abus de langage.

Mais s'il fallait vraiment utiliser un langage précis, il faudrait dire que perso_1 n'est pas l'objet mais simplement la référence vers un objet dont l'id est 1812704861432.

reference_et_objet

3 - Les objets dans les fonctions

En réalité, cette histoire de références va quand même avoir des répercussions sur la façon dont on gère les objets dans les fonctions. Surtout la portée.

Cette partie est assez technique et assez "optionnelle" dans la mesure où vous n'avez pas besoin de l'utiliser dans les parties suivantes. Par contre, il faudra les lire un jour car sinon vous risquez de ne pas comprendre ce qui cloche dans votre beau code.

Si vous avez du courage, en route. Sinon, dites le moi que je vous fasse un petit résumé.

Rappel sur la portée des noms : espace des noms

Si on désigne une variable toto dans une fonction, Python commence par regarder si ce nom toto est présent dans l'espace des noms de la fonction.

Si la réponse est OUI, l'entité toto est un objet local : on ne pourra pas y accéder depuis ailleurs et il sera détruit par le ramasse-miette une fois qu'on sortira de la fonction.

Si la réponse est NON, Python va voir si quelque chose nommée toto existe dans l'espace des noms du corps du programme. On pourra alors y accéder en lecture mais pas en écriture.

Par contre, on peut utiliser le mot-clé GLOBAL pour permettre à la fonction d'agir sur l'entité.

Regardons si tout ceci est vrai avec les objets.

3.1 - Peut-on modifier un objet depuis l'intérieur d'une fonction ?

08° Utiliser le code suivant qui crée un personnage perso_1 et perso_2. Il appelle une fonction modifier qui impose perso_1 = perso_2. On affiche perso_1 dans la fonction et au retour dans le corps du programme.

#!/usr/bin/env python

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


import mes_classes

# mes classes car le fichier externe se nomme mes_classes.py


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

# Déclarations des fonctions

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

def modifier() :

    perso_1 = perso_2

    print("\nDans la fonction elle-même, contenu de perso_1 :")

    perso_1.presentation()


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

# Corps du programme

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

perso_1 = mes_classes.Personnage()

perso_1.nom = "Skywalker"

perso_1.prenom = "Anakin"

perso_1.niveau = 1

perso_1.classe = "Jedi noir"


perso_2 = mes_classes.Personnage()


modifier()

print("\nAprès le retour dans le corps du programme, contenu de perso_1 :")

perso_1.presentation()


input("\nAppuyer sur ENTREE")

...CORRECTION...

La fonction parvient à créer un objet perso_1 à partir de perso_2.

Mais, c'est un nouvel objet local qui est créé lorsque vous avez utiliser perso_1 = perso_2.

Il faut le lire ainsi : perso_1_de_la_fonction = perso_2_du_programme_principal.


De retour dans le programme principal, on voit que perso_1 n'a pas bougé : lorsqu'on lit perso_1 dans le programme principal, Python va chercher perso_1_du_programme_principal.

Dans la fonction elle-même, contenu de perso_1 :

nobody nobody

aucune classe de niveau 0


Après le retour dans le corps du programme, contenu de perso_1 :

Skywalker Anakin

Jedi noir de niveau 1


Appuyer sur ENTREE

BILAN : effectivement, si on tente en Python de modifier avec un signe EGAL "=" un objet dans une fonction, cela ne fait que créer une version locale qui disparaitra ensuite. Le comportement des variables-objets" et des "variables-typées" est donc identique de ce point de vue.

Mais si on tente juste de modifier une attribut ? Avec un code du type :

perso_1.nom = "Titi"

09° Utiliser le code suivant qui crée un personnage perso_1. Il appelle une fonction modifier qui impose perso_1.nom = "Titi". On affiche perso_1 dans la fonction et au retour dans le corps du programme.

#!/usr/bin/env python

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


import mes_classes

# mes classes car le fichier externe se nomme mes_classes.py


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

# Déclarations des fonctions

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


def modifier() :

    perso_1.nom = "Titi"

    print("\nDans la fonction elle-même, contenu de perso_1 :")

    perso_1.presentation()


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

# Corps du programme

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

perso_1 = mes_classes.Personnage()

perso_1.nom = "Skywalker"

perso_1.prenom = "Anakin"

perso_1.niveau = 1

perso_1.classe = "Jedi noir"


modifier()

print("\nAprès le retour dans le corps du programme, contenu de perso_1 :")

perso_1.presentation()


input("\nAppuyer sur ENTREE")

...CORRECTION...

La fonction parvient à modifier l'attribut de l'objet !

Une fois de retour dans le corps du programme, le nom stocké dans perso_1_programme_principal a bien été modifié.

Pourquoi ? Simplement car on utilse perso_1 du programme principal en lecture dans la fonction : souvenez-vous, perso_1 ne contient qu'une référence à l'objet, pas l'objet lui-même.

Ici, on utilise donc l'adresse contenue dans perso_1 pour aller agir sur l'un des attributs. C'est donc légal.

Dans la fonction elle-même, contenu de perso_1 :

Titi Anakin

aucune classe de niveau 0


Après le retour dans le corps du programme, contenu de perso_1 :

Titi Anakin

Jedi noir de niveau 1


Appuyer sur ENTREE

Il faudra donc faire attention : vous pouvez modifier les valeurs des attributs des objets depuis une fonction car on transmet la référence (l'id en Python ou l'adresse pour d'autres langages).

Ainsi, depuis une fonction, la modification d'attributs ou l'utilisation de méthodes, permet d'agir réellement sur l'objet : perso_1.niveau = 3 va réellement mettre le niveau à 3 dans l'objet du corps du programmme et perso_1.perdre_un_niveau() va réellement faire diminuer le niveau de l'objet désigné. Il ne s'agira pas d'une action sur un duplicata local.

3.2 - Peut-on modifier un objet passé en argument à une fonction ?

Pour rappel, voici un exemple de programme passant un integer x en argument : on est sur le cas de la variable 'typée' :

#!/usr/bin/env python

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


import mes_classes

# mes classes car le fichier externe se nomme mes_classes.py


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

# Déclarations des fonctions

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


def modifier(y) :

    y = y/2

    print("\nDans la fonction elle-même : y = ", y)


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

# Corps du programme

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


x = 50


modifier(x)

print("\nAprès le retour dans le corps du programme : x = ", x)


input("\nAppuyer sur ENTREE")

Le résultat est que la variable x n'est pas modifié par le fait qu'elle soit transmise à la fonction et que la fonction modifie ensuite localement le paramètre y qui reçoit x : on crée un paramètre y qui contient la même chose que x.

Regardons ce qui se passe pour un objet passé en argument.

10° Utiliser le code suivant pour répondre à la question suivante : un objet passé en argument est-il modifié lors de l'utilisation d'un signe = dans la fonction ?

#!/usr/bin/env python

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


import mes_classes


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

# Déclaration des fonctions

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


def modifier(perso_x) :

    perso_x = perso_2

    print("\nDans la fonction elle-même :")

    perso_x.presentation()


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

# Corps du programme

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

perso_1 = mes_classes.Personnage()

perso_1.nom = "Solo"

perso_1.prenom = "Han"

perso_1.niveau = 4

perso_1.classe = "Contrebandier"


perso_2 = mes_classes.Personnage()


modifier(perso_1)

print("\nAprès le retour dans le corps du programme :")

perso_1.presentation()


input("\nAppuyer sur ENTREE")

...CORRECTION...

Initialement, perso_x contient bien la référence au même objet que perso_1. Les deux pointent vers le même objet.


Mais dans la fonction, on crée une variable locale perso_x qu'on remplit avec la référence perso_2 : perso_x = perso_2.

Lorsqu'on execute alors (dans la fonction) perso_x.presentation(), on affiche la présentation contenu de l'objet pointé par perso_2.


De retour dans le programme principal, on voit bien que perso_1 n'a pas été modifié alors qu'il a été fourni en argument pour remplir le paramètre perso_x.

Dans la fonction elle-même :

nobody nobody

aucune classe de niveau 0


Après le retour dans le corps du programme :

Solo Han

Contrebandier de niveau 4


Appuyer sur ENTREE

Regardons ce qui se passe pour les objets passés en argument si on tente de les modifier à l'aide du point (changement d'attributs ou utilisation d'une méthode).

11° Utiliser le code suivant pour répondre à la question suivante : un objet passé en argument est-il modifié lors de l'utilisation d'un point (sur un attribut ou une méthode) dans la fonction ?

#!/usr/bin/env python

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


import mes_classes

# mes classes car le fichier externe se nomme mes_classes.py


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

# Déclarations des fonctions

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


def modifier(perso_x) :

    perso_x.nom = "Titi"

    print("\nDans la fonction elle-même :")

    perso_x.presentation()


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

# Corps du programme

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


perso_1 = mes_classes.Personnage()

perso_1.nom = "Solo"

perso_1.prenom = "Han"

perso_1.niveau = 4

perso_1.classe = "Contrebandier"


modifier(perso_1)

print("\nAprès le retour dans le corps du programme :")

perso_1.presentation()


input("\nAppuyer sur ENTREE")

...CORRECTION...

Cette fois, le paramètre perso_x contient tout le temps la référence au même objet que perso_1 fourni en argument.

Lorsqu'on tape perso_x.nom cela pointe donc au même endroit que perso_x_1.nom.

Le contenu vers lequel dirige perso_1 est donc bien modifié.

Dans la fonction elle-même :

Titi Han

Contrebandier de niveau 4


Après le retour dans le corps du programme :

Titi Han

Contrebandier de niveau 4


Appuyer sur ENTREE

Bien entendu, cela fonctionne également avec l'utilsation d'une méthode. Tant qu'on utilise un point plutôt qu'un =, c'est bon.

Voilà, c'est fini. Tentez maintenant de vous souvenir qu'on transmet toujours l'adresse d'un objet lorsqu'on fournit en argument une 'variable-objet'.

4 - Des objets qui contiennent des objets

Un objet contient des attributs. Pour l'instant, nous les avons utilisé par stocker des types simples (integer, string ...). Mais en réalité, les attributs d'un objet peuvent stocker des variables-objets également.

Imaginons que nous voulions que nos héros de classe Personnage puissent porter une arme et une armure.

Nous allons créer une classe Arme et une classe Armure.

Nous allons y inclure une méthode __init__ pour l'initialiser et une méthode presentation qui en donnera les caractéristiques.

La méthode présentation de Personnage va être modifiée pour intégrer les présentations d'arme et d'armure du personnage.

12° Modifier le fichier mes_classes.py :

#!/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égâts à 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égâts')


class Personnage:

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


    def __init__(self) :

        self.nom = 'nobody'

        self.prenom = 'nobody'

        self.niveau = 0

        self.classe = 'aucune classe'

        self.arme = Arme() # NOUVEAU

        self.armure = Armure() # NOUVEAU


    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() # NOUVEAU

        self.armure.presentation() # NOUVEAU


    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

                test = False

            elif valeur2>valeur1 :

                perdant = self

                test = False

        perdant.perdre_un_niveau()

        return(perdant)


13° Créer un fichier Python qui va utiliser l'ensemble. Créons par exemple un pistolet laser et affectons le à Han Solo :

#!/usr/bin/env python

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


import mes_classes

# mes classes car le fichier externe se nomme mes_classes.py


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

# Corps du programme

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


pistolaser = mes_classes.Arme()

pistolaser.nom = "Pistoler Laser"

pistolaser.degats = 2

pistolaser.distance = 'courte'


perso_1 = mes_classes.Personnage()

perso_1.nom = "Solo"

perso_1.prenom = "Han"

perso_1.niveau = 4

perso_1.classe = "Contrebandier"

perso_1.arme = pistolaser


print("Personnage")

perso_1.presentation()


input("\nAppuyer sur ENTREE")

En lançant l'application, vous devriez commencer à voir la puissance de la programmation Objet : en créant quelques classes, en intégrant des classes dans d'autres classes, on parvient à coder des choses très compliquées en quelques lignes. Regardez la taille de votre programme principal par rapport à ce qu'il fait en réalité, et vous comprendrez ce que je veux dire...

14° Comment se nomme la variable qui fait référence à l'objet Pistolet Laser ?

...CORRECTION...

La variable pistolaser pointe vers l'objet définissant le pistolet laser.

15° Où se trouve l'affection du pistolet Laser à Han Solo ?

...CORRECTION...

Tout se fait sur cette ligne :

perso_1.arme = pistolaser

16° Où se trouve dans Personnage l'instruction d'afficher l'arme ?

...CORRECTION...

        self.arme.presentation() # NOUVEAU

On cherche dans l'attribut arme de l'instance de Personnage la référence à l'arme :

self.arme

Une fois trouvée, on utilise la méthode presentation de la classe Arme sur l'instance qu'on vient de trouver juste avant.

self.arme.presentation()

17° Pourquoi ne pas utiliser de print lorsqu'on utilise self.arme.presentation() ?

...CORRECTION...

Le print est dans la méthode presentation de la classe Arme.

5 - Mini-projet

18° Modifier le code pour l'armure et l'arme aient un impact lors des combats. Par exemple (si vous êtes en panne d'inspiration) :

19° Rajouter un test pour qu'on ne puisse jamais avoir moins de 0 en armure et en niveau.

20° Créer une méthode combat_total : les deux objets Personnages doivent se battre jusqu'à ce qu'un des deux tombe au niveau 0.

Et bien, vous avez maintenant de nombreux outils à disposition.

Notre jeu de Pac-man serait maintenant assez facile à concevoir puisqu'il "suffit" de coder une classe PacManJoueur et une classe PacManAdversaire. Et de créer les instances nécessaires.

Oui mais ... Les deux classes de PacMan ont certainement énormément de lignes de codes similaires non ?

Oui, et c'est ce que nous allons voir lors de la prochaine activité Objets-3 : comment créer des classes à partir d'autres classes.

Et vous commencerez à comprendre la puissance de la "Programmation Orientée Objet".

En attendant, voici une correction possible des dernières questions :

#!/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égâts à la distance :',self.distance)


    def supprimer_arme(self) : # NOUVEAU

        "Annule les données de l'arme en réinitialisant les valeurs grace à __init__"

        self.__init__()


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égâts')


    def reduire_armure(self,reduction): # NOUVEAU

        "Réduit la protection de l'armure du paramètre fourni dans reduction"

        self.protection -= reduction

        if self.protection < 0 :

            self.__init__()


class Personnage:

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


    def __init__(self) :

        self.nom = 'nobody'

        self.prenom = 'nobody'

        self.niveau = 0

        self.classe = 'aucune classe'

        self.arme = Arme()

        self.armure = Armure()

        self.de = 0 # NOUVEAU


    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

        if self.niveau < 0 : # NOUVEAU

            self.niveau = 0 # NOUVEAU


    def gestion_arme_armure(self,referenceDefenseur) : # NOUVEAU

        """Cette méthode permet de gérer l'efficacité et les dommages subis par l'arme et l'armure de l'attaquant

        Elle renvoie finalement la valeur de l'attaquant pour ce combat

        Le paramètre attendu est la référence vers le Personnage en défense sur ce calcul"""

        valeurAttaquant = self.de

        valeurDefenseur = referenceDefenseur.de

        if valeurDefenseur == 6 :

            self.armure.reduire_armure(2)

        elif valeurDefenseur >= 4 :

            self.armure.reduire_armure(1)

        if valeurAttaquant == 1 :

            self.arme.supprimer_arme()

        valeur_combat = valeurAttaquant + self.niveau + self.arme.degats - referenceDefenseur.armure.protection

        return(valeur_combat)


    def combat_total(self,adversaire) :

        "Ceci est une méthode qui fait combattre les adversaires tant qu'aucun n'a atteint le niveau 0"

        while not(self.niveau == 0 or adversaire.niveau == 0) :

            print("\n",self.combat(adversaire).nom, " vient de perdre un combat")


    def combat(self,adversaire) :

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

        perdant = ''

        test = True

        while test :

            self.de = random.randint(1,6) # NOUVEAU

            adversaire.de = random.randint(1,6) # NOUVEAU

            valeur1 = self.gestion_arme_armure(adversaire) # MODIFIE

            valeur2 = adversaire.gestion_arme_armure(self) # MODIFIE

            if valeur1 > valeur2 :

                perdant = adversaire

                test = False

            elif valeur2 > valeur1 :

                perdant = self

                test = False

        perdant.perdre_un_niveau()

        return(perdant)


En voici un exemple d'utilisation : un combat entier géré entre Han Solo et un Stormtrooper :

#!/usr/bin/env python

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


import mes_classes

# mes classes car le fichier externe se nomme mes_classes.py


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

# Corps du programme

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


pistolaser = mes_classes.Arme()

pistolaser.nom = "Pistoler Laser"

pistolaser.degats = 2

pistolaser.distance = 'courte'


fusilaser = mes_classes.Arme()

fusilaser.nom = "Fusil Laser"

fusilaser.degats = 3

fusilaser.distance = 'longue'


vestecuir = mes_classes.Armure()

vestecuir.nom = "Une simple veste en cuir"

vestecuir.protection = 1


armurestorm = mes_classes.Armure()

armurestorm.nom = "Une belle armure blanche"

armurestorm.protection =4


perso_1 = mes_classes.Personnage()

perso_1.nom = "Solo"

perso_1.prenom = "Han"

perso_1.niveau = 6

perso_1.classe = "Contrebandier"

perso_1.arme = pistolaser

perso_1.armure = vestecuir


perso_2 = mes_classes.Personnage()

perso_2.nom = "Boum"

perso_2.prenom = "Bob"

perso_2.niveau = 2

perso_2.classe = "Stormtrooper"

perso_2.arme = fusilaser

perso_2.armure = armurestorm


print("\n-------------------- Avant le combat :")

print("\nPersonnage")

perso_1.presentation()

print("\nPersonnage")

perso_2.presentation()


print("\n-------------------- Le combat :")

perso_1.combat_total(perso_2)


print("\n-------------------- Après le combat :")

print("\nPersonnage")

perso_1.presentation()

print("\nPersonnage")

perso_2.presentation()

input("\nAppuyer sur ENTREE")