PROJET AUTOBLOG


Sam et Max

source: Sam et Max

⇐ retour index

Quelques bonnes raisons de plus d’utiliser iPython

dimanche 23 décembre 2012 à 19:01

Le shell Python est vraiment pratique pour expérimenter, apprendre le langage, tester un snippet vitos ou administrer son site à distance. Mais iPython, ola, iPython, il déchire sa génitrice avec une poutrelle en verre pilé.

Voici quelques commandes qui vous donneront moult raisons de plus pour très vite installer ce shell alternatif.

Autocall

Taper des parenthèses et des guillemets, c’est sooooooo 1995. iPython peut vous les rajouter automatiquement dans les appels de fonctions, il faut juste préfixer de “/”, “,” ou “;” votre appel de fonction pour qu’il fasse la conversion:

/f 1,2 => f(1,2)
,f 1 2 => f("1","2")
;f 1 2 => f("1 2")

Accès au shell système

Préfixez votre commande d’un “!” bien couillu, et iPython va vous exécuter ça dans le shell système (par exemple bash) et vous retourner le résulat:

>>> ! cat /etc/fstab | grep 'ext4'
UUID=e33c5b98-1570-44d6-a32f-5e7970e1e588 /               ext4    errors=remount-ro 0       1

Oui, oui, on est dans un shell Python là. Ou comment bien faire chier la coloration syntaxique de votre blog.

Et on peut mettre tout ça dans une variable et le traiter en Python derrière:

>>> files = !ls
>>> [f.upper() for f in files]
['BUREAU', 'DOCUMENTS', 'EXAMPLES.DESKTOP', 'IMAGES', 'MOD\xc3\xa8LES', 'MUSIQUE', 'PUBLIC', 'T\xc3\xa9L\xc3\xa9CHARGEMENTS', 'UBUNTU ONE', 'VID\xc3\xa9OS',]

A noter que tout est exécuté dans un autre process, ainsi !cd n’aura aucun effet. Mais tout est prévu: %cd et %pwd proxient tout ça vers os.chdir et os.getcwd :-)

Debugger

Entrez %timeit expression pour qu’il qu’iPython vous l’exécute 10000000 fois et vous donne sa performance.

>>> %timeit x=2**100
10000000 loops, best of 3: 22.5 ns per loop

Autre commande: %pdb vous lancera automatiquement votre débuggeur favoris si une exception se lève durant votre session.

Se faciliter la vie

%edit ouvre un editeur (par défault VI sous nunux, mais c’est configurable), et si vous sauvegardez, le contenu tapé est récupéré et exécuté par iPython. Génial pour les bouts de code trop relou à taper sur le prompt.

%gui qt|wx|gtk lance l’intégration de la main loop d’un des toolkits graphiques afin pour de pouvoir faire mumuse avec des widgets sans bloquer votre shell.

Vous étiez vous demandé pourquoi le prompt iPython était plein de In et de Out, et pas de >>> ? Parce que tout l’historique est numéroté, et accessible:

In [38]: 1 + 1
Out[38]: 2
 
In [39]: In[38]
Out[39]: u'1 + 1'
 
In [40]: Out[38]
Out[40]: 2

Mais vous pouvez aussi utiliser %doctest_mode qui donne à votre prompt un air de shell Python normal. L’avantage ? On peut copier le contenu d’un autre shell dedans (et donc d’une doctest) avec les chevrons !

In [49]: %doctest_mode
Exception reporting mode: Plain
Doctest mode is: ON
>>> >>> for pom in ('pom', 'pom', 'pom', 'pom'):
...     ...     print pom
...     ... 
pom
pom
pom
pom

Quel hébergement Web pour les projets Python ?

samedi 22 décembre 2012 à 14:14

Un lecteur m’a demandé il y a quelques temps quel hébergement choisir pour son projet Django, en ces termes poétiques:

Chez moi, je teste un peu les frameworks web python django et pyramid. Seulement une question demeure à laquelle je ne trouve pas de réponse probante : OK j’ai fait mon super site en python OK il demoule sur mon pc perso Mais maintenant, quel hébergeur utiliser ?

D’abord, je vous invite à consulter les listes des hébergeurs gratuits et payants sur le wiki officiel de Python.

Parmi les hosts gratuits, j’ai essayé et approuvé PythonAnywhere qui est parfait pour un petit projet. Il gère très bien WSGI donc on peut facilement y plugger Django, Bottle, ou Flask. Et pas besoin de savoir comment configurer un serveur, il y a juste quelques réglages et roulez jeunesse.

