Le guide ultime et définitif sur la programmation orientée objet en Python à l’usage des débutants qui sont rassurés par les textes détaillés qui prennent le temps de tout expliquer. Partie 5.


Maestro, musique !

Prérequis :

La POO, c’est comme les poupées russes. Une fois qu’on a maîtrisé un concept, paf, y en a un autre qui se ramène derrière.

J’ai plusieurs mamans

En ces temps de polémique sur l’autorisation du mariage entre êtres amoureux et consentants (par opposition à celui qu’on fait par convention sociale depuis des siècles), je vous propose de vous rappeler qu’une fois de plus les informaticiens sont en avance sur les mœurs.

D’abord parce qu’une communauté qui fait autant de links vers Never Gona Give You Up est forcément pro gay par essence. Ensuite parce qu’une classe fille peut avoir plusieurs classes mères sans que ça choque personne.

Prenez ces deux classes qui passaient par là sans rien demander :

class MouMoutte(object):
 
    type = 'top' # c'est tip top moumoutte
 
class Raoul(object):
 
    trop = 'cool'

Hé merde, j’ai écris ça, et maintenant j’ai aucune idée de comment faire un code cohérent à partir de cet exemple à la con.

On annule tout.

On recommence.

Vous faites un jeu vidéo. Ça, ça parle bien. Et dedans vous avez des protections et des armes.

class Arme(object):
 
    def __init__(self, nom, degat):
 
        self.nom = nom
        self.degat = degat
 
    def attaque(self, cible): # on retire les degâts de l'épee des points de vie
        cible.vie -= self.degat
 
 
class Protection(object):
 
    def __init__(self, nom, armure):
 
        self.nom = nom
        self.armure = armure
 
    def defend(self, degat): # on diminue les degâts, voire on les annule
 
        degat = degat - self.armure
        if degat < 0:
            return 0
 
        return degat
 
>>> epee = Arme('Epée Mana', degat=999)
>>> casque = Protection('Casque de Balduran', armure=1)

C’est simpliste, mais vous voyez le tableau. Maintenant un connard de client arrive et vous sort une idée trop cool : il faudrait ajouter un barbare dans le jeu. Qui tape aussi avec son bouclier, parce que la concurrence le fait et qu’ils veulent pas se faire mettre un vent par Blizzard.

Enfer et Rutabaga ! Comment allons nous nous sortir de cette situation ?

Il y a moult manières de faire, mais l’une d’elle est d’utiliser l’héritage multiple, c’est-à-dire de créer une classe qui hérite des deux classes en même temps.

class ProtectionOffensive(Arme, Protection):
 
    def __init__(self, nom, degat, armure):
 
        Arme.__init__(self, nom, degat) # appelle le __init__ de arme
        Protection.__init__(self, nom, armure) # appelle le __init de protection
 
        # comme on a appelé les deux __init__, on va avoir les attributs
        # settés dans les deux __init__ attachés à cette classe

Nous avons alors une classe qui possède les méthodes des deux classes parentes :

>>> bouclier = ProtectionOffensive('Bouclier du dragon', degat=10, armure=100)
>>> bouclier.degat
10
>>> bouclier.armure
100
>>> bouclier.defend(10)
0

Ne cherchez pas compliqué, ça fait exactement ce que ça à l’air de faire : “copier” (oui bon, entre guillemets) le code de chaque parent dans l’enfant.

Néanmoins vous avez vu qu’il y a quelques subtilités, notamment la partie __init__.

Posez-vous deux minutes. Respirez. Concentrez-vous. Prêt ?

Les deux classes parentes ont une méthode __init__, mais Python ne peut en “copier” qu’une seule dans l’enfant. Il copie donc la première qu’il trouve. Il va prendre la liste des parents (ici: Arme, Protection), et la lire de gauche à droite. Il va regarder chaque parent, et si la méthode existe, il va la “copier” dans l’enfant.

Si il retrouve une méthode de même nom dans un des parents suivants, il l’ignore. (Je dis un DES parents suivants car vous pouvez avoir 10 parents si vous voulez).

Donc dans notre exemple, si je fais :

class ProtectionOffensive(Arme, Protection):
    pass

ProtectionOffensive n’aura que la méthode __init__ de Arme. Or ce n’est pas ce qu’on veut. On va donc overrider la méthode __init__, et dedans appeler la méthode __init__ de Arme ET celle de Protection.

Cette syntaxe : Classe.methode(self, args...) que l’on retrouve dans Arme.__init__(self, nom, degat) est juste un moyen d’appeler spécifiquement la méthode du parent.

Dans la partie précédente, je vous ai montré qu’on pouvait faire cela avec super(). Or super() vous retournera la première méthode du premier parent qu’elle trouve : c’est le but de super(), de faire ça automatiquement sans se soucier de savoir qui est le premier parent à avoir une méthode du bon nom.

C’est utile car parfois c’est le parent du parent du parent qui a la méthode qu’on veut appeler. On ne connaît pas forcément son nom, ou alors on ne veut pas l’écrire en dur. Mais dans notre cas, on veut spécifiquement une méthode d’un parent en particulier, il faut donc l’écrire à la main.

D’une manière générale :

  • Utilisez super() quand vous faites de l’héritage simple où que vous voulez juste appeler la méthode du premier parent venu sans vous soucier de son nom (car il peut être très haut dans la chaîne d’héritage).
  • Utilisez Classe.methode(self, args...) quand vous voulez spécifiquement appeler la méthode d’un parent en particulier.

Faites attention !

Le self n’est pas au même endroit dans super(ClassCourante, self).methode(args...) et ClasseParente.methode(self, args...). Et dans le premier cas, on passe la classe courante (que super() va analyser pour trouver les parents automatiquement), dans le cas suivant, on écrit le nom de la classe parente en dur.

