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 e
t _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
Donnez votre avis
Vous devez avoir ouvert une session pour écrire un commentaire.