De réputation, il y a aussi AlwaysData en hosting gratuit. Certes je ne l’ai pas essayé, mais il est sponsor de pas mal d’événements Python et rien que pour ça, ça me donne envie de le soutenir. Par contre avec 10 Mo de disque, vous avez intérêt à pas viser trop gros.

Dans la liste des hébergeurs gratuits, il n’est pas fait mention de Google App Engine. Pourtant l’offre de base est bien gratuite, même si si ça devient payant assez vite. Il faut aussi savoir que ce n’est pas une installation Python standard, et qu’il n’y a pas de base de données autre que l’API Big Data de Google. Bref, c’est assez particulier.

Néanmoins, une fois que votre projet dépote, un hébergeur gratuit ne suffit plus. Dans un premier temps, vous pouvez vous lancer dans un hébergeur pas cher, mais sur un serveur dédié. Ca demande un peu de sysadmin, mais vous êtes plus libres.

Online.net et sa fameuse offre Dedibox est probablement un des meilleurs rapport qualité / prix puisque vous aurez un serveur dédié pour 9 euros / mois. C’est quand même pas la mer à boire de débourser 10 balles, et on est tranquille. Ce n’est pas un hasard, c’est une filiale d’Iliad qui n’est autre que la maison mère de Free. C’est une super opportunité pour se faire la main, car on s’en branle un peu de tuer un serveur à 10 euros, donc on peut s’amuser avec, apprendre à config redis, optimiser PosGres, faire un proxy VPN ou un spam bot via Tor. Très chouette.

Alternativement, vous pouvez faire confiance au français Gandi. C’est un poil plus cher, mais leur service est excellent, et c’est une boîte qui a une très belle éthique. Vous aurez alors un serveur dédié, mais virtualisé, donc vous pourrez augmenter la puissance a posteriori alors qu’avec la dédiboule, vous êtes bon pour migrer tout le serveur.

Après, il faut carrément taper dans les services près des back bones si on veut monter dans les tours. En ce moment, on aime pas mal Leaseweb. Là on aligne deux zéros mensuels par serveurs sur la facture, mais c’est déjà des jolis bébés.

Pour finir, vous voudrez peut-être passer en mode Cloud, et dans ce cas Rackspace et WebFaction ont une très bonne réputation, particulièrement en terme de support. Leur facture est par contre salée, mais le nuage, c’est toujours cher au kilo.

Si vous êtes du genre à aimer les trucs fashions et bien intégré, Heroku et Gondor ont des solutions spécialisées autour de Python. Tout ce qui est déploiement se fait apparemment les doigts dans le pif si vous êtes du genre à pas aimer le sysadmin, avec intégration du versioning et des migrations. Bref, le truc hype de la vibes du flex. Enfin c’est ce qu’on dit, car on a pas testé. On aime avoir le contrôle sur nos machines.

Enfin, mention spéciale à Cinfu qui propose des serveurs payables en Bitcoin. Toujours utile pour créer un site de phishing. Ils interdisent les nœuds Tor par contre.

Heapq, le module Python incompris

vendredi 21 décembre 2012 à 18:23

Guido Van Rossum, notre-Dieu-à-tous-beni-soit-il, a un jour accepté de faire une session de questions-réponses publique, dans laquelle un petit malin lui a demandé “Comment ordonner un million d’entiers 32 bits dans 2Mo de Ram avec Python“.

Ca aurait été sur ce blog, le mec se serait pris un tampon “drozophiliafucker” dans la gueule et ça aurait été plié. Mais quand on est BDFL et, à l’époque, employé chez Google, on est obligé de donner une réponse un peu sérieuse.

Si vous avez suivi le lien précédent, vous verrez que sa réponse implique un obscure module appelé heapq. Si vous allez sur la doc du-dit module, vous verrez qu’elle implique une obscure explication innomable et vous aller retourner à la vidéo de porn que vous avez laissé bufferiser en haute résolution afin de pouvoir voir les grains de beauté qui longent le pourtour anal de la principale protagoniste. Respirons.

Il y a une explication rationnelle à tout cela

heapq est un algorithme qui organise une liste sous forme d’arbre binaire. Vous voyez c’était simple non ?

Nan je déconne, je vais pas vous laisser avec ça quand même.

Le module heapq permet d’utiliser le type “list” pour y ajouter ou retirer les éléments de telle sorte qu’ils soient toujours dans l’ordre.

