PROJET AUTOBLOG


Sam et Max

source: Sam et Max

⇐ retour index

Les listes en intention VS map() en Python

jeudi 4 octobre 2012 à 15:26

Les adeptes de la programmation fonctionnelle connaissent bien le principe de la fonction map() et sont souvent plus à l’aise avec elle qu’avec les listes en intention en Python.

Les deux font pourtant la même chose, tant et si bien que Python 3 voit map() retiré de ses built-in. Ouuuups. Dérapage un-con-trollé de Sam.

C’est plutôt une nouvelle chose car dans un language orienté objet comme Python, il y a une subtile différence entre map() et les comprehension lists. Quand on utilise une méthode plutôt qu’une fonction, map() tue le duck tuping:

>>> l = [u'      Le choix dans la date     ', '     Les nouilles cuisent au jus de canne     ']
>>> map(str.strip, l)
Traceback (most recent call last):
  File "<ipython-input-14-41df8a735feb>", line 1, in <module>
    map(str.strip, l)
TypeError: descriptor 'strip' requires a 'str' object but received a 'unicode'
 
>>> map(unicode.strip, l)
Traceback (most recent call last):
  File "<ipython-input-15-fc0fc8fef32d>", line 1, in <module>
    map(unicode.strip, l)
TypeError: descriptor 'strip' requires a 'unicode' object but received a 'str'
 
>>> [x.strip() for x in l]
[u'Le choix dans la date', 'Les nouilles cuisent au jus de canne']

Notez que je vous recomande d’éviter à tout prix de mélanger des chaînes encodées, et des chaînes unicode, de toute façon. J’utilise ceci uniquement parce que c’est un cas simple à comprendre.

Mais ici, le problème peut être résumé ainsi: nous avons une collection hétérogène d’objets (qui pourraient être de n’importe quels autres types, il y a des cas beaucoup plus légitimes), et nous appliquons la méthode de l’objet. Qui ne correspond pas forcément à son type.

Avoir des types différents avec la même interface est courant en Python pour profiter du duck typing, donc ce genre de chose n’est pas un edge case.

La solution est bien entendu de wrapper l’appel dans une fonction anonyme:

>>> map(lambda x: x.strip(), l)
[u'Le choix dans la date', 'Les nouilles cuisent au jus de canne']

Mais à ce stade, [x.strip() for x in l] est beaucoup plus lisible et rapide. Sans compter qu’il peut être transformé en générateur juste en changeant les [] en ().

Une petite compilation de trucs de cul bien fun

mercredi 3 octobre 2012 à 14:39

On croise des trucs pas croyable, et je me dis qu’il serait dommage de ne pas partager.

Si vous croyez que le porno c’est hard, dites vous que certaines videos youtubes peuvent être beaucoup plus perturbantes

En voici une autre qui parait normale jusqu’à ce que vous remarquiez une premier détail. Puis vous vous dites que ce n’est pas si rare sur la toile, jusqu’à ce que vous remarquiez un second détail.

Ensuite, celle-là, c’est une vidéo éducative (Oui, oui…):

Pour les non anglophones:

“Oh mon fils, tu te branlais ? C’était bon ? Et bien contente que tu le fasses en privé dans ta chambre.” (et je vais continuer à rentrer dans ta chambre sans frapper avec un air de serial killer jusqu’à ce que tu te suicide).

Et juste pour le SEO, encore un truc de cul japonais complètement barge:

Le sexe sur la plage, c’est un fantasme surévalué, le sexe avec la plage par contre:

Le SM est un grand classique, mais j’avoue que les vacuum beds et les tits pumps ne me blasent jamais:

Et pour finir, qui n’a jamais eu envie de se taper catwoman ?

Comment utiliser yield et les générateurs en Python ?

mardi 2 octobre 2012 à 14:09

Les générateurs sont une fonctionalité fabuleuse de Python, et une étape indispensable dans la maîtrise du langage. Une fois compris, vous ne pourrez plus vous en passer.

Rappel sur les itérables

Quand vous lisez des éléments un par un d’une liste, on appelle cela l’itération:

lst = [1, 2, 3]
>>> for i in lst :
...     print(i)
1
2
3

Et quand on utilise une liste en intention, on créé une liste, donc un itérable. Encore une fois, avec une boucle for, on prend ses éléments un par un, donc on itère dessus:

lst = [x*x for x in range(3)]
>>> for i in lst :
...     print(i)
0
1
4

À chaque fois qu’on peut utiliser “forin…” sur quelque chose, c’est un itérable : lists, strings, files…

Ces itérables sont pratiques car on peut les lire autant qu’on veut, mais ce n’est pas toujours idéal car on doit stocker tous les éléments en mémoire.

Les générateurs

Si vous vous souvenez de l’article sur les comprehension lists, on peut également créer des expressions génératrices:

generateur = (x*x for x in range(3))
>>> for i in generateur :
...     print(i)
0
1
4

La seule différence avec précédement, c’est qu’on utilise () au lieu de []. Mais on ne peut pas lire generateur une seconde fois car le principe des générateurs, c’est justement qu’ils génèrent tout à la volée: ici il calcule 0, puis l’oublie, puis calcule 1, et l’oublie, et calcule 4. Tout ça un par un.

Le mot clé yield

yield est un mot clé utilisé en lieu et place de return, à la différence prêt qu’on va récupérer un générateur.

>>> def creerGenerateur() :
...     mylist = range(3)
...     for i in mylist:
...         yield i*i
...
>>> generateur = creerGenerateur() # crée un générateur
>>> print(generateur) # generateur est un objet !
< generator object creerGenerateur at 0x2b484b9addc0>
>>> for i in generateur:
...     print(i)
0
1
4

Ici c’est un exemple inutile, mais dans la vraie vie vivante, c’est pratique quand on sait que la fonction va retourner de nombreuses valeurs qu’on ne souhaite lire qu’une seule fois.

Le secret des maîtres Zen qui ont aquis la comprenhension transcendentale de yield, c’est de savoir que quand on appelle la fonction, le code de la fonction n’est pas éxécuté. A la place, la fonction va retourner un objet générateur.

C’est pas évident à comprendre, alors relisez plusieurs fois cette partie.

creerGenerateur() n’éxécute pas le code de creerGenerateur. creerGenerateur() retourne un objet générateur.

En fait, tant qu’on ne touche pas au générateur, il ne se passe rien. Puis, dès qu’on commence à itérer sur le générateur, le code de la fonction s’éxécuter.

La première fois que le code s’éxécute, il va partir du début de la fonction, arriver jusqu’à yield, et retourner la première valeur. Ensuite, à chaque nouveau tour de boucle, le code va reprendre de la où il s’est arrêté (oui, Python sauvegarde l’état du code du générateur entre chaque appel), et éxécuter le code à nouveau jusqu’à ce qu’il rencontre yield. Donc dans notre cas, il va faire un tour de boucle.

Il va continuer comme ça jusqu’à ce que le code ne rencontre plus yield, et donc qu’il n’y a plus de valeur à retourner. Le générateur est alors considéré comme définitivement vide. Il ne peut pas être “rembobiné”, il faut en créer un autre.

La raison pour laquelle le code ne rencontre plus yield est celle de votre choix: condition if/else, boucle, recursion… Vous pouvez même yielder à l’infini.

Un exemple concret et un café, plz

yield permet non seulement d’économiser de la mémoire, mais surtout de masquer la complexité d’un algo derrière une API classique d’itération.

Supposez que vous ayez une fonction qui – tada ! – extrait les mots de plus de 3 caractères de tous les fichiers d’un dossier.

Elle pourrait ressembler à ça:

import os
 
def extraire_mots(dossier):
    for fichier in os.listdir(dossier):
        with open(os.path.join(dossier, fichier)) as f:
            for ligne in f:
                for mot in ligne.split():
                    if len(mot) > 3:
                        yield mot

Vous avez là un algo dont on masque complètement la complexité, car du point de vue de l’utilisateur, il fait juste ça:

for mot in extraire_mots(dossier):
    print mot

