PROJET AUTOBLOG


Sam et Max

source: Sam et Max

⇐ retour index

Usages et dangers du null object pattern en Python

mardi 4 décembre 2012 à 16:46

Le motif de conception de l’objet nul ou “Null object pattern” dans la langue de Justin Bieber, est une technique qui consiste à créer un objet qui ne fait rien. Et quand on lui demande de faire quelque chose, il se renvoie lui-même.

Voici à quoi ça ressemble en Python:

class Null(object):
 
    _instances = {}
 
    def __new__(cls, *args, **kwargs):
        """
            On s'assure que Null est un singleton, c'est à dire qu'on ne peut
            pas créer deux fois Null. Ainsi, id(Null()) == id(Null()).
 
            Je devrais peut être faire un article sur le Singleton.
        """
        if cls not in cls._instances:
            cls._instances[cls] = super(Null, cls).__new__(cls, *args, **kwargs)
        return cls._instances[cls]
 
 
    def __init__(self, *args, **kwargs):
        """
            Null accepte n'importe quel argument
        """
        pass
 
    def __repr__(self):
        """
            On lui met quand même quelque représentation pour le debug
        """
        return "<Null>"
 
    def __str__(self):
        """
            Null se cast sous forme de chaîne vide. Comme ça vous pouvez
            faire open('fichier').write(Null()) et ça ne fait rien.
        """
        return ""
 
 
    def __eq__(self, other):
        """
            Null est égal à lui même, ou à None car on peut les utiliser dans
            des endroits similaires. On peut toujours les distinguer avec "is".
        """
        return id(self) == id(other) or other is None
 
    # Comme None, Null() est faux dans un contexte booléen
    __nonzero__ = __bool__ = lambda self: False
 
    # Et là, on fait le gros travail d'annihilation: toutes les méthodes
    # et opérations du Null() renvoient Null(). Et comme Null() accepte tout
    # et ne fait rien, toute opération marche toujours, ne fait rien, et
    # renvoie une valeur qui assure que les suivantes feront pareil.
    nullify = lambda self, *x, **kwargs: self
 
    __call__ = nullify
    __getattr__ = __setattr__ = __delattr__ = nullify
    __cmp__ = __ne__ = __lt__ = __gt__ = __le__ = __ge__ = nullify
    __pos__ = __neg__ = __abs__ = __invert__ = nullify
    __add__ = __sub__ = __mul__ = __mod__ = __pow__ = nullify
    __floordiv__ = __div__ = __truediv__ = __divmod__ = nullify
    __lshift__ = __rshift__ = __and__ = __or__ = __xor__ = nullify
    __radd__ = __rsub__ = __rmul__ = __rmod__ = __rpow__ = nullify
    __rfloordiv__ = __rdiv__ = __rtruediv__ = __rdivmod__ = nullify
    __rlshift__ = __rrshift__ = __rand__ = __ror__ = __rxor__ = nullify
    __iadd__ = __isub__ = __imul__ = __imod__ = __ipow__ = nullify
    __ifloordiv__ = __idiv__ = __itruediv__ = __idivmod__ = nullify
    __ilshift__ = __irshift__ = __iand__ = __ior__ = __ixor__ = nullify
    __getitem__ = __setitem__ = __delitem__ = nullify
    __getslice__ = __setslice__ = __delslice__ = nullify
    __reversed__ = nullify
    __contains__ = __missing__ = nullify
    __enter__ = __exit__ = nullify

Certaines méthodes ne peuvent pas retourner Null() car elle sont tenues
par l’implémentation en C de retourner un certain type. C’est le cas
des méthode de conversion, d’itération, d’approximation ou certaines
renvoyant des metadata comme: __int__ , __iter__, __round__ ou __len__.

Donc il y aura toujours des cas extrêmes où Null ne marchera pas, mais croyez moi, avec le code ci-dessus, on a déjà couvert un paquet de trucs.

Comment on s’en sert

C’est la beauté de l’objet Null, il n’y a rien à faire d’autre que l’instancier. Après il se débrouille tout seul… pour ne rien faire !

Null() accepte tout à la création, et renvoie toujours le même objet :

>>> n = Null()
>>> n
<Null>
>>> id(n) == id(Null('value')) == id(Null('value', param='value'))
True