Sous le capot, ça marche effectivement avec un arbre binaire, mais on s’en branle. Tout ce qu’il faut comprendre c’est:

>>> from heapq import heappush
>>> l = []
>>> heappush(l, 69)
>>> heappush(l, 42)
>>> heappush(l, 2048)
>>> heappush(l, -273.15)
>>> l # la liste est ordonnée en arbre binaire...
[-273.15, 42, 2048, 69]
>>> for x in xrange(len(l)): # et on depop pour itérer dans l'odre
    print heappop(l)
...     
-273.15
42
69
2048

Donc on a une liste, on lui met des éléments dans la tronche dans n’importe quel ordre, et bam, on peut itérer dans le bon ordre sans avoir à rien faire. Et cette insertion est assez rapide (O(lg n) pour les tatillons). Le parcours l’est également (de l’ordre de O(n log n)).

Et donc c’est très pratique pour trouver les x plus petits éléments (ou les plus grands), implémenter des queues de priorité, etc.

Exemple de queue de priorité, (courageusement volé d’ici):

import heapq
 
class PriorityQueue(list):
 
    def __init__(self, data):
        super(Heap, self).__init__()
        for i, x in enumerate(data):
            self.push(i, x)
 
    def push(self, priority, item):
        """
            On push en rajoute une priorité
        """
        heapq.heappush(self, (priority, item))
 
    def pop(self):
        """
            On pop en retirant la proprité
        """
        return heapq.heappop(self)[1]
 
    def __len__(self):
        return len(self)
 
    def __iter__(self):
        """
            Comme on a une méthode next(), on peut se retourner soi-même.
            Ainsi la boucle for appelera next() automatiquement. 
        """
        return self
 
    def next(self):
        """ 
           On depop la liste du plus petit au plus grand.
        """
        try:
            return self.pop()
        except IndexError:
            raise StopIteration
 
>>> l = PriorityQueue(("azerty"))
>>> l
>>> l.push(100, 'après')
>>> l.push(-1, 'avant')
>>> l.push(5, 'pendant')
>>> for x in l:
...     print x
...     
avant
a
z
e
r
t
pendant
y
après

Et le plus beau, c’est qu’on peut prendre plusieurs itérables ordonnés, et utiliser heapq.merge pour obtenir un générateur (qui ne charge pas tout en mémoire d’un coup) qui va permettre d’iterer de manière ordonnée sur tous les éléments.

>>> import heapq
>>> l1 = sorted([random.randint(0, 1000) for x in xrange(5)])
>>> l2 = sorted([random.randint(0, 1000) for x in xrange(5)])
>>> l3 = sorted([random.randint(0, 1000) for x in xrange(5)])
>>> list(heapq.merge(l1, l2, l3))
[52, 59, 60, 171, 174, 262, 336, 402, 435, 487, 557, 645, 899, 949, 996]

Notez que ce n’est pas du tout la même chose que de concaténer les listes:

>>> l1 + l2 + l3
[59, 174, 336, 487, 996, 52, 171, 557, 645, 949, 60, 262, 402, 435, 899]

Car des élements de l2 peuvent être inférieurs à ceux de l1. heap.merge nous ordonne tout bien correctement. C’est l’équivalent de sorted(l1 + l2 + l3), sauf que ça ne charge pas tout en mémoire:

>>> heapq.merge(l1, l2, l3)
<generator object merge at 0x0314F238>

Alors que sorted(), charge tout en mémoire:

>>> sorted(l1 + l2 + l3)
[52, 59, 60, 171, 174, 262, 336, 402, 435, 487, 557, 645, 899, 949, 996]

Bien entendu, ça marche avec des listes créées avec
heapq, ainsi:

>>> l1 = []

>>> for x in xrange(5): heappush(l1, random.randint(0, 1000))

>>> l2 = []

>>> for x in xrange(5): heappush(l2, random.randint(0, 1000))

>>> l3 = []

>>> for x in xrange(5): heappush(l3, random.randint(0, 1000))

>>> list(heapq.merge(l1, l2, l3))

[31, 40, 133, 360, 504, 508, 513, 679, 645, 792, 838, 413, 765, 886, 924]

(Grosse connasserie de ma part, faites comme si vous aviez rien vu.)

Quand on a de grosses quantités de données à trier, c’est très pratique, car l’effort de tri est répartie à chaque insertion, et à chaque itération pendant le parcours, pas concentré sur de gros appels de sorted().

On peut aussi récupérer des trucs du genre, les n plus petits / grands éléments sans tout coder à la main:

>>> l = [random.randint(0, 1000) for x in xrange(100)]
>>> heapq.nsmallest(4, l)
[0, 2, 4, 7]
>>> heapq.nlargest(3, l)
[999, 996, 983]

Et c’est beaucoup plus efficace que de le faire soi-même.

J’en profite pour rappeler au passage que tous les objets en Python sont ordonnables:

>>> (1, 2) > (2, 1)
False
>>> (1, 2) < (2, 1)
True
>>> "a" > "b"
False
>>> "a" < "b"
True

Et qu’en définissant les méthodes __eq__, __lt__, __gt__, etc., on peut donner un moyen de comparer n’importe quelle classe.

Bref, pour tous les besoins de classement, de priorisations, d’ordonnancement, de notion de plus petits, de plus grands, etc. qui concernent un gros jeu de données, heapq est le module qu’il vous faut.

Et là je percute que ça fait quelque temps déjà que je fais des articles élitistes pour des uses cases de 1% de la population. Donc les prochains tutos concerneront des trucs plus terre à terre. Parce que, je fais le malin là, mais je savais pas à quoi servait heapq il y a 3 semaines.

xoxo les filles

Implémenter une fenêtre glissante en Python avec un deque

jeudi 20 décembre 2012 à 20:02

Quand j’entends deque, je pense plus à un deck de Magic de Gathering qu’à un module Python, mais aujourd’hui j’utilise beaucoup plus le dernier que le précédent.

On a déjà vu comment implémenter l’itération par morceaux sur un itérable de n’importe quelle taille. Grâce au deque, on peut aussi facilement créer une fenêtre glissante.

Qu’est-ce qu’une fenêtre glissante ?

Il s’agit de parcourir un itérable par tranches de “n” élements, en faisant en sorte que l’insertion d’un nouvel élément dans la tranche implique la supression du premier entré. Mouai, dit comme ça c’est pas clair. Un p’tit exemple ?

Fenêtre glissante de 3 éléments sur une liste de 10 éléments, à la mano:

>>> l = range(10)
>>> l
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> l[:3]
[0, 1, 2]
>>> l[1:4]
[1, 2, 3]
>>> l[2:5]
[2, 3, 4]
>>> l[3:6]
[3, 4, 5]
...

A ne pas confondre avec le parcours par morceaux, qu’on déjà traité, et qui donne ça:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> l[0:3]
[0, 1, 2]
>>> l[3:6]
[3, 4, 5]
>>> l[6:9]
[6, 7, 8]
...

Et bien sûr, comme d’hab, vous allez dire “Cher Sam, malgré ton génie qui n’a d’égal que ta beauté et l’incohérence de cette remarque, je ne vois pas vraiment à quoi ça sert.”

Et bien essentiellement dans les situations où un élément est dépendant des précédents, par exemple pour répondre à des questions existentielles comme “quel est le premier élément qui est la somme des 5 le précédant dans cette liste ?”. Ça n’arrive certes pas tous les jours, mais fuck, j’avais pas de meilleur exemple en tête, là, tout de suite.

Qu’est-ce qu’un deque ?

Un deque est une structure de données qui permet d’implémenter des comportements de types piles, files, FIFO, LIFO, et autres pipos avec efficacité. Traduit en langage de tous les jours, c’est comme une liste, mais:

On va donc l’utiliser comme acumulateur ou pour faire des files d’attentes.

Démonstration:

>>> from collections import deque
>>> ze_deck = deque(maxlen=3) # on limite le deque à 3 éléments
>>> ze_deck.append(1)
>>> ze_deck
deque([1], maxlen=3)
>>> ze_deck.append(2)
>>> ze_deck
deque([1, 2], maxlen=3)
>>> ze_deck.append(3)
>>> ze_deck
deque([1, 2, 3], maxlen=3)
>>> ze_deck.append(4) # ... et MAGIQUEMENT !
>>> ze_deck
deque([2, 3, 4], maxlen=3)
>>> for x in ze_deck:
...     print x
...     
2
3
4

Bam le dernier élément est squizzé, et tout ça rapidement et efficacement. Vous voyez où je veux subtilement en venir ?

Et finalement: comment on utilise l’un pour faire l’autre ?