Faites quelques tests avec des scripts bidons pour bien comprendre comment ça marche. Faites ça avec des classes toutes simples. Sinon le jour où vous aurez une classe compliquée, vous allez vous embrouiller.

La composition (POO pour les vrais mecs)

Jusqu’ici c’était un tuto avec des notions de base pour des petits geeks imberbes qui jouent avec des action figures fabriquées en Chine et achetées sur ebay. Mais maintenant nous allons voir la POO pour les vrais hommes, les barbus, ceux qui jouent avec des reals dolls et qui n’ont pas peur de mettre des chaussettes dépareillées.

Voyez-vous, un objet, tout seul, il sert à rien. Il s’emmerde déjà, rien à foutre le samedi, nul part où sortir, tout ça. Mais surtout, il a personne pour prendre l’apéro. Non, dans un programme digne d’un vrai pastis, il faut plusieurs objets qui interagissent entre eux. En fait, plusieurs objets qui s’utilisent les uns les autres.

Retournez à notre exemple de jeu video :

class HeroViril(object):
 
    # def __init__(self, nom, prenom, groupe_sanguin, signe_astrologique,
    #              couleur_preferee, tendance_sexuelle, culte,
    #              taille_de_la_troisieme_phallange_de_l_index_gauche)
    # TODO : voir le CDC avec le client pour confirmer les attributs du personnage
    def __init__(self, nom, vie, arme=None, protection=None):
 
        self.nom = nom
        self.vie = vie
        self.arme = arme
        self.protection = protection
 
    def combattre(self, ennemi):
 
        print "{} attaque {}".format(self.nom, ennemi.nom)
 
        while True:
 
            if self.arme:
                self.arme.attaque(ennemi)
 
            if ennemi.vie <= 0:
                break
 
            if ennemi.arme:
                ennemi.arme.attaque(self)
 
            if self.vie <= 0:
                break
 
        if self.vie > 0:
            print "Victoire de {}".format(self.nom)
        else:
            print "{} est mort comme une pov' merde".format(self.nom)

Et là vous notez un truc, c’est que nous n’avons pas de méthode attaque() sur notre héros. Nous utilisons la méthode attaque d’un objet arme. Que l’on a en attribut.

C’est cela la composition : un objet, qui en fait est composé de plusieurs sous-objets. Dans notre cas, notre objet héros est aussi composé d’une arme et d’une protection, qui sont ses attributs. Il peut ainsi utiliser le comportement de ses objets pour faire le boulot à sa place : c’est ce qu’on appelle la délégation.

Reprenons notre code des armes, un peu adapté :

# le code de l'armure ne change pas
class Protection(object):
 
    def __init__(self, nom, armure):
 
        self.nom = nom
        self.armure = armure
 
    def defend(self, degat):
 
        degat = degat - self.armure
        if degat < 0:
            return 0
 
        return degat
 
 
# on change le code de l'arme, si la cible a une protection
# cela diminue les degâts pris
class Arme(object):
 
    def __init__(self, nom, degat):
 
        self.nom = nom
        self.degat = degat
 
    def attaque(self, cible):
 
        # je mets aussi quelques prints pour le lulz
        if cible.protection:
            degat = cible.protection.defend(self.degat)
            print "{} - {} = {}".format(cible.vie, degat, cible.vie - degat)
            cible.vie -= degat
        else:
            print "{} - {} = {}".format(cible.vie, self.degat, cible.vie - self.degat)
            cible.vie -= self.degat

Maintenant créons deux héros, armons-les, et faisons-les combattre :

>>> gosu = HeroViril("Drizzt Do'Urden", 2000)
>>> gosu.arme = Arme('Lame Vorpale', 10)
>>> gosu.protection = Protection("Maille en Kevlar de mithril doré a l'adamantium", 10)
>>> noob_qui_repop = HeroViril("Bob", 200)
>>> noob_qui_repop.arme = Arme('Cure-dent', 1)
>>> noob_qui_repop.protection = Protection("Slip", 1)
>>> noob_qui_repop.combattre(gosu) # yaaaaaaaaaaaaaaaaaaaaaaa !
 
Bob attaque Drizzt Do'Urden
2000 - 0 = 2000
200 - 9 = 191
2000 - 0 = 2000
191 - 9 = 182
2000 - 0 = 2000
182 - 9 = 173
2000 - 0 = 2000
173 - 9 = 164
2000 - 0 = 2000
164 - 9 = 155
2000 - 0 = 2000
155 - 9 = 146
2000 - 0 = 2000
146 - 9 = 137
2000 - 0 = 2000
137 - 9 = 128
2000 - 0 = 2000
128 - 9 = 119
2000 - 0 = 2000
119 - 9 = 110
2000 - 0 = 2000
110 - 9 = 101
2000 - 0 = 2000
101 - 9 = 92
2000 - 0 = 2000
92 - 9 = 83
2000 - 0 = 2000
83 - 9 = 74
2000 - 0 = 2000
74 - 9 = 65
2000 - 0 = 2000
65 - 9 = 56
2000 - 0 = 2000
56 - 9 = 47
2000 - 0 = 2000
47 - 9 = 38
2000 - 0 = 2000
38 - 9 = 29
2000 - 0 = 2000
29 - 9 = 20
2000 - 0 = 2000
20 - 9 = 11
2000 - 0 = 2000
11 - 9 = 2
2000 - 0 = 2000
2 - 9 = -7
Bob est mort comme une pov' merde

Ok, j’ai pigé le principe, mais comment ça marche dans le détail ?