On peut lui demander tout ce qu’on veut, il retourne Null() :

>>> n() == n('value') == n('value', param='value') == n
True
>>> n.attr1
<Null>
>>> n.attr1.attr2
<Null>
>>> n.method1()
<Null>
>>> n.method1().method2()
<Null>
>>> n.method('value')
<Null>
>>> n.method(param='value')
<Null>
>>> n.method('value', param='value')
<Null>
>>> n.attr1.method1()
<Null>
>>> n.method1().attr1
<Null>

On peut le modifier, ça ne change rien :

>>> n.attr1 = 'value'
>>> n.attr1.attr2 = 'value'
>>> del n.attr1
>>> del n.attr1.attr2.attr3

Les opérations sur lui le laissent de glace:

>>> str(n) == ''
True
>>> n + 1 / 7 % 3
<Null>
>>> n[1] == n[:4] == n
True
>>> 'test' in n
False
>>> n['test']
<Null>
>>> Null() >> 1
<Null>
>>> Null() == None
True

Chouetttteeeeeee ! Mais ça sert à quoi ?

Et bien à simplifier les vérifications, et supprimer pas mal de if. Quand vous avez un état inconnu dans votre programme, au lieu de renvoyer None, ou faire des tests dans tous les sens pour éviter de planter, vous pouvez juste renvoyer Null(). Il va continuer sa petite vie dans votre programme, et accepter tout ce qu’on lui demande, sans rien faire crasher.

Attention cependant, Null() accepte tout silencieusement, et donc votre programme ne plantera jamais là où Null() est utilisé. En clair, si vous merdez et que Null() se retrouve là où il ne doit pas, il va se multiplier et remplir tout votre programme et le transformer en une soupe d’appels à Null() qui ne foutent rien, mais le font bien, et en silence.

Un design pattern puissant, mais dangereux.

L’édit obligatoire

Suite aux objections tout à fait valides de Moumoutte, je rajoute un exemple d’usage.

Il faut bien comprendre en effet que Null() n’est pas un passe droit pour faire n’importe quoi avec la gestion des erreurs, et n’est en aucun cas quelque chose à utiliser massivement.

Il est particulièrement utile quand les tests à faire sont juste trop nombreux. Par exemple, dans des secteurs comme la banque ou la biologie, on traite des données en masse, et hétérogènes, mais on fait des opérations entre les différents types.

Certains types sont compatibles, d’autres pas. Si on a des milliers de types, et des milliers d’opérations différentes, cela fait des milliers de milliers de combinaisons possibles à évaluer. Dans ce cas, Null() permet de s’affranchir de tous ces tests et simplifier énormément l’algo.

Voici un exemple simple de ce que ça donne à petit échelle (multipliez ça par mille types et opérations pour avoir un ordre de grandeur d’un cas réel):

# Chaque type peut faire des opérations uniquement sur d'autres types
 
class TypeA(object):
    def operation(self, other):
        if isinstance(other, TypeB):
            return other
        return Null()
 
 
class TypeB(object):
    def operation(self, other):
        if isinstance(other, TypeC):
            return other
        return Null()
 
 
class TypeC(object):
    def operation(self, other):
        if isinstance(other, TypeD):
            return other
        return Null()
 
 
class TypeD(object):
    def operation(self, other):
        if isinstance(other, TypeE):
            return other
        return Null()
 
 
class TypeE(object):
    def operation(self, other):
        if isinstance(other, TypeA):
            return other
        return Null()
 
 
# Votre pool de données vous arrive en masse et complètement mélangé.
# Vous n'avez AUCUN contrôle là dessus.
pool = (TypeA, TypeB, TypeC, TypeD, TypeE)
 
data1 = (random.choice(pool)() for x in xrange(1000))
data2 = (random.choice(pool)() for x in xrange(1000))
data3 = (random.choice(pool)() for x in xrange(1000))
data4 = (random.choice(pool)() for x in xrange(1000))
 
# Avec le Null() object pattern, vous ne vous posez pas la question de quel type
# va avec quoi. Vous faites l'opération comme si tout était homogène. Tout
# va se trier automatiquement.
 
