Devenez Pythonisse
icone programmation
Signature :
GNU/Linux Magazine

Retrouvez cet article dans : Linux Magazine 84

Cet article présente un côté technique sur une trame mythologique. Cette forme particulière a pour but de vous faire respirer un peu tout en travaillant. Bien entendu, la technique est au rendez-vous. Cependant, les diverses références à la mythologie grecque n’ont aucune valeur historique et ne devront pas être utilisées au sens strict. Ainsi, au fil des prochaines pages, vous pourrez à la fois vous promener dans un monde imaginaire et dans une documentation hors du commun. La nouvelle pythonisse vient de prendre ses quartiers. Rien ne vaut un tel événement pour apprendre les rouages de l’institution religieuse. Mais d’abord, qui est la pythonisse ? Pythonisse : n. f. Prêtresse du dieu Apollon. La pythonisse pratique sa profession dans la ville de Pytho, encore appelée Delphes. Mais Apollon ne s’est attribué ses services que bien après le commencement. Il a lâchement assassiné Python pour lui voler son oracle ! Vous voyez, cela fait depuis l’Antiquité que le langage Python est envié... Enfin, passons. La pythonisse répète les paroles évangéliques d’Apollon du haut d’un trépied, au-dessus d’un gouffre. Elle doit donc interroger la divinité selon un langage secret que vous découvrirez petit à petit. L’article ne reviendra pas sur les bases de Python. Si vous souhaitez apprendre le langage, rien ne vaut un ouvrage spécialisé tel que Learning Python de Mark Lutz et de David Ascher aux éditions O’Reilly ou encore le célèbre Dive Into Python disponible directement sur Internet. Le langage Python permet de quasiment tout manipuler. Rien ne vous empêchera de redéfinir des fonctions ou des propriétés d’une classe à la volée. Imaginez seulement tous les miracles pouvant être accomplis ainsi ! En bref, certaines propriétés du système sont accessibles directement. Essayez seulement de caser dans une classe la fonction suivante :
print __dict__
Notes : Les shells Python Tous les exemples de cet article peuvent être vérifiés sur un shell Python. La traditionnelle commande Python ne donne pas forcément un cadre de développement agréable. Il existe cependant d’autres shells. IPython est l’un d’eux. Il dispose d’une colorisation ou le lancement automatique du débogueur PDB. L’environnement de développement IDLE intègre également une console agréable en plus d’un éditeur de texte. Enfin, divers environnements complets vous permettront de développer rapidement une application, comme Jext, Eclipse ou encore Vim. Certains sont plus spécifiques comme SPE qui intègre directement un débogueur graphique. La chasse aux bogues peut également s’effectuer avec DDD, le frontend de gdb, à condition de le lancer avec l’option --pydb. Vous avez ainsi un petit aperçu des objets manipulés par Apollon. Vous verrez en fait apparaître l’ensemble des éléments de votre classe sous forme d’un dictionnaire. Il s’agit bel et bien du dictionnaire des données d’un objet, mais rien ne vous empêche de l’utiliser pour une classe non instanciée. Il faut savoir qu’une classe est un objet. Par exemple, la chèvre que vous voyez dans la cage derrière vous est une hostie. Oui, cela est cruel, mais il faut bien faire des sacrifices pour servir les dieux ! La chèvre en question est une instance de la classe chèvre. Cette dernière est elle-même une instance de la classe class. Pour résumer, tout est objet, y compris les classes. Ces quelques notions vont vous servir pour les consultations suivantes. Justement, en parlant de consultation, voici notre premier client. Le pauvre potier a reçu une commande de cinq mille vases. Chacun d’entre eux devra avoir un nombre d’ouvertures, une couleur, un nom et un dessin. Et en plus, le tyran qui a demandé cela souhaite le tout dans trois jours. Évidement, le potier ne pourra jamais satisfaire son ignoble client. Il requiert donc une aide immédiate. Nous allons utiliser une technique particulière pour générer les propriétés. Il serait donc intéressant de multiplier le stock à chaque nouvelle entrée. Pour cela, Python dispose d’une fonction intéressante : property. Le code suivant permet de gérer les stocks.
#!/usr/bin/env python
# -*- coding: iso-8859-15 -*-