Regardons la méthode combattre de plus près :

    # elle attend un ennemi en paramètre, donc UN OBJET HeroViril
    # self est l'objet en cours, donc aussi un objet HeroViril
    def combattre(self, ennemi):
 
        print "{} attaque {}".format(self.nom, ennemi.nom)
 
        # une petite boucle infinie. Warning, c'est un tuto. Ne faites pas
        # ça chez vous les enfants.
        # cette boucle loop pour toujours si il n'y a pas d'attribut arme donc
        # ceci n'est qu'un exemple. Hein ? Noté ? Les deux du fond là ?
        while True:
 
            # on donne le premier coup à la personne qui attaque (l'objet en
            # cours). On vérifie qu'il a une arme. Si c'est le cas,
            # on appelle la méthode de l'arme "attaque()", et on lui passe
            # en paramètre l'ennemi.
            if self.arme:
                self.arme.attaque(ennemi)
 
            # condition de sortie de la boucle sur la vie du héros qui a pris
            # le coup
            if ennemi.vie <= 0:
                break
 
            # ensuite on fait pareil à l'envers pour donner une chance à l'autre
            # de répliquer : on vérifie que l'ennemi a une arme, et si c'est
            # le cas, on applique la méthode "attaque" de l'arme à l'objet
            # en cours
            if ennemi.arme:
                ennemi.arme.attaque(self)
 
            # condition de sortie de la boucle sur la vie du héros qui a pris
            # le coup
            if self.vie <= 0:
                break
 
        # une fois sorti de la boucle, on vérifie le niveau de vie pour
        # désigner le vainqueur
        if self.vie > 0:
            print "Victoire de {}".format(self.nom)
        else:
            print "{} est mort comme une pov' merde".format(self.nom)

Donc combattre utilise un objet arme, et appelle sa méthode attaque() sur un héros (j’ai viré les prints pour rendre le truc plus clair) :

    # self est l'objet en cours, donc l'arme.
    # cible est un héros, puisqu'on l'a passé en paramètre.
    def attaque(self, cible):
 
        # si la cible (l'objet héros) a un attribut protection,
        # les dégâts retirés sont diminués (ce calcul est fait par la protection)
        if cible.protection:
            cible.vie -= cible.protection.defend(self.degat)
 
        # sinon, on retire les dégâts à la vie de la cible (le héros)
        # directement
        else:
            cible.vie -= self.degat

Diantre ! La méthode attaque utilise elle-même la méthode defend() de la protection :

    # self est l'objet en cours, donc la protection.
    # degat est un simple int.
    def defend(self, degat):
        # on retourne les degâts infligés, moins la protection
        return degat - self.armure

Pour comprendre tous ces codes, il faut bien piger deux trucs :

  • Il y a 6 objets. Deux héros, deux armes, et deux protections. Les héros ont les armes / protections comme attributs.
  • On se sert des méthodes des armes pour attaquer. On passe les héros en paramètres à ces méthodes. Les armes se servent des protections des héros pour calculer les dégats.

Ce dernier point est le plus important. Si vous comprenez ça, vous avez maîtrisé le plus important de la POO. Relisez le plusieurs fois :

Les héros ont une référence aux armes. Et ensuite, on passe une référence des héros aux armes. Les armes retirent de la vie à ces héros, non sans calculer les dégâts en fonction de la protection qu’ils portent.

Les objets ont tous des références les uns vers les autres. Ils se manipulent tous les uns les autres.

Cela fait bizarre car dans la vie une épée ne manipule pas un héros (bon, je connais peu d’elfes noirs IRL aussi). On comprend facilement qu’un héros ait un attribut ‘épée’, mais il est difficile de comprendre qu’une épée ait une méthode, et que le paramètre de cette méthode soit un héros.

C’est un concept purement informatique : la logique des dégâts est codée dans l’arme, pas dans le héros. L’avantage de cette architecture, c’est que si vous changez l’arme, vous changez toute la logique des dégâts. Par exemple, vous rajoutez une arme empoisonnée :

class ArmeMegaEmpoisonnee(Arme):
 
    def __init__(self, nom, degat, poison=100000):
        super(ArmeMegaEmpoisonnee, self).__init__(nom, degat)
        self.poison = poison
 
 
    def attaque(self, cible):
 
        # on prend les degâts une premiere fois
        super(ArmeMegaEmpoisonnee, self).attaque(cible)
 
        # puis on prend les dégâts du poison
        cible.vie -= self.poison

Cette arme fait plus de dégâts. Son mécanisme pour faire des dégâts est différent. Il suffit d’équiper un héros avec l’arme (en changeant l’attribut) pour que ce nouveau calcul de dégâts soit pris en compte.

>>> noob_qui_repop.vie = 200 # rePOP !
>>> noob_qui_repop.arme = ArmeMegaEmpoisonnee('Cheat Code', 1)
>>> noob_qui_repop.combattre(gosu) # Vengeance !
Bob attaque Drizzt Do'Urden
2000 - 0 = 2000
Victoire de Bob

Ce qu’il faut retenir : on peut mettre des objets en tant qu’attributs d’objets. Il n’y a pas de limite dans les nombres d’objets à mettre, leur mélange, les niveaux d’imbrications, etc. On peut mettre des objets, dans des objets, dans des objets… C’est la composition.

Et les objets peuvent utiliser les méthodes des autres objets. Et on peut passer des objets comme paramètres à des méthodes. C’est la délégation.

C’est la partouze des objets, quoi.

On peut mettre des objets dans des sets, des dicos, des listes… Par juste dans des attributs. Il y en a des choses à faire !

Choisir entre l’héritage et la composition

Les deux techniques permettent de réutiliser du code, mais pas de la même façon. Aucune règle générale ne tient la route dans tous les cas, mais un bon point de départ est de se dire que :

  • Si vous avez deux objets de même nature et que l’un est une spécialisation de l’autre (Garçon est une spécialisation de Personne, Voiture est une spécialisation de véhicule, Clio est une spécialisation de voiture, Reptincel est une spécialisation de pokemon, foxmask est une spécialisation du correcteur orthographique de Word, etc), alors utilisez l’héritage.
  • Si vous avez deux objets qui échangent des données, qui sont associés ou qui dans la vie réelle sont des ‘part de’ (un article est une partie d’un blog, Raymond Barre est membre d’un parti, Raymond fait partie d’un bar, le bar et la raie font parti des poissons ah non ça c’est une spécialisation, etc), utilisez la composition.