res1 = (z.operation(y) for z, y in zip(data1, data2))
res2 = (z.operation(y) for z, y in zip(res1, data3))
res3 = (z.operation(y) for z, y in zip(res2, data4))
 
# Et à la fin, il est très facile de récupérer uniquement les données
# significatives.
 
print list(x.__class__.__name__ for x in res3 if x)
 
# Ceci va printer quelque chose comme:
# ['TypeD', 'TypeA', 'TypeE', 'TypeE', 'TypeC', 'TypeE', 'TypeE', 'TypeA']

On a ainsi réduit un énorme jeu de données hétérogènes en un petit jeu de données significatif, avec très très peu d’effort et un algo simple et lisible.

Nginx en reverse proxy + Gunicorn pour vos apps Django

lundi 3 décembre 2012 à 20:48

Nginx est bien connu depuis quelques années. Puissant serveur HTTP, il peut être utilisé en tant que reverse proxy (mandataire inverse en Français) afin d’améliorer les performances.
Je vous copie/colle les avantages depuis Wikipédia parce que je suis une feignasse:

cache : le mandataire inverse ou (reverse proxy) peut décharger les serveurs Web de la charge de pages/objets statiques (pages HTML, images) par la gestion d’un cache local. La charge des serveurs Web est ainsi généralement diminuée, on parle alors d’« accélérateur web » ou d’« accélérateur HTTP ».
Intermédiaire de sécurité : le mandataire inverse protège un serveur Web des attaques provenant de l’extérieur. En effet, la couche supplémentaire apportée par les mandataires inverses peut apporter une sécurité supplémentaire. La ré-écriture programmable des URL permet de masquer et de contrôler, par exemple, l’architecture d’un site web interne. Mais cette architecture permet surtout le filtrage en un point unique des accès aux ressources Web.
Chiffrement SSL : le mandataire inverse peut être utilisé en tant que « terminateur SSL », par exemple par le biais de matériel dédié,
Répartition de charge : le mandataire inverse peut distribuer la charge d’un site unique sur plusieurs serveurs Web applicatifs. Selon sa configuration, un travail de ré-écriture d’URL sera donc nécessaire,
Compression : le mandataire inverse peut optimiser la compression du contenu des sites.

Pour résumé:

Visiteur > Web > Votre Serveur > Nginx > Gunicorn > Django

Dans la configuration suivante Nginx va servir les fichiers statiques (images, js, etc) et envoyer le reste des requêtes sur Gunicorn. Gunicorn est une interface WSGI entre le serveur HTTP (nginx) et Python, je n’ai pas trouvé de bonne explication sur WSGI à part le site officiel fait par des autistes.

Soit la configuration de Gunicorn suivante (on peut le lancer avec supervisor):

gunicorn_django  myapp.py --bind 127.0.0.1:8080 --log-file /var/log/myapp.gunicorn.log --log-level info --workers 2 --pid /tmp/myapp.pid  --worker-class gevent

Gunicorn va “écouter” en local sur le port 8080, il va logger des infos dans le fichier /var/log/myapp.gunicorn.log (pratique pour le debug si besoin).
Pour les workers et worker-class regardez la doc.

Configurer Nginx:
Dans la conf Nginx on va avoir.

server {
        listen       80;
        server_name mysite.com www.mysite.com;
 
        location /static/ {
            root  /home/myapp/prod;
            gzip  on;
        }
 
        location / {
            proxy_pass http://127.0.0.1:8080; # Pass to Gunicorn
            proxy_set_header X-Real-IP $remote_addr; # get real Client IP
        }
}

location va indiquer à Nginx ce qu’il faut faire lorsqu’il rencontre certaines URL.

Dans notre cas le location /static/ va dire à Nginx de servir directement tous les fichiers du répertoire /home/myapp/prod/static pour les URL http://mysite.com/static/… En général les images, javascript, etc.

Pour le reste location / va renvoyer les requêtes sur votre serveur Gunicorn qui va les traiter.

proxy_set_header sert à passer à Gunicorn certaines infos comme l’IP réelle du client (sinon Gunicorn verrait celle de NGinx 127.0.0.1).

De cette manière on ne surcharge pas Gunicorn pour servir nos fichiers statiques, on ajoute une protection (Nginx).
On peut éventuellement mettre en cache certaines pages mais je n’ai jamais utilisé cette option.
Nginx peut également distribuer la charge sur plusieurs serveurs et se charger de la compression des pages (gzip).