>>> from collections import deque
>>> from itertools import islice
>>> 
>>> def window(iterable, size=2):
...         iterable = iter(iterable)
...         d = deque(islice(iterable, size), size)
...         yield d
...         for x in iterable:
...                 d.append(x)
...                 yield d
...         
>>> for x in window('azertyuiop'):
...         print x
...     
deque(['a', 'z'], maxlen=2)
deque(['z', 'e'], maxlen=2)
deque(['e', 'r'], maxlen=2)
deque(['r', 't'], maxlen=2)
deque(['t', 'y'], maxlen=2)
deque(['y', 'u'], maxlen=2)
deque(['u', 'i'], maxlen=2)
deque(['i', 'o'], maxlen=2)
deque(['o', 'p'], maxlen=2)

Vous noterez au passage que nous faisons ici un usage malin des générateurs, ce qui nous permet de retourner un itérable sans tout charger en mémoire et de slider sur notre window avec classe.

Ainsi on peut se la peter en faisant des trucs compliqués en une ligne comme:

>>> {tuple(d): i for i, d in enumerate(window(xrange(10), 3))}
{(5, 6, 7): 5, (0, 1, 2): 0, (7, 8, 9): 7, (6, 7, 8): 6, (1, 2, 3): 1, (3, 4, 5): 3, (2, 3, 4): 2, (4, 5, 6): 4}

Ce qui n’a absolument aucun intérêt, mais si vous comprenez ce que ça fait, alors vous avec bien masterisé Python.

Faire des enums en python

mercredi 19 décembre 2012 à 18:41

Ceci est un post invité de Réchèr posté sous licence creative common 3.0 unported.

“Moi, je viens du C++”

Voilà un propos qui fait classe. Ça donne un air de vieux de la vieille, à qui on ne la fait pas, et qui connaît des langages que c’est pas des langages de taffioles. “De mon temps, fallait savoir ce qu’on faisait, sinon tout plantait. On ciselait notre code à la main dans des blocs de granit. Vous les gosses de maintenant, vous connaissez plus rien. Eh ! Sale jeune, au lieu d’importer des lib de feignasses, vient donc me changer ma poche à urine.

Ça aurait été encore plus classe de dire que je viens de l’Assembleur, mais je suis pas assez vieux pour ça. Et de toutes façons, en vrai, je viens du Pascal. Bref, passons.

Tout ça pour dire que dans le C++, il existe quelque chose que j’avais trouvé bien sympa : les enums.

Comment ça marche ?

Un enum est un type de variables ayant un domaine de valeur fixe et prédéfini, que l’on spécifie lors de la déclaration du type.

typedef enum
{
     Pique,
     Coeur,
     Trefle,
     Carreau
} ECouleursCartes;

Ensuite, on peut déclarer une variable de ce type, et lui affecter l’une des valeurs autorisées.

ECouleursCartes vMaCouleurCarte;
vMaCouleurCarte = Trefle;

Concrètement, chaque valeur correspond à un entier. Le compilateur fait les correspondances tout seul comme un grand.

À quoi ça sert ?

À avoir du code plus parlant quand on veut représenter des notions concrètes de la vraie vie, ou quand on implémente une machine à état.

Par exemple, vous créez une classe FeuCirculation. Vous avez besoin d’une variable renseignant sa couleur. Vous pouvez utiliser un int à l’arrache, et noter en commentaire quelque part : “0 c’est rouge, 1 c’est vert, 2 c’est orange.”. Mais c’est chiant. Il faut toujours garder en tête les correspondances. On risque d’attribuer des valeurs non documentées. Bref, y’a n’importe quoi dedans et ça pue, (un peu comme une poche à urine).

Avec un enum, tout est plus clair, aussi bien dans le code définissant la classe FeuCirculation, que dans le code qui l’utilise. Et c’est plus robuste.

L’exemple qui est donné très souvent dans les tutoriaux, c’est un enum Animal, contenant les valeurs chien, chat, bœuf musqué, ... Ça fonctionne, mais personnellement cet exemple me gêne un peu. Chaque animal est un objet à part entière. Dans ce cas, il vaudrait mieux créer une classe générique Animal, et la faire hériter. Ce qui permettra ensuite de créer des méthodes telles que manger(), crier(), péter(), ...

If it quacks like a duck, it may have noticed you are fucking it.

Pour décrire plusieurs objets, utilisez des classes. Pour décrire les différents états d’un même objet, utilisez un enum. Pour décrire plusieurs objets simples, utilisez l’un ou l’autre, c’est à vous de choisir.

Comment faire un enum en python ?

Il n’y a, à ma connaissance, pas de structure syntaxique dédiée. Mais c’est pas grave, on va bien trouver quelque chose.