Il y a aussi, quelque part, dans le lointain pays des enculeurs de mouche, une différence entre l’agrégation (qu’on a pas vu) et la composition. Vous vivrez très bien en considérant que c’est la même chose.

Le design pattern “stratégie”

Vous entendrez parfois parler du motif de conception “stratégie”. C’est en fait une mise en application abstraite de la composition.

Normalement la composition s’utilise avec des “part de” concrètes. Vous avez une voiture : elle est composée d’objets pneus, d’un objet moteur, etc.

Le design pattern stratégie est l’extraction d’une part du comportement d’un objet pour le mettre dans un autre objet, mais la nature de l’objet importe peu. Ceci est fait purement pour découpler le comportement de l’objet.

On a vu plus haut que changer l’arme permet de changer le calcul des dégâts. C’est ce type de résultat qu’on vise avec le design pattern strategy.

import os
 
class ParseurXml(object):
    ...
 
class ParseurJson(object):
    ...
 
 
class ParseurDeFichier(object):
 
    _strategy = { # les stratégie par défaut
        'json': ParseurXml,
        'xml': ParseurJson
    }
 
    def __init__(self, fichier, strategy=None):
 
        self.fichier = fichier
        # on récupère l'extension du fichier
        path, ext = os.path.splitext(fichier)
 
        # Strategy est une classe de parseur
        # on la récupère depuis les paramètres ou selon
        # l'extension
        Strategy = strategy or self._strategy[ext.lstrip('.')]
        # on instancie notre classe de strategie
        self.strategy = Strategy(fichier)
 
    def parse(self):
        # on délègue le boulot à la stratégie
        self.strategy.parse()

La ligne la plus importante est :

Strategy = strategy or self._strategy[ext]

Ici on dit récupérer la stratégie de parsing en paramètre, ou sinon, la bonne en fonction de l’extension de fichier. On charge donc une classe dynamiquement, on va créer un objet à partir de cette classe. Et c’est cet objet à qui on va déléguer le comportement du parseur :

    def parse(self):
        self.strategy.parse()

On utilise l’objet dynamiquement pour gérer tout le parsing. On peut ainsi choisir un parseur à la volée.

La pattern strategy mélange donc composition (l’objet strategy est une part de l’objet général), délégation (l’objet général utilise le comportement de l’objet strategy) et d’injection de dépendance (on peut changer l’algo à la volée, il suffit de changer de stratégie).

Bon, ça c’était le mode hard. C’est pas grave si vous finissez pas le niveau toute de suite. Mettez pause. Allez pisser un coup et revenez- plus tard, les continues sont infinis sur le blog.

C’est bon, vous êtes chaud ? Prochaine stations, le monde merveilleux des méthodes magiques.