Des idées cadeaux pour Noël

dimanche 2 décembre 2012 à 16:48

Tous les ans c’est pareil. On repousse au maximum, on traine et puis arrivent les derniers jours.
Alors on se presse pour aller acheter les cadeaux de Noël.
Et souvent ça se fait le dernier jour dans la grande surface du coin bien sûr bondée d’autres personnes ayant aussi attendu le dernier moment.

J’espère que ce post sera complété par vos coms car je fais partie de ces gens là :)

Ma méthode (imparfaite):
Elle consiste en une chose simple préférée des feignasses. Je fuis le centre ville, la périphérie pour rester…. devant mon ordi.
En fait 90% de mes achats se concentrent sur Amazon, ils ont une option de wishlist que je remplis au fur et à mesure dans l’année et arrivé à la date fatidique je la complète avec souvent des suggestions et je commande.
Les 10% restant sont en général Champagne, foie gras, hommard.
De cette façon même si mes cadeaux sont pourris on a toujours un bon repas.

N’achetez jamais un seul gros cadeau !
Un seul cadeau a plus de chance de décevoir et coutera plus cher que plusieurs petits cadeaux. Par exemple je ne vais pas acheter un service à Thé mais plutôt quelques tasses design + un échantillons de Thé + un livre sur l’histoire du Thé. ça fait plus de cadeaux donc plus de chance d’en avoir un qui plait.

Procéder par association thématique:
Pour aller encore plus vite je procède par association pour les cadeaux. Comme dit ci-dessus je groupe les cadeaux par famille, la bouffe avec la bouffe, les vêtements avec les vêtements, etc.
De la sorte je lance une thématique par personne et ça accélère encore les choix.
Les suggestions Amazon servent bien en général pour ça.

Un conseiller qui répond à vos attentes...

Des cadeaux ciblés:
Avant de commencer à chercher les cadeaux je “profile” mes cibles pour connaitre leurs envies du moment et en général. Ça m’aide à mettre une étiquette sur chacune d’entre elles et commencer à choisir ses futurs cadeaux.

Quelques idées d’associations:

personne 1: cycliste amateur :-> casque, gants, gourde, lumières, chaussettes, etc
personne 2: aime la cuisine :-> livre de recettes pratiques, kit à fabriquer des sushis, couteau céramique, planche à pizza pour four, etc
personne 3: ado attardé :-> un jeu pour sa gameboy, écouteurs Dr DREE megabass, trousse de rangement pour sa GB, etc
personne 4: la soeur Hilton :-> un rdv chez Sephora pour nettoyage de peau, 1 seance d’UV, quelques rouges à lèvres copiés sur ceux qu’elle utilise souvent. Pas de parfum c’est perso.

Lancer la commande sur Amazon.fr en prenant aussi les rouleaux de papier d’emballages et y a plus qu’à attendre.

Et à tout ça en général je propose mes services de cuisinier.

Et la terrine de chaton ?..

Le matin de Noël je vais faire les courses avec 2, 3 personnes, on fait le marché et on choisit les produits frais que l’on va manger le soir.
C’est un moment de partage que j’apprécie, on parle, on est en famille, on déconne, on goûte les produits.

Les cadeaux deviennent moins importants que la bouffe du coup et c’est pas plus mal car c’est là que ça se passe, autour d’une bonne table, en famille ou entre amis.

 

Je sais que Sam n’aime pas Amazon, que certains disent qu’on fait mourir le commerce de proximité, etc, j’ai déjà fait quelques aller/retour en ville et je n’ai jamais trouvé ce que je voulais, obligé donc de rentrer commander sur le Net.
Je garde cependant le côté shopping pour la bouffe et là je ne regarde pas à la dépense, que de la qualité (on a qu’une vie).

 

Bon, à vous de partager vos astuces (celle de voler un caddy plein à la sortie du Mamouth je la connais déjà)…

 

Passez de bonnes Vacances et un joyeux Noël

 

Transformer des caractères spéciaux en ASCII

samedi 1 décembre 2012 à 15:23