Le plus simple, pour commencer, c’est de garder l’idée de la correspondance avec des entiers. Ça offre plein d’avantages :

La première idée qui vient à l’esprit, c’est de créer une classe avec les valeurs dedans, et de ne plus jamais les changer par la suite.

# On l'appelle FeuCirculation et non pas FeuRouge. 
# Parce qu'un feu n'est pas forcément rouge.
# (Putain de langue française à la con qui fait n'importe quoi).
class FeuCirculation:
    ROUGE = 0
    ORANGE = 1
    VERT = 2
    ORANGE_CLIGNOTANT = 3
    TOUT_ETEINT = 4
 
feuActuel = FeuCirculation.ORANGE

C’est bien, mais chiant à maintenir. Si on veut ajouter un autre état (par exemple, FLECHE_DROITE_ORANGE_CLIGNOTANTE), et qu’on se plante dans les valeurs numériques, il y a un risque d’avoir des doublons, et ça fiche tout en l’air.

Voici un petit peu mieux :

class FeuCirculation:
    (ROUGE,
     ORANGE,
     VERT,
     ORANGE_CLIGNOTANT,
     TOUT_ETEINT,
    ) = range(5)

range(5) renvoie un tuple (0, 1, 2, 3, 4). Des entiers différents sont attribués aux valeurs de l’enum. Si on veut en rajouter une et qu’on oublie de remplacer range(5) par range(6), ça va balancer une exception. Tout va bien.

Au cas où vous demanderiez : “qu’est-ce qui m’empêche de redéfinir l’une des valeurs, à un autre endroit du code, et de foutre le bordel ?”, je répondrais “We are all consenting adults here”. (Se référer à la philosophie du python. Si besoin, ce sera l’occasion d’un autre article).

Some consenting adults. (Je peux venir ?)

Le code est beau, mais pour débugger ?

C’est un peu lourdingue. Lorsqu’on fait print feuActuel (ou lorsqu’on tape directement feuActuel dans une console pdb), c’est une valeur numérique pas claire qui va s’afficher, au lieu d’un joli “ORANGE”, “ROUGE”, …

Un petit dictionnaire de correspondance “valeur numérique” -> “nom à afficher” serait de bon aloi. Quelque chose de ce genre, à placer dans la définiton de la classe FeuCirculation :

    dictReverse = {
        ROUGE : "ROUGE",
        ORANGE : "ORANGE",
        VERT : "VERT",
        ORANGE_CLIGNOTANT : "ORANGE_CLIGNOTANT",
        TOUT_ETEINT : "TOUT_ETEINT",
    }

Fort bien. Mais n’est-ce pas un peu casse-couille ? Eh si. Des répétitions à la pelle, et dictReverse doit rester synchro avec les définitions des valeurs. C’est redevenu chiant à maintenir.

Hey, je suis Dict-Reverse, et je suis chiant à maintenir. bi-boppe eu loulaaaa.

Définition dynamique de nouveaux types

Soyons franc, j’ai piqué cette astuce ici :
http://stackoverflow.com/questions/36932/whats-the-best-way-to-implement-an-enum-in-python

Vous connaissez certainement déjà la fonction native type(). Elle renvoie le type du truc passé en paramètre. Youpi.

Mais saviez-vous qu’elle permet également de créer de nouveaux types ? Pour ce faire, il faut l’appeler avec 3 paramètres :

La création de l’enum FeuCirculation pourrait donc se faire de cette manière :

FeuCirculation = type(
    "FeuCirculation",
    (),
    {
        "ROUGE" : 0, 
        "ORANGE" : 1, 
        "VERT" : 2, 
        "ORANGE_CLIGNOTANT" : 3, 
        "TOUT_ETEINT" : 4
    })
 
>>> FeuCirculation
<class '__main__.FeuCirculation'>
>>> FeuCirculation.VERT
2

On peut également ajouter le dictReverse dans le dictionnaire des attributs. (Ça fait un dictionnaire dans un dictionnaire, c’est lol).

FeuCirculation = type(
    "FeuCirculation",
    (),
    {
        "ROUGE" : 0,
        "ORANGE" : 1,
        "VERT" : 2,
        "ORANGE_CLIGNOTANT" : 3,
        "TOUT_ETEINT" : 4,
        "dictReverse" : {
            0 : "ROUGE",
            1 : "ORANGE",
            2 : "VERT",
            3 : "ORANGE_CLIGNOTANT",
            4 : "TOUT_ETEINT"}
    })
 