64 thoughts on “Le guide ultime et définitif sur la programmation orientée objet en Python à l’usage des débutants qui sont rassurés par les textes détaillés qui prennent le temps de tout expliquer. Partie 5.

  • Anucunnilinguiste

    J’ai envie de pleurer, c’est beau ! Et maîtriser ces concepts est quasi indispensable lorsqu’on développe des applications en Qt par exemple et de l’interface graphique en général ! Si vous voulez voir de beaux exemples d’application de ces concepts, regardez le framework Kivy ou Pyside/PyQT.

    Par exemple :

    class PannelScreen(Screen):

    def __init__(self, cities, **kwargs):
    super(PannelScreen, self).__init__(**kwargs)
    gridlayout = GridLayout(cols=2, padding=10, spacing=10)
    for city in cities:
    btn = Button(text=u'%s' % city, font_size=25)
    btn.bind(on_press=self.screen_call)
    gridlayout.add_widget(btn)
    self.add_widget(gridlayout)

    def screen_call(self,obj):
    viewer.ville = obj.text
    root.current = 'viewer'

    Ici on hérite de la classe Screen, donc héritage simple de spécialisation, pas d’héritage multiple sur ce code. Mais on utilise la délégation pour btn en faisant appel à la classe Button.

    On utilise des propriétés au sens Kivy : ville qui a été déclarée ailleurs en tant que ville = StringProperty() dans la classe Viewer.

    On utilise des signaux/slots avec le binding du callback “screen_call” sur le bouton.

    Oh Yeah !

    Désolé pour l’indentation, je suis pas familier de la saisie de code dans vos commentaires encore…

  • sil

    L’héritage multiple est une technique certes très élégante. Néanmoins, son emploi rend parfois difficile la compréhension du code.

  • Sam Post author

    Et en plus on peut s’en passer la plupart du temps avec de la strategy bien placée.

    Néanmoins c’est utile pour les mixins comme par exemple dans django-rest-framework.

  • entwanne

    Juste une remarque : dans le 3ème exemple de code, Protection.__init__ est appelé avec degat au lieu d’armure

  • roro

    Je suis quasiment certain qu’il y a un bonne quantité de personnes qui visitent ce site, et qui n’osent pas poser des questions de peur de passer pour des bourricots.
    Voici donc la question:
    Ce tuto m’a tout l’air d’être fonctionnel. Il serait de bon alloua de faire un: “pas à pas”, pour permettre aux “vrais débutants” de le faire fonctionner. En n’omettant aucune action.
    Ce “pas à pas” Pourrait se nommer: “Ouverture de la gueule du python”.

  • Sam Post author

    J’ai pas compris. Pas à pas ? Genre en partant du niveau 0 de Python ?

  • Kontre

    Un genre de doctest, mais en une seule fois ? Ça va faire une Arme(‘Wall of text’, 1e32), et sur un noob en slip, ça fait mal…

  • Anucunnilinguiste

    “Je suis quasiment certain qu’il y a un bonne quantité de personnes qui visitent ce site, et qui n’osent pas poser des questions de peur de passer pour des bourricots.”

    Non, ça c’est sur le newsgroup python fr seulement ;), ici on est un peu tous débutant ! Débutant en quoi d’ailleurs, en algo, en analyse et architecture, en codage ? En algo on l’est toujours un peu tous ;)

  • roro

    @Sam “J’ai pas compris. Pas à pas ? Genre en partant du niveau 0.”
    Naan, juste: “Ce code est parfaitement fonctionnel, nous allons le prouver

    céans.”
    1)- Disposer les oignons # -*-coding:Latin-1 -ou: #

    -*-coding:Utf-8 – selon que vous serez puissant ou misérable

    sous windoze ou sous nunuxe / pompom au fond d’une cocote

    en tête de code.
    Note: Ici, est-il vraiment besoin d’expliquer pourquoi des oignons? A ce stade, certes non.

    C’est comme ça, c’est la recette.
    2)-…Tu connais la suite
    On peut signaler que la recette peut être réalisée à l’étouffée en shell, ou à découvert en console.
    Et si on précise les particularités d’incorporation des ingrédients pour les deux cas; alors oui, le mot “débutants” aura sa place dans le titre. Car dans “débutants”, il y a: “début”.
    Mais bien sûr rien ne t’oblige à te baisser autant, bien que ce soit en général sous le niveau “0” du sol que se trouve le minerai.
    Je tiens à ta disposition une béta testeuse de sept ans qui fait faire les pieds au mur à sa souris.

  • kontre

    je dirais que le guide en cours, c’est “la POO en python pour débutants”, pas “python pour débutants”. Pour le second, le site du zéro est peut-être plus approprié ? D’ailleurs, il y a une liste de pré-requis en haut de chaque partie ! ;)

  • Sam Post author

    Je crois que kontre a bien répondu. Pour apprendre la POO, on a besoin d’avoir des déjà des bases en Python. Il n’y a pas d’alternative : on ne peut pas apprendre le saut à ski avant de savoir faire le chasse neige. C’est un peu le guide du saut à ski pour débutant. Débutant en saut à ski. Je m’embrouille là ou bien ?

    Cela dit globalement la plupart des exemples marchent en copier / coller. A part le dernier je crois qu’ils marchent sous, vu que je les ai codé sous dreampie.

    Mais je comprends ce que tu veux dire, ça peut être frustrant d’avoir un truc qu’on ne peut pas comprendre parce qu’il manque des bouts. Et c’est dur de poser la question car on n’a pas envie de se faire ravager en comment.

    Le mieux que je puisse faire (à part écrire un tuto Python pour débutants) :

    Si il y a un truc qui foire sa race, me le signaler que je corrige. Si il y a des questions sur des informations qui manquent : me poser la question et je rajoute ou je pointe sur un tuto qui existe déjà pour donner des explications sur le sujet.

    Sinon, j’invite tout le monde à aller sur le forum de l’afpy pour demander de l’aide sur les détails de base.

    Un jour peut être on aura ce genre de forum sur le site.

  • roro

    J’ai bien vu ça, je me faisait juste cette réflexion en rapport avec le topic: ” Comment dynamiser la communauté…”, et l’analyse des coms qu’il avait suscités; en particulier concernant le renouvellement générationnel.

  • roro

    @Kontre: “Un genre de doctest, mais en une seule fois ? Ça va faire une Arme(‘Wall of text’, 1e32), et sur un noob en slip, ça fait mal…”
    Si je te lis “entre les lignes”, et pour pousser un peu le bouchon, je crains de devoir conclure; qu’il est plus aisé d’expliquer l’esprit de la POO, que sa réelle mise en oeuvre.
    Bon, je vais traire ma chaise…à+

  • kontre

    Tiens, ça me donne une idée : pourquoi ne pas mettre un lien en fin d’article, qui permettrait de télécharger un script contenant uniquement le code des exemples, exécutable ? Ça rejoint ce que voudrait roro, je crois.

    Ici, ça donnerait par exemple http://pastebin.com/3sSJAsGm

    Et au passage, j’ai vu une ou deux erreurs à corriger ! ^^

  • cyp

    Toujours aussi fun et intéressant, je crois que j’ai quasi tout capté référence a Stormbringer comprise.

    J’ai juste trouvé le passage sur le design pattern un peux rapide sur la fin (nouveau concept et nouvel exemple en quelques lignes).
    Du moins c’est un peux chaud quand on en est encore a digérer la partie qui précède.

  • Sam Post author

    Oui la dernière partie est plus pour faire découvrir le concept du design pattern stratégie. Je ne m’attend pas à ce que tout de suite les gens popent et que tout deviennent clair. L’abstraction, ça demande beaucoup plus qu’un tuto pour être maitrisé, ça demande pas mal de pratique.

  • GB

    excellent tuto, j’adore et j’adhère

    2 petits points cependant, dans le dernier exemple, (concernant la classe ParseurDeFichier)

    1) je pense que tu voulais très certainement écrire une méthode __init__ et non __ini__ ;)

    2) Dans cet __init__ justement, si je me rappelle bien, la méthode

    os.path.splitext

    renvoie un tuple (path, ext) où l’extension possède le ‘point’ du fichier.

    Ainsi, ta variable ext contiendra une valeur du genre ‘.json’ ou ‘.xml’ … ce qui ne correspond à aucune clé du dictionnaire self._strategy

  • roro

    Hey, je m’attendais à une attaque au pied de biche.
    Quelle agréable surprise!
    Je transvase…Si la testeuse me le fait fonctionner. Je mange mon chapeau…Sans boire.

  • Sam Post author

    C’est pas con cette idée de mettre en fin de tuto un script complet avec uniquement les exemples éxécutables.

    Il faudrait que je standardise les tutos:

    – un intro
    – le tuto complet
    – un résumé genre cheat sheet
    – les ressources incluant des liens pour allez plus loins et les codes source complets à lire ou télécharger

    Sur 4 onglets, ça pourrait être pas mal. On en revient à faire une plateforme de tutos…

  • kontre

    (l’autre droite pour le point, Sam ^^)

    Je ne suis pas trop pour avoir 4 onglets. Je vois bien que ça structurerait le système, mais ça me semble trop lourd à l’utilisation (sans parler de la gestion pour vous). Par exemple, il faut activer l’onglet pour savoir ce qu’il y a dedans. Juste une partie en plus à la fin avec le code source et des liens pour aller plus loin, je trouve que ça suffit largement.
    Rajouter un lien en début d’article pour le code serait intéressant, histoire d’exécuter les exemples en même temps qu’on lit l’article. Sur http://www.pythontutor.com c’est très visuel (j’avais complètement oublié ce site, merci cyp).

    Je ne connaissais pas dreampie, mais après avoir testé 5 min je préfère Reinteract pour élaborer des scripts de ce genre.

  • Sam Post author

    Je suis un boulet. Merci kontre.

    Je vais essayer reinteract aussi. J’ai pas l’usage de scipy ni matplotlib mais si l’ergonomie est bonne, faut pas s’en priver.

  • Sam Post author

    Le concept de reinteract est brillant. Il est un peu buggé par contre (plantage en 5 minutes d’usage). Mais pour expérimenter avec Python, ça mérite un article.

  • roro

    Compte rendu du béta test:
    Bon, au lancement, on a tout pris sur la tronche.
    Préconisations:
    a)- Débugger la partie existante. lignes 34, 72 et 138 ???
    b)- Boucler le code quelque part.
    c)- Inclure une tempo entre chaque affichage pendant le combat.
    On attend l’avis des experts.

  • kontre

    Comment ça, débugguer ? Quand j’ai testé ça tournait bien, je ne vois pas d’erreurs aux lignes que tu indiques.

    Je me faisais aussi une réflexion sur la temporisation mais plutôt entre les parties. Tu dois pouvoir rajouter un appel à raw_input() aux endroits où tu veux que ça s’arrête.

    Si ça continue les exemples de code vont devenir de vrais tutos interactifs !

  • roro

    C’est bon, ce matin ils ont repris ta version, ça marche nickel.
    Hier, ils ont commencé avec la version de cyp qui était montée avec des N° de lignes, quand ils en ont eu viré les N°, l’indentation en avait pris un coup, et quand aprés avoir galérer un moment à réindenter; il leur est sorti ça:
    return outside fonction line 33 (le return 0, de Protection)
    Ils ont tout envoyé péter. Quand ils ont pris la tienne, ils n’avaient plus les yeux en face des trous.
    Donc, il y deux adeptes de plus dans la communauté.
    Le renouvellement générationnel est assuré.
    Mon insistance venait du fait qu’ils voulaient absolument se débrouiller seuls face au tutos, et tant qu’il n’y avait pas un truc qui marche “de suite”, ils n’y croyaient pas.
    Sam qui dit: posez des questions !?!
    Ici, ils en ont rempli quatre pages de questions.

  • Sam Post author

    Merci du retour roro. C’est toujours difficile de se remettre en cause, donc ne nous en veut pas trop si on tique sur certains trucs. Il faut pouvoir changer de point de vue, s’adapter, etc.

    Dans tous les cas, je note tout ce que tu dis. Parfois j’ai le courage d’y réfléchir, parfois non. Mais je pense qu’on s’améliore petit à petit.

  • roro

    A voir le rythme de parution de tes articles, et le rythme de ton tapotage de clavier, je pense que tu est un frénétique.
    Tout ce qu’on peut faire, c’est essayer de te suivre, la langue pendante, les poumons en feu, et les pieds en sang.
    Mais on tient bon.

  • Sam Post author

    Il est vrai que Max à tendance à dire que je suis un enfant hyperactif. Mais j’ai de bons mécanismes de compensation pour m’éviter l’autisme. Enfin je crois.

    La théorie de Max, c’est que c’est une question de bouffe tout ça :-)

  • roro

    Sûr que si tu bouffe des ressorts, c’est normal que tu fasse boing!.. booing!

  • kontre

    En effet, sur pythontutor il faut copier le code en mode édition et par en mode exécution, sinon tu choppes les numéros de ligne. C’est vicieux pour un débutant.

  • Feadurn

    Je ne suis pas sur, mais il me semble qu’il manque l’attribut protection dans l’__init__ de la class HerosViril. En tout cas moi j’ai une erreur si je ne le rajoute pas.

  • roro

    @Feadurn. Du haut de mon minuscule savoir: il me semble que c’est dans la classe Protection que “nom” se rapporte à HerosViril.
    En tous cas, chez moi il n’y a pas de protection dans l’init de HerosViril, et ça marche très bien.
    Le maître va gronder, sur toi ou sur moi. P’tet bien sur les deux.

  • Feadurn

    @Roro J’ai peur maintenant!

    Sinon, je pensais que le “nom” se rapporte au nom de la protection et non au nom de HeroViril. Mais bon de la à vraiment savoir moi même de quoi je parle, il n’y a qu’un pas que je ne franchirai pas.

  • roro

    Perso, avec les scripts python, je suis comme dans les catacombes, avec une boite d’allumettes et une demi bougie.

  • kontre

    @Feadurn : Bien vu, il y a en effet un souci de ce côté là.

    Le micmac, c’est que protection n’est pas défini dans l’__init__ mais est rajouté dans l’exemple ensuite :

    gosu.protection = Protection("Maille en Kevlar de mithril doré a l'adamantium", 10)
    noob_qui_repop.protection = Protection("Slip", 1)

    et c’est pour ça que ça marche ici. Par contre, si tu fais des exemples de ton côté sans mettre de protection, tu choppes une MST.

    Il faudrait avoir :

    class HeroViril(object):
        def __init__(self, nom, vie, arme=None, protection=None):
            self.nom = nom
            self.vie = vie
            self.arme = arme
            self.protection = protection

    (J’ai corrigé l’article, on va dire que c’était une grosse faute d’orthographe)

    @roro : dans la classe Protection, nom se rapporte bien à la Protection, tu donnes le nom dans le constructeur :

    Protection("Maille en Kevlar de mithril doré a l'adamantium", 10)
    Protection("Slip", 1)

    Ici, c’est Slip et Maillemachin.

  • roro

    @Kontre. dans class HeroViril, j’ai ça:
    def __init__(self, nom, vie, arme=None):
    Et ça marche très bien. Mais j’ai fais un mic-mac avec les classes j’ai mis:
    class Arme(object):
    class Protection(object):
    class ProtectionOffensive(Arme, Protection):
    class HeroViril(object):
    class Protection(object):
    et j’ai embrayé avec le changement qui fait gagner noob
    class Arme(object):
    tiens, c’est bizarre j’ai deux class Protection(object)
    Y’a eu du rabe de mic-mac.

  • roro

    Mayrde, on peut pas éditer.
    En fait le changement, c’est plus loin avec:
    class ArmeMegaEmpoisonnee(Arme):
    Et un nouveau combat.

  • roro

    En fait, ce tuto, l’air de rien il était assez violent pour des noob’s.

  • kontre

    @roro : Oui, les exemples marchaient parce que la protection était redéfinie à chaque fois. Mais c’était pas très joli-joli. La modif que j’ai faite permet que ça fonctionne dans tous les cas. Sinon ça plante si tu lances cet exemple sans les protections :

    gosu = HeroViril("Drizzt Do'Urden", 2000)
    gosu.arme = Arme('Lame Vorpale', 10)
    #gosu.protection = Protection("Maille en Kevlar de mithril doré a l'adamantium", 10)
    noob_qui_repop = HeroViril("Bob", 200)
    noob_qui_repop.arme = Arme('Cure-dent', 1)
    #noob_qui_repop.protection = Protection("Slip", 1)
    noob_qui_repop.combattre(gosu) # yaaaaaaaaaaaaaaaaaaaaaaa !

    Y’a pas mal de concepts en POO, et vu qu’en python on peut modifier les classes à la volée, ça augmente fortement le potentiel d’embrouilles. Je te rassure, une fois que ça a fait tilt tout est bien logique et c’est génial à utiliser.

    En relisant le tuto, je m’aperçois que je me suis amusé à faire des embrouilles avec __new__ alors que j’aurais dû utiliser une strategy et ça aurait été vachement plus simple. Merde.

  • roro

    @Kontre; Tu a modifié le code qui est sur cette page ?
    Je ne vois plus le lien de téléchargement du code.

  • kontre

    :Oui, j’ai changé

    class HeroViril(object):
        def __init__(self, nom, vie, arme=None):
            self.nom = nom
            self.vie = vie
            self.arme = arme

    en:

    class HeroViril(object):
        def __init__(self, nom, vie, arme=None, protection=None):
            self.nom = nom
            self.vie = vie
            self.arme = arme
            self.protection = protection

    Je crois que les liens vers le code sont apparus plus tard que cet article, c’est pour ça qu’il n’y a pas de lien.

  • foxmask

    wow !
    j’etais bien jusqu’à composition :)
    bon apres le pattern strategy heu faut que je relise :)
    j’ai bien aimé Raymond l’enculeur de mouches ;)

  • Réchèr

    Coucou ! Les trucs du jour.

    (que super() va l’analyser pour trouver les parents automatiquement)
    (que super() va analyser pour trouver les parents automatiquement)

    avec des actions figures
    avec des action figures
    (Pas sûr de moi sur ce coup là. Les anglicismes, j’ai rien contre, mais on sait pas forcément comment les accorder).

    # self est l’objet en cours, donc l’arme
    # cible est un héros, puisqu’on l’a passé en paramètre

    # self est l’objet en cours, donc l’arme.
    # cible est un héros, puisqu’on l’a passé en paramètre

    Je comprenais rien à ce commentaire, jusqu’à ce que je réalise que ce sont deux phrases séparées.

    # self est l’objet en cours, donc la protection
    # degat est un simple int

    # self est l’objet en cours, donc la protection.
    # degat est un simple int

    On peut mettre des objets dans des sets, des dicos, des listes… Par juste des attributs. Il y en a des choses à faire !
    On peut mettre des objets dans des sets, des dicos, des listes… Pas juste dans des attributs. Il y en a des choses à faire !

    Raymond fait parti d’un bar,
    Raymond fait partie d’un bar,

    le bar et la raie font parti des poissons,
    Justement, non.
    le bar et la raie sont des spécialisations de poissons,
    Du coup, ça casse un peu l’exemple. Je pense qu’il faut carrément enlever ce bout de phrase, car il embrouille le lecteur. C’est dommage parce que c’est très drôle.

  • Roro

    Merde…On a même un poissonnier !
    J’espère que tu ne vends pas du python pour de la morue.

  • sensini42

    Sans transition:

    Etre : Être
    moult : pas invariable si pas adverbe -> moultes manières

    # appelle le __init__ de arme : commentaire tout nul : # appelle le __init__ des parents juste au dessus, c’est un peu mieux

    Si il retrouve : S’il retrouve

    doré a l’adamantium : si c’est la maille qui est dorée : +e et À l’adamantium

    J’ai ri (sans Tom) quand Bob est mort…

    objet arme : Arme + balise code

    return degat – self.armure : return max(0,degat – self.armure) ?

    Dans la victoire de Bob le tricheur, on s’attend à voir 2000 – 100000 du poison ->

    2000 - 0 = 2000 #attaque «normale» à 1
    #effet du poison
    Victoire de Bob

    Par juste dans des attributs : Pas

    (Garçon est une spécialisation de Personne, Voiture est une spécialisation de véhicule, Clio est une spécialisation de voiture, Reptincel est une spécialisation de pokemon, foxmask est une spécialisation du correcteur orthographique de Word, etc) : etc. + Majuscule au nom des «classe» + en balise code ?

    le bar et la raie font parti des poissons ah non ça c’est une spécialisation, etc : etc. + partiE

    #les stratégie par défaut : stratégies

    #on instancie notre classe de strategie : notre classe Strategy /de stratégie

    Ici on dit récupérer : Ici, on dit de récupérer

    objet strategy : manque les balises code 2 3 fois

    Prochaine stations : sans s

  • Vivien

    Petite question sans rapport direct avec l’objet de l’article, mais un peu quand même :

    Les objets qui interagissent entre eux via la composition font donc appel à des méthodes d’autres objets. Ça me perturbe. N’ayant jamais fait autre chose que de la procédurale, pour moi une fonction doit être déclarée avant d’être appelée (patapé si j’utilise pas le bon vocabulaire, je suis un gros noob).

    Mais il semble que ça ne soit pas le cas avec les objets vu que l’interaction est dans les deux sens.

    Du coup, comment ça se passe ? L’interpréteur “met de côté” les attributs et méthodes dont il ne trouve pas immédiatement de référence ? Ou alors il y a bien un ordre pour déclarer les classes ?

  • Sam Post author

    Tous les objets sont instanciés avant que les méthodes soient appelées.

    Ne confont pas l’ordre de déclaration du code et son exécutation. Dans :

    “`

    class Truc(object):

    def init(self):

    print(‘machine)

    t = Truc()

    ““

    La méthode init est bien déclarée au milieu, mais est exécutée à la fin. C’est pareil pour toutes les interactions de l’article : regarde l’ordre de création des objets, et tu verras qu’ils sont toujours créés avant d’être utilisés. C’est une des difficultées de la POO : voir quand quel code est appelé.

  • Vivien

    @Sam : intéressant, je pensais pas qu’il y avait 2 étapes en fait (ou plus, si ça se trouve). C’est le genre de chose qu’on peut savoir en étudiant la doc ou y’a d’autres sources d’information, genre fichier de log ou analytics ?

  • Sam Post author

    C’est comme ça dans tous les langages objets. En vérité il y a plus de 2 étapes. Le guide sur le POO comprends 8 parties, qu’il faut lire dans l’ordre pour bien se mettre le workflow dans la tête. Met des print() dans les méthodes pour voir leur ordre d’appel.

  • Vivien

    Ok système D donc. Pour le guide, j’en suis à la suivante, pas encore tout lu, je reviendrais peut-être sur certains chapitre avant d’ailleurs. Merci beaucoup en tout cas pour ce tuto qui explique vraiment bien les choses. :)

  • aikinhdo

    Bonjour,

    C’est moi qui comprend pas ou self.arme et self.projection ne servent à rien dans la classe HeroViril ?

    def init(self, nom, vie, arme=None, protection=None):

        self.nom = nom
        self.vie = vie
        self.arme = arme
        self.protection = protection
    

  • Feadurn

    @aikinhdo

    Je suppose que la question porte sur le fait que arme=None et protection=None dans le def init().

    Si c’est le cas, alors si ca sert a quelque chose mais la classe est ecrite dans lpour qu’ un Heros ait obligatoirement un nom et de la vie mais pas necessairement une arme et une protection.

    Le (... arme=None, protection=None) sert dans le cas ou tu instancie un nouvel heros viril tu n’est pas oblige de leur donner une arme et une protection parce que la valeur par defaut c’est None (par contre il ne pourra pas attaquer ou se proteger.

    Donc ca permet de faire comme ca:

    Popeye = HeroViril(nom=Popeye, vie=1000)

    Sans que python gueule parce qu’il lui manque des attributs.

    Note que faire Popeye = HeroViril(nom=Popeye, vie=1000, arme=None, protection=None) donnera le meme resultat.

  • Sam Post author

    Ils définissent les caractéristiques du héros, qui sont utilisés par l’objet arme pour calculer les dégats.

  • Maykiwogno

    Merci vraiment pour tes tutos.

    Dans la partie stratégie.

    Le mot ‘Strategy’, c’est un mot clé python de la classe Strategy ou juste un mot pour l’exemple ?

    Et la méthode parse:

    def parse(self):

    self.strategy.parse()

    J’ai pas bien compris à quoi elle servait. Je pensais que le parsing était délégué aux 2 classes ParseurXml ou ParseurJson.

    Merci

  • Mrgn

    Hello,

    Merci pour ce tuto (très bien fait depuis la partie 1), je galérais à implémenter une partie de mon appli, et je me dis “allez on va relire les bases”. ET BOUM je tombe sur le design pattern strategy qui correspond PARFAITEMENT à ce dont j’avais besoin O____O…

    @Maykiwogno “Strategy/Stratégie” est le nom d’un design pattern, c’est à dire une solution à un problème régulier en informatique.

    design pattern strategy

  • Janzo

    Bonjour,

    Merci pour tous ces posts fort utiles :-)

    Une petite typo-déterrage, juste avant le titre “Choisir entre l’héritage et la composition” :

    On peut mettre des objets dans des sets, des dicos, des listes… Par juste dans des attributs. Il y en a des choses à faire !

    Merci :)

Comments are closed.

Des questions Python sans rapport avec l'article ? Posez-les sur IndexError.