# Import du module permettant la copie des objets
import copy

class Stock(object):
    “””
    Classe de stock
    “””
    def __init__(self):
        #On initialise le stock à proprement parler
        self.poteries = []

    def _getter(self):
        # On retourne le dernier élément du stock
        if len(self.poteries) > 0:
            return self.poteries.pop()
        else:
            print “Le stock est vide”
            return None

    def _setter(self, p):
        # On ajoute un élément au stock
        self.poteries.append(p)
        self.poteries.append(copy.copy(p))

    def _deleter(self):
        # On vide le stock !
        self.poteries = []

    # Il faut bien indiquer le point d’entrée du stock
    poterie = property(_getter, _setter,
        _deleter, “La dernière poterie du stock")

class Test:
    """
    Test du bon fonctionnement du stock
    """
    def __init__(self):
        s=Stock()
        s.poterie = "Pot"
        s.poterie = “Vase”
        s.poterie = “Amphore”
        print s.poterie
        print s.poterie
        print s.poterie
        print s.poterie
        print s.poterie
        print s.poterie
        print s.poterie
        print s.poterie

Test()
Le fonctionnement de ce bout de code est un peu particulier. La classe Stock permet de gérer le stock des poteries. La fonction __init__ est appelée à l’instanciation comme toujours en Python. Le tableau poteries est alors initialisé. Les fonctions _getter, _setter et _deleter sont alors définies. Elles ne seront aucunement appelées directement. La première de ces trois fonctions permet de récupérer la dernière poterie de stock et bien sûr la retirer du stock. La seconde rajoute une poterie et la troisième liquide le stock. Après tout cela, la propriété poterie est définie à partir des trois fonctions déclarées au-dessus. La fonction Python property permet de générer des attributs particuliers. Lorsque l’attribut est récupéré, la fonction _getter est exécutée. La machine virtuelle ne retournera pas la valeur de poterie, mais le résultat de cette fonction. La fonction _setter est exécutée lorsque le code essaie d’affecter une valeur à poterie. L’attribut poterie ne changera pas. Il en va de même pour _deleter qui est lancée lors de la destruction. Le petit Hercule a reçu une punition. Ce sont des choses qui arrivent souvent à l’école. Il devait conjuguer dix mille fois " Je ne dois pas perturber les adultes qui partent en guerre ! " à toutes les personnes. Seulement, il a oublié la fin de la phrase et a simplement écrit " Je ne dois pas perturber les adultes qui partent !". Il va donc falloir corriger cela. Supposons que nous ayons un tableau tout prêt appelé punition. Il contient toutes les phrases écrites par Hercule. Il suffit alors de corriger la phrase.
punition = map(lambda phrase: phrase[:-2]
       + " en guerre" + phrase[-2:], punition)
Cette ligne peut paraître obscure, car elle regorge d’astuces du langage Python. Tout d’abord, la phrase est ici une chaîne de caractères. En tant que telle, il est possible d’atteindre un caractère en connaissant sa position de cette manière : phrase[position]. Mais il est également possible de récupérer une sous-chaîne en connaissant les positions de début et de fin. Par exemple, phrase[5:10] retournera la chaîne de caractères comprise entre les caractères 5 et 10. Si l’une des bornes est omise, la sous-chaîne débutera ou se terminera à l’extrémité. Enfin, il est possible d’utiliser des positions négatives ! Cette particularité est utilisée ici. Le caractère -1 correspond au dernier caractère. Le -2 est le caractère précédent, et ainsi de suite. Maintenant, nous arrivons à un point plus particulier encore. Le mot clé lambda permet de définir une fonction anonyme. Cette fonction correspond à une valeur, une affectation. Ainsi, la fonction ici retourne automatiquement le résultat de l’opération phrase[:-2] + " en guerre" + phrase[-2:] et reçoit en paramètre la phrase. La fonction map, quant à elle, transforme la liste donnée en second argument en appliquant la fonction donnée en premier argument à chaque valeur. Elle retourne la nouvelle liste que nous réaffectons à la variable punition. Ainsi, Hercule rendra sa punition à temps. Un mathématicien de renom arrive enfin à vous voir. Il n’a pas de calculette – cela n’existe pas encore – et il a besoin de réaliser un calcul de la plus haute importance ! Il a en effet une liste contenant des nombres. Le premier nombre est un solde initial et chaque autre nombre doit lui être soustrait. Le but est d’obtenir l’opération (((A-B)-C)-D) à partir de la liste [A, B, C, D]. Pour cela, nous allons utiliser une autre fonction pratique de Python.
total = reduce(lambda x, y: x-y, nombres)
Vous ne devriez pas avoir besoin d’explications pour comprendre cette ligne mis à part que la fonction reduce réalise exactement ce que nous souhaitons. Elle applique la fonction donnée en paramètre aux éléments de la liste deux à deux. Ainsi, il serait possible d’écrire les lignes suivantes pour obtenir l’équivalent :
def reduceMyList(myfonction, mylist):
    result = []
    if len(mylist) == 1:
        return mylist[0]
    elif len(mylist) > 1:
        result.append(myfonction(mylist[0], mylist[1]))
        if len(mylist) > 2:
            result.extend(mylist[2:])
        return reduceMyList(myfonction, result)
    raise Exception, “List is empty”
Ici, nous créons notre propre fonction reduce. Celle-ci est récursive. Elle retourne le résultat uniquement si le nombre d’élément de la liste est 1. Si la liste est vide, elle envoie une exception pour signaler l’erreur. La fonction len permet donc de connaître la longueur d’une entité. En plus de cela, nous appliquons ici deux méthodes différentes sur la liste result. append ajoute un élément dans la liste tandis que extend ajoute tous les éléments d’une autre liste dans la liste. Mais la mission de la Pythonisse ne s’arrête pas là. L’organisateur principal des Jeux Pythiques croule sous le travail. Les jeux commencent dans trois jours et rien n’est prêt. Les Jeux Pythiques étaient les plus importants après les Jeux Olympiques. Chant, art et sport se mêlaient en permanence durant six à huit jours de fête. Cet événement particulier nécessite un minimum d’organisation. Les athlètes ne sont toujours pas enregistrés auprès du service concerné. Il faut donc créer les dossiers manquants avec les bonnes informations selon la discipline concernée. Pour cela, nous utiliserons un pattern un peu particulier : le multiton. Chaque dossier est un objet. Pour le créer, il faut que l’athlète n’ait pas participé à une compétition précédente – il serait alors enregistré deux fois. Ainsi, le multiton s’instancie si la clé (le nom de l’athlète) n’est pas déjà présente. Par contre, si une instance du multiton existe avec la clé donnée, il retourne l’instance existante. Pour ce faire, nous emploierons une métaclasse. Cette dernière est en fait une classe qui instancie non pas un objet classique, mais une classe. Souvenez-vous du début de l’article : une classe est un objet ! Enfin, il va nous falloir générer les attributs en fonction de la discipline exercée. Pour cela, nous utiliserons un simple dictionnaire contenant les noms des attributs et leur valeur par défaut en fonction de la discipline.
#!/usr/bin/env python
# -*- coding: iso-8859-15 -*-

class Multiton(type):
    __instances__ = {}
    def __call__(cls, nom, discipline):
        if Multiton.__instances__.has_key(nom):
            obj = Multiton.__instances__[nom]
        else:
            obj = object.__new__(cls)
            obj.__init__(nom, discipline)
            Multiton.__instances__[nom] = obj
        if hasattr(obj, "_setattributs"):
            obj._setattributs(nom, discipline)
        return Multiton.__instances__[nom]

class Athlete(object):
    __metaclass__ = Multiton
    def __init__(self, nom, discipline):
        pass
    def _setattributs(self, nom, discipline):
        attributs = {
        “lanceur javelot” : [(“distance”, 200),
                (“javelot”, “petit”)],
        “lanceur poids”  : [(“poids”, “2 kg”)],
        “lutteur” : [(“categorie”, “léger")]
        }
        if attributs.has_key(discipline):
            for attribut in attributs[discipline]:
                setattr(self, attribut[0], attribut[1])

a = Athlete(“Hercules”,”lutteur”)
print “Athlète Hercules, lutteur"
print "objet : %s" % a
print "type : %s " % type(a)
print "type du type : %s" % type(type(a))
print "propriétés - catégorie %s" % a.categorie

print "---------"

b = Athlete("Ulysse", "lanceur javelot")
print "Athlète Ulysse, lanceur javelot"
print "objet : %s" % b
print "type : %s " % type(b)
print "type du type : %s" % type(type(b))
print "propriétés - distance %s - javelot %s"
        % (b.distance, b.javelot)

print "---------"

c = Athlete("Hercules", "lanceur poids")
print "Athlète Hercules, lanceur poids"
print "objet : %s" % c
print "type : %s " % type(c)
print "type du type : %s" % type(type(c))
print "propriétés - catégorie %s - poids %s"
        % (c.categorie, c.poids)
La sortie de ce programme ressemble à ceci. 

Athlète Hercules, lutteur
objet : <__main__.Athlete object at 0xa7d8734c>
type : 
type du type : 
propriétés - catégorie léger
---------
Athlète Ulysse, lanceur javelot
objet : <__main__.Athlete object at 0xa7d874ac>
type : 
type du type : 
propriétés - distance 200 - javelot petit
---------
Athlète Hercules, lanceur poids
objet : <__main__.Athlete object at 0xa7d8734c>
type : 
type du type : 
propriétés - catégorie léger - poids 2 kg
Trois fonctions sont importantes en Python : setattr, getattr et hasattr. La syntaxe reste relativement simple.
setattr(entite, attribut, valeur)
valeur = getattr(entite, attribut)
condition = hasattr(entite, attribut)
La première fonction définit l’attribut s’il n’existe pas et lui affecte la valeur. La deuxième récupère la valeur d’un attribut et la dernière indique si l’attribut existe. Ce qui est appelé attribut dans la syntaxe correspond au nom de l’attribut sous forme de chaîne de caractères. Les points sont acceptés, mais ne permettent pas d’accéder directement à l’attribut en question autrement que par getattr. En effet, le point représente l’appartenance et ne fait donc pas partie du nom. Les fonctions données ici ne fonctionnent pas de cette manière. Elles intègrent le point au nom. Pour être plus clair, la fonction getattr(maclasse, "mon.attribut") donne la valeur de l’attribut "mon.attribut" de maclasse. Par contre, l’instruction maclasse.mon.attribut donnera l’attribut attribut de mon de maclasse. En Python, toutes les métaclasses héritent de types. C’est cette dernière qui permet de générer non pas un objet, mais une nouvelle classe lors de l’instanciation. De plus, l’attribut __metaclass__ est utilisé dans la classe implémentant la métaclasse. Il permet de générer l’objet non pas avec type, mais avec la classe définie plus haut. Cela permet donc de personnaliser au maximum l’instanciation. Dans ce code, lors de l’instanciation d’un athlète, la métaclasse Multiton est appelée. Elle génère une nouvelle classe Multiton et l’instancie. La classe Athlete génère également ses attributs grâce à une simple boucle et en utilisant la fonction setattr. Il est à noter que les attributs affectés à la classe Multiton ne sont pas disponibles dans une instance d’Athlète ! En effet, la classe Athlète est un objet de type Multiton. La classe est donc instance de Multiton. Par contre, l’instance d’Athlète n’a pas de lien direct avec le Multiton, il s’agit juste de l’instance de l’instance. Mais que se passe-t-il exactement ? Lorsque l’on regarde la sortie du programme, il est nettement visible que les athlètes a et c ne sont qu’un. Ils ont en effet la même adresse. De même, si le type des athlètes est la classe Athlete, le type de cette dernière classe est bien le Multiton. En fait, l’attribut __metaclass__ indique que la classe en question se construit à partir de la classe indiquée. Cette dernière instancie des classes puisqu’elle hérite de types. Ainsi, lorsque vous lancez la commande a = Athlete("Hercules","lutteur"), Python va vouloir instancier la classe Athlète. Il repère alors la fonction __call__ qui vient directement de la métaclasse. Il utilisera cette fonction qui instanciera un nouvel objet si aucun athlète de ce nom n’existe. Dans le cas contraire, il récupèrera l’instance correspondante. La fonction _setattributs est également lancée pour générer les attributs de l’instance d’athlète. Nous n’aurions pas pu mettre cela dans l’__init__. En effet, cette fonction est utilisée uniquement à l’instanciation. Il est bien plus propre d’utiliser une fonction à part. Ce genre d’astuces est tordu, mais peut s’avérer fortement efficace. Ainsi s’arrête cet article un peu mystérieux et obscur. La pythonisse vient de finir son travail aujourd’hui. Comme toujours, elle s’est évanouie. Après trois jours de jeûne et les vapeurs toxiques du dieu Apollon, il est tout à fait normal de ne pas se sentir bien. Nous l’abandonnons ici, gisant dans son lit. Les trucs et astuces qu’elle vous a livrés ici sont, bien sûr, à utiliser avec parcimonie. Les métaclasses, par exemple, peuvent rendre la lecture d’un code difficile, si elles ne sont pas justifiées.

Retrouvez cet article dans : Linux Magazine 84

Il y a : 0 commentaire(s)

Donnez votre avis

Vous devez avoir ouvert une session pour écrire un commentaire.

Brèves
Édito : Linux Pratique Essentiel N°24
Édito : Linux Pratique HS N°23
Édito : GNU/Linux Magazine 146
Édito : GNU/Linux Magazine HS N°58
Édito : Open Silicium N°5
Communication
Linux Pratique HS 23 – Communiqué de presse
Linux Pratique Essentiel N°24 – Communiqué de presse
Gnu/Linux Magazine sponsor et partenaire de PROLOGIN
Linux Essentiel partenaire des Rencontres du Libre de Lion sur Mer (Normandie)
GNU/Linux Magazine HS 58 – Communiqué de presse
prochainement moteur de recherches des articles
 
:
:
Jours heures minutes secondes
En kiosque
Le tout nouveau Linux Pratique Essentiel est disponible dès maintenant chez votre marchand de journaux et sur notre site...

Lire la suite...

Le tout nouveau Linux Pratique est disponible dès maintenant chez votre marchand de journaux et sur notre site...

Lire la suite...

Le tout nouveau GNU/Linux Magazine est disponible dès maintenant chez votre marchand de journaux et sur notre site...

Lire la suite...

Le tout nouveau GNU/Linux Magazine HS est disponible dès maintenant chez votre marchand de journaux et sur notre site...

Lire la suite...

Le tout nouveau Open Silicium est disponible dès maintenant chez votre marchand de journaux et sur notre site...

Lire la suite...

Le tout nouveau Linux Pratique est disponible dès maintenant chez votre marchand de journaux et sur notre site...

Lire la suite...

Le tout nouveau Misc est disponible dès maintenant chez votre marchand de journaux et sur notre site...

Lire la suite...

Le tout nouveau GNU/Linux Magazine est disponible dès maintenant chez votre marchand de journaux et sur notre site...

Lire la suite...