Et pour lui c’est transparent. En plus, il peut utiliser tous les outils qu’on utilise sur les itérables d’habitude. Toutes les fonctions qui acceptent les itérables acceptent donc le résultat de la fonction en paramètre grâce à la magie du duck typing. On créé ainsi une merveilleuse toolbox.

Controller yield

>>> class DistributeurDeCapote():
    stock = True
    def allumer(self):
        while self.stock:
            yield "capote"
...

Tant qu’il y a du stock, on peut récupérer autant de capotes que l’on veut.

>>> distributeur_en_bas_de_la_rue = DistributeurDeCapote()
>>> distribuer = distributeur_en_bas_de_la_rue.allumer()
>>> print distribuer.next()
capote
>>> print distribuer.next()
capote
>>> print([distribuer.next() for c in range(4)])
['capote', 'capote', 'capote', 'capote']

Dès qu’il n’y a plus de stock…

>>> distributeur_en_bas_de_la_rue.stock = False
>>> distribuer.next()
Traceback (most recent call last):
  File "<ipython-input-22-389e61418395>", line 1, in <module>
    distribuer.next()
StopIteration
< type 'exceptions.StopIteration'>

Et c’est vrai pour tout nouveau générateur:

>>> distribuer = distributeur_en_bas_de_la_rue.allumer()
>>> distribuer.next()
Traceback (most recent call last):
  File "<ipython-input-24-389e61418395>", line 1, in <module>
    distribuer.next()
StopIteration

Allumer une machine vide n’a jamais permis de remplir le stock ;-) Mais il suffit de remplir le stock pour repartir comme en 40:

>>> distributeur_en_bas_de_la_rue.stock = True
>>> distribuer = distributeur_en_bas_de_la_rue.allumer()
>>> for c in distribuer :
...     print c
capote
capote
capote
capote
capote
capote
capote
capote
capote
capote
capote
capote
...

itertools: votre nouveau module favori

Le truc avec les générateurs, c’est qu’il faut les manipuler en prenant en compte leur nature: on ne peut les lire qu’une fois, et on ne peut pas déterminer leur longeur à l’avance. itertools est un module spécialisé la dedans: map, zip, slice… Il contient des fonctions qui marchent sur tous les itérables, y compris les générateurs.

Et rappelez-vous, les strings, les listes, les sets et même les fichiers sont itérables.

Chaîner deux itérables, et prendre les 10 premiers caractères ? Piece of cake !

>>> import itertools
>>> d = DistributeurDeCapote().allumer()
>>> generateur = itertools.chain("12345", d)
>>> generateur = itertools.islice(generateur, 0, 10)
>>> for x in generateur:
...     print x
...     
1
2
3
4
5
capote
capote
capote
capote
capote

Les dessous de l’itération

Sous le capôt, tous les itérables utilisent un générateur appelé “itérateur”. On peut récupérer l’itérateur en utiliser la fonction iter() sur un itérable:

>>> iter([1, 2, 3])
< listiterator object at 0x7f58b9735dd0>
>>> iter((1, 2, 3))
< tupleiterator object at 0x7f58b9735e10>
>>> iter(x*x for x in (1, 2, 3))
< generator object  at 0x7f58b9723820>

Les itérateurs ont une méthode next() qui retourne une valeur pour chaque appel de la méthode. Quand il n’y a plus de valeur, ils lèvent l’exception StopIteration:

>>> gen = iter([1, 2, 3])
>>> gen.next()
1
>>> gen.next()
2
>>> gen.next()
3
>>> gen.next()
Traceback (most recent call last):
  File "< stdin>", line 1, in < module>
StopIteration

Message à tous ceux qui pensent que je fabule quand je dis qu’en Python on utilise les exceptions pour contrôler le flux d’un programme (sacrilège !): ceci est le mécanisme des boucles interne en Python. Les boucles for utilisent iter() pour créer un générateur, puis attrappent une exception pour s’arrêter. À chaque boucle for, vous levez une exception sans le savoir.

Pour la petite histoire, l’implémentation actuelle est que iter() appelle la méthode __iter__() sur l’objet passé en paramètre. Donc ça veut dire que vous pouvez créer vos propres itérables:

>>> class MonIterableRienQuaMoi(object):
...     def __iter__(self):
...         yield 'Python'
...         yield "ça"
...         yield 'déchire'
...
>>> gen = iter(MonIterableRienQuaMoi())
>>> gen.next()
'Python'
>>> gen.next()
'ça'
>>> gen.next()
'déchire'
>>> gen.next()
Traceback (most recent call last):
  File "< stdin>", line 1, in < module>
StopIteration
>>> for x in MonIterableRienQuaMoi():
...     print x
...
Python
ça
déchire

Le piège de la méthode strip() des chaînes en Python

lundi 1 octobre 2012 à 15:27

strip(), et ses acolytes lstrip() et rstrip(), ne retirent pas la chaîne de caractères aux extrémités d’une autre chaîne de caractères.

Elles retirent des extrémités toutes lettres qui sont dans la chaînes passée en paramètre. La nuance est subtile, mais c’est la différence entre ça:

>>> "# ##  # test ".strip('# ') # comportement attendu mais faux
'##  # test '

Et ça:

>>> "# ##  # test ".strip('# ') # comportement réel
'test'

À ceux qui n’ont pas eu de réponse via le formulaire de contact: désolé

lundi 1 octobre 2012 à 03:06

Depuis qu’on a changé de formulaire de contact, on a quelques problèmes pour répondre.

Après diagnostique, c’est une combinaison de deux plugins qui est en cause. On a rechangé le formulaire de contact, il est moche, mais cette fois il marche.

Donc si vous avez envoyé un message dans les dernières semaines, nous sommes désolés, nous n’avons pas pu répondre.

Liste de mails pour lesquels on a cliqué aveuglément sur le bouton “répondre”, entraînant un doute que la réponse soit jamais arrivé:

Je me permet de vous envoyer un email afin d’avoir une confirmation que l’initiative de traduction de la doc django (djangoSpirit) est de vous ?

Non, ce n’est pas nous. C’est une superbe initiative, d’ailleurs l’AFPY est en train de faire pareil avec la doc Python. Donc n’hésitez pas à vous porter volontaire. Je me tâte à le faire moi-même, mais ça voudrait dire moins d’articles sur le blog.

Bonjour, afin d’eviter que ca parte en vrille voici qd meme des corrections sur l’orthographe sur le billet http://sametmax.com/javascript-le-conquerant

(et d’autres mails de foxmask). Merci vieux !

parfois, firefox (15.0.1 sous GNU/Linux) affiche étrangement votre site. Deux screens (haut et bas de la page) :

http://imgur.com/eE4ex,6OsoB

Et:

il semble que parfois, sametmax.com prenne mon firefox pour un safari mobile, et il fait des histoires (là je vous envoie ça depuis elinks ^^)

Vous n’êtes pas les seuls. Pour une raison inconnue (les joies de WordPress), le site switch de temps en temps tout seul en version mobile pour un browser desktop. Et la version mobile vise les smartphones, qui ont tous toujours JS activé, et table dessus. Même si nous avions le temps, ni Max ni moi n’avons le courage de nous lancer dans le debuggage du code immonde de ce moteur de blog, ou pire, de celui des plugins. Donc sorry, ça va arriver, encore et encore, et on ne prévoit pas de le corriger.

Serait-il possible d’avoir un flux RSS pour les commentaires des posts ?

C’est fait ! On a rajouté un lien sous les commentaires.

je crois qu’il y a un petit problème avec votre flux RSS: les articles sont à la bonne date, mais en timezone GMT…du coup les news sont décalées de +2 heures dans mes agrégateurs

Mais mon ami, qui a dit que nous vivions actuellement en France ?

No Matter what you are selling – Hit-Booster
will send targeted visitors to your website!

Within 15 minutes you will have your own website
traffic generator that will bring in an ever increasing
amount of hits to your websites! Automatically

This software is perfect for bringing real traffic to
your site… even if… it’s an affiliate link where you
have no control over the website content!

Super ! On se recontacte très vite alors ! J’ai un ami diplomate au Nigeria en train de mourir qui a justement besoin de publicité pour donner son immense fortune.