>>> FeuCirculation
<class '__main__.FeuCirculation'>
>>> feuxActuel = FeuCirculation.VERT
>>> feuxActuel
2
>>> FeuCirculation.dictReverse[feuxActuel]
'VERT'

Et maintenant y’a plus qu’à coder une petite fonction qui fait tout ça génériquement.

def enum(enumName, *listValueNames):
    # Une suite d'entiers, on en crée autant
    # qu'il y a de valeurs dans l'enum.
    listValueNumbers = range(len(listValueNames))
    # création du dictionaire des attributs.
    # Remplissage initial avec les correspondances : valeur d'enum -> entier
    dictAttrib = dict( zip(listValueNames, listValueNumbers) )
    # création du dictionnaire inverse. entier -> valeur d'enum
    dictReverse = dict( zip(listValueNumbers, listValueNames) )
    # ajout du dictionnaire inverse dans les attributs
    dictAttrib["dictReverse"] = dictReverse
    # création et renvoyage du type
    mainType = type(enumName, (), dictAttrib)
    return mainType
 
>>> FeuCirculation = enum(
        "FeuCirculation",
        "ROUGE", "ORANGE", "VERT",
        "ORANGE_CLIGNOTANT", "TOUT_ETEINT")
>>> FeuCirculation.TOUT_ETEINT
4
>>> FeuCirculation.dictReverse[FeuCirculation.TOUT_ETEINT]
'TOUT_ETEINT'

Une petite précision : d’habitude, quand on crée des classes ou des types, c’est pour les instancier. Là on ne le fait pas, on se contente d’utiliser des valeurs statiques contenues dans le type. Vous pourriez faire feuDuCarrefour = FeuCirculation(). Ça va fonctionner, mais ça ne vous servira à rien.

Le code de la route a parfois besoin de refactoring

Mais si on mélange les enums ?

>>> FeuCirculation = enum(
        "FeuCirculation",
        "ROUGE", "ORANGE", "VERT",
        "ORANGE_CLIGNOTANT", "TOUT_ETEINT")
>>> etatMatiere = enum(
        "etatMatiere", 
        "SOLIDE", "LIQUIDE", "GAZEUX")
>>> etatMatiere.dictReverse[FeuCirculation.VERT]
'GAZEUX'

Mince alors. Ça fait n’importe quoi, sans signaler aucune erreur. C’est normal. D’un enum à l’autre, on ne fait que manipuler des entiers, qui restent les mêmes. Les correspondances se font joyeusement, même si ça n’a aucun sens. Dans le même ordre d’idée : FeuCirculation.ROUGE == etatMatiere.SOLIDE renverra True, ce qui ne veut rien dire. Comment régler ce problème ?

Better have traffic ice than traffic jam.

Solution 1 : ne pas régler le problème

“We are all consenting adults here”, on ne va donc pas compliquer le code en rajoutant des sécurités de partout, sous prétexte d’empêcher des erreurs. Le seule moyen valable de se prémunir de faire n’importe quoi quand on code, c’est tout bêtement de rester concentré et de faire gaffe à ce qu’on fait.

Honnêtement, ça ne m’est jamais arrivé de mélanger des enums entre eux. Pourtant, je n’ai pas la prétention de tout faire juste et sans bugs du premier coup. Parmi mes bourdes préférées : copier-coller la ligne du dessus et ne pas faire les bons remplacements (les “x” par des “y”, les “1″ par des “2″, etc.), ou encore : déclarer une fonction, lui faire renvoyer un résultat bidon parce que je préfère la coder plus tard, et oublier que je ne l’ai pas codé.

def calculDist(point1, point2):
    distX = point1.x - point1.x
    distY = point2.x - point2.x
    return math.sqrt(distX * distX + distY * distY)
    # ami lecteur, sauras-tu trouver tous les fails
    # présents dans cette fonction ?
 
def RemoveDoubleLetters(strValue):
    # TODO : coder le truc. Mais là, j'ai la flemme.
    return strValue

BSOD !!

Pour limiter les risques de mélange, voici quelques conseils :

Vieng fayre toi-même leu mélainge des enums, sur les muuuurs deu la cabaneu du côdeur. Vieng t'assouar.

Solution 2 : des enums fortement typés.

Au lieu que les valeurs internes des enums soient de simples entiers, on crée un type pour chacune d’elle. Comme ça, on est sûr que ça renverra une erreur si on se confusionne. Par contre, on perd en simplicité, et ce n’est plus aussi simple pour la sérialisation.