Dans beaucoup de cas, plutôt que de se taper la gestion de l’encodage, on préfère tout ramener au plus petit dénominateur commun: l’ASCII. Pas d’accent, pas de problème, comme disait mon grand-père juif. Ça devait être un autre contexte. Mais quand même.

Remplacer les accents par leurs équivalents ASCII les plus proches

Une astuce que j’ai lue pour la toute première fois dans les excellents snippets Python de Sauvage (encore et toujours…).

>>> import unicodedata
>>> chaine_unicode = u"Vous êtes le Père Noël ? s'étonna le petit garçon."
>>> unicodedata.normalize('NFKD', chaine_unicode).encode('ascii', 'ignore')
"Vous etes le Pere Noel ? s'etonna le petit garcon."

Si un caractère n’est pas vraiment remplaçable, on peut choisir plusieurs stratégies. Ignorer le caractère:

>>> unicodedata.normalize('NFKD', u"Bei Jing en chinois s'écrit 北亰").encode('ascii', 'ignore')
"Bei Jing en chinois s'ecrit "

Remplacer le caractère par un point d’interrogation:

>>> unicodedata.normalize('NFKD', u"Bei Jing en chinois s'écrit 北亰").encode('ascii', 'replace')
"Bei Jing en chinois s'e?crit ??"

Notez l’effet sur le caractère accentué…

Ou se vautrer comme une grosse loutre bourrée à la bière:

>>> unicodedata.normalize('NFKD', u"Bei Jing en chinois s'écrit 北亰").encode('ascii')
Traceback (most recent call last):
  File "<ipython-input-21-24181c80fc7f>", line 1, in <module>
    unicodedata.normalize('NFKD', u"Bei Jing en chinois s'écrit 北亰").encode('ascii')
UnicodeEncodeError: 'ascii' codec can't encode character u'\u0301' in position 23: ordinal not in range(128)

Ce qui pour nous a un intérêt limité.

Certains alphabets, comme le hongrois, ont des caractères qui pourraient être extrapolés vers l’ASCII, mais la normalisation NFKD ne le permet pas. On peut alors faire appel à une bibliothèque externe.

Unidecode, la cafetière de l’encoding

Unidecode est une lib pip installable qui permet la translittération de caractères. Ce n’est pas toujours très rigoureux, mais c’est toujours très pratique.

Par exemple, vous voulez créer un site Web de mode à destination du marché Hongrois. Si, si. C’est un marché en pleine expansion, j’vous dis.

Et bien entendu vous avez une rubrique manteau, qui se dit en hongrois, comme nous le savons tous: “kožušček”.

Bon, si vous essayé de mettre ça en slug dans vos URL, ça va être un peu chiant à taper, et malheureusement l’astuce précédente ne va pas vous aider:

In [5]: unicodedata.normalize('NFKD', u"kožušček").encode('ascii', 'replace')
Out[5]: 'koz?us?c?ek'

Partant de là, vous pouvez soit:

In [7]: from unidecode import unidecode
In [8]: unidecode(u"kožušček")
Out[8]: 'kozuscek'

Encore plus fastoche, dans le XML et HTML

Dans ces formats à balise, on peut s’affranchir des problème d’encodage en transformant chaque caractère en une entité XML / HTML. On récupère alors du texte ASCII et le client se tapera le boulot d’afficher tous les caractères tordus auxquels l’humanité a pu penser:

In [11]: print u"Le Père Noël m'a offert un kožušček à 北亰".encode('ascii', 'xmlcharrefreplace')
Le P&#232;re No&#235;l m'a offert un ko&#382;u&#353;&#269;ek &#224; &#21271;&#20144;

Et dans tous les autres cas ?

Ben c’est comme d’hab: str.decode() pour tout ce qui rentre pour récupérer une chaîne unicode, et unicode.encode() pour tout ce qui sort pour rebalancer une chaîne de caractères au monde extérieur.

Ça mérite peut être un article d’ailleurs…

P.S: Lazarus vient de me sauver la mise en restaurant cet article suite à un clic malheureux. Vive lazarus.

Le travail d’écriture dans le domaine du cul

jeudi 29 novembre 2012 à 16:53

On associe rarement cul et travail d’écriture.

C’est un tort.

C’est comme d’autres choses, c’est bon quand c’est bien fait !

Par exemple, le très vieux mais excellent film de boule Maitresses très particulières fait partie de ces œuvres old school qui ont … un scénario ! Et les acteurs ne sont pas incroyablement mauvais. On y sent presque une une caricature d’Audiard dans la manière dont ils déclament leur texte. Quand aux répliques, arf, les répliques. Je vous laisse écouter:

L’image apporte une vraie valeur ajoutée, mais elle est censurée sur les tubes. Vous pouvez néanmoins facilement trouver le film complet ailleurs. Certaines phrases sont tellement mythiques que la masse créatrice du net s’est amusée à faire des samples pour pouvoir les ressortir à loisir au bureau:

J’attends l’application smartphone avec impatience.

On sait que le porno est globalement assez pauvre artistiquement, mais quand il s’agit de parodie, et quand l’œuvre originale est déjà moyenne, on se retrouve avec des choses amusantes. Par exemple, je pense que vous reconnaîtrez ce package complet:

Pack DVD collector d'une parodie pornographique d'avatar

Notez les lunettes 3D et la flashlight bleue... Je vous laisse imaginer les bonus !

Et ce qui est marrant avec ce truc, c’est que les scènes sont classiques pour du pron, et donc semblent presque plus à propos que la version réelle. Par exemple, la bonasse qui joue le rôle de la meuf qui était jouée par Sigourney Weaver chope le soldat lors de son examen médicale pour la turlutte protocolaire après décryogénisation:

It’s biolab protocol, Marine, gotta do it.

\o/

Mais pour avoir du texte bien fendard, on est pas obligé de se pencher sur un boular. Les canadiens sont très bons pour ce genre de choses. J’ai particulièrement aimé Le déclin de l’empire américain, qui est finalement un film X où les acteurs sont intelligents et habillés:

Rémy : Et puis c’est pas tout, il faut la faire JOUIR. C’est pas de la tarte, ça. D’abord, trouver le clitoris.
Claude : Oh mon Dieu !
Rémy : Déjà, ça, c’est une entreprise assez délicate, merci. Y’a même des cas où c’est pire que de chercher une chenille sur un damier. Ensuite, t’as dans la tête toutes les notes, appendices et chapitres de Masters & Johnson, le rapport Hite, la controverse du G spot, Germaine Greer, Nancy Friday… Tu sais plus si tu dois employer tes doigts, ta langue, ta queue. Là, tu la guettes du coin de l’oeil. Tu te dis “Elle a l’air de… j’espère que… Mais même si…” L’ENFER, L’ENFER !!

Mais souvenez-vous que pour parler cochon, on est pas obligé de faire dans le film. La littérature française abonde (ne serait-ce que les centaines de succulentes phrases du genre ” Vous avez dû en voir de dures, ma pauvre amie! – - J’en ai vu davantage de molles!” de San Antonio), mais surtout, le Web sait se lâcher. Bien sûr, il y a des blogs (je parle pas forcément du nôtre, mais aussi des trucs super marrants comme l’ancien blog de l’escorte Belle de jour), mais parfois les pros savent aussi se lâcher. Libé par exemple (ils utilisent Django, c’est forcément des gens bien), sait parfois rapprocher les balles des boules:

… Sylvain Wiltord, qui égalise juste avant le clap de fin. Coït interrompu et prolongations-débandade : Trezeguet claque de volée. 2-1 ! But (et couilles) en or pour les Bleus. La Squadra Azzurra va encore faire ceinture. Dix-huit ans que ça dure !

Et puis merde, on est des gros pervers dans notre pays. Tous les auteurs classiques ont écrit des trucs polissons. Vous avez étudié Guillaume Apollinaire au collège ? Je suis certain qu’on vous a pas fait apprendre ce verset par coeur, extrait des Onze Mille Verges :

Tes mains introduiront mon beau membre asinin
Dans le sacré bordel ouvert entre tes cuisses
Et je veux t’avouer, en dépit d’Avinain,
Que me fait ton amour pourvu que tu jouisses!

Et votre papa, qui écoutait Brassens, validait complètement cet état de fait. Y a pas que les chanteurs de Hard Rock qui mettent du “Fuck” partout:

Sur ce, un whisky ?