Soyons fous, et voyons ce que ça donne !

def strongTypedEnum(enumName, *listValueNames):
    # création d'une liste de type. sans attribut, sans héritage.
    # le nom du type est composé du nom de l'enum et du nom de la valeur,
    # séparés par un point.
    listValueTyped = [ type(".".join((enumName, nameValue)), (), {})
                       for nameValue in listValueNames ]
    # Ensuite, c'est tout pareil que la fonction d'avant.
    dictAttrib = dict( zip(listValueNames, listValueTyped) )
    dictReverse = dict( zip(listValueTyped, listValueNames) )
    dictAttrib["dictReverse"] = dictReverse
    mainType = type(enumName, (), dictAttrib)
    return mainType
 
>>> FeuCirculation = strongTypedEnum("FeuCirculation",
        "ROUGE", "ORANGE", "VERT",
        "ORANGE_CLIGNOTANT", "TOUT_ETEINT")
>>> etatMatiere = strongTypedEnum(
        "etatMatiere", 
        "SOLIDE", "LIQUIDE", "GAZEUX")
>>> etatMatiere.LIQUIDE
<class '__main__.etatMatiere.LIQUIDE'>
>>> etatMatiere.dictReverse[FeuCirculation.ROUGE]
Traceback (most recent call last):
  File "<pyshell#20>", line 1, in <module>
    etatMatiere.dictReverse[FeuCirculation.ROUGE]
KeyError: <class '__main__.FeuCirculation.ROUGE'>

Mais est-ce bien nécessaire ?

Y’a une lib pour ça

Comme toujours, avec cette foutue prolifération de code libre, dès que quelque chose de cool peut potentiellement exister, un connard de geek l’a déjà fait, vous privant de l’occasion de le faire vous-même et de retirer la gloire qui en décombe. La lib flufl.enum permet donc de créer et manipuler des enums, avec autant et même plus de fonctionnalités que ce que je viens de décrire ici. (Connards de geeks qui viennent manger le pain des français).

Par contre, j’ai regardé le code, et j’ai pas pigé comment ça fonctionnait à l’intérieur. Ça fera peut-être l’objet d’un autre article, si j’ai le courage de me plonger dedans, et que je trouve ça intéressant et rigolo.

De toutes façons, les libs c’est chiant, ça ajoute des dépendances, il vaut mieux tout coder à la main.

Disgression 1 : quelle classe ce type !

Déclarer un type avec la fonction type(), et déclarer une classe, ça ne fait pas exactement la même chose.

>>> class MaClasse:
	pass
 
>>> MaClasse
<class __main__.MaClasse at 0x011CB270>
>>> instanceClasse = MaClasse()
>>> instanceClasse
<__main__.MaClasse instance at 0x011C8BC0>
 
>>> MonType = type("Le_Nom_De_Mon_Type", (), {})
>>> MonType
<class '__main__.Le_Nom_De_Mon_Type'>
# Hey ! Le nom est dans une string, et y'a pas d'adresse mémoire !
 
>>> instanceType = MonType()
>>> instanceType
<__main__.Le_Nom_De_Mon_Type object at 0x011C1B30>
# Hey ! C'est un object, et pas une instance !

Ça a peut-être quelque chose à voir avec une histoire de old-style class et new-style class. J’ai pas cherché plus loin pour l’instant. Ça fera peut-être l’objet d’un autre-autre article, si j’ai le courage de me plonger dedans, et que je trouve ça intéressant et rigolo.

Disgression 2 : Restons dans les règles

J’avais trouvé un autre exemple de machine à état, plus rigolo que des feux de circulation ou de la matière. Je ne m’en suis pas servi durant mes explications, parce que ça aurait distrait le lecteur / la lectrice (charge cérébrale, tout ça…). Mais ce serait vraiment dommage de ne pas vous en faire profiter. Le voici donc :

>>> PhaseCycleUterin = enum(
        "PhaseCycleUterin",
        "PREPUBERE", "MENSTRUELLE", 
        "OVULATION", "PROLIPERATIVE", "SECRETRICE"
        "ENCEINTE", "MENOPAUSE")
 
>>> etatMatiere.dictReverse[PhaseCycleUterin.MENSTRUELLE]
'LIQUIDE'

Voilà, ce sera tout pour aujourd’hui. La prochaine fois, nous redéfinirons le type “object” en utilisant uniquement les picots du langage Braille.

Une femme qui a ses règles.