PROJET AUTOBLOG


Sam & Max: Python, Django, Git et du cul

Site original : Sam & Max: Python, Django, Git et du cul

⇐ retour index

Un petit tour des frameworks Web Python

dimanche 22 juin 2014 à 05:28

Bottle : le plus petit disponible (tient dans un fichier). Génial pour du code jetable ou des petits sites, etc. Fantastique outil d’enseignement et d’apprentissage. En plus, ça tient plutôt bien la charge compte tenu de la taille.

django : le framework Python le plus connu. On peut tout faire avec, l’écosystème est fantastique (il y a des apps djangos tierces partout pour tout et n’importe quoi, c’est un truc de fou). Mais le maîtriser prend du temps. Efficace pour construire un site web avec beaucoup de logique personnalisée. Au final, si on veut être sérieux dans en programmation Web avec Python, on finit toujours par passer par Django.

flask : sa taille est entre django et bottle, et avec tout ce qu’il faut pour faire un site de taille moyenne. Il commence à avoir pas mal de plugins disponibles un peu partout sur la toile, et est une alternative très sympa quand on n’a pas besoin de charger les 3 tonnes de Django.

wep2py : se positionne en alternative à Django et Flask, mais avec une philosophie très différente. Pas mal de magie (à la rails), des interfaces graphiques pour l’admin, composants très couplés mais très intégrés… Je ne suis pas fan, mais on m’en a dit du bien.

cherrypy : un framework WSGI pure Python qui a de très bonnes perfs sans rien rajouter, même en prod. Mais depuis qu’ils ont rendu disponible sa partie serveur et qu’on peut l’utiliser pour Django/flask/bottle, ça ne vaut plus le coup d’utiliser sa partie framework qui n’a rien de fantastique.

pyramid : le plus gros compétiteur de Django en termes de fonctionnalités. Beaucoup moins monolithique, et bien plus flexible, donnant pas mal de contrôle. Mais l’intégration des composants de django le rend beaucoup plus facile à maîtriser, et son écosystème est 100 fois meilleur (j’ai toujours été en froid avec l’écosystème Zope).

twisted un framework internet asynchrone en pur Python. J’ai dit INTERNET, pas WEB. On peut faire HTTP, mais aussi SSH, IMAP, FTP, et à peu près n’importe quoi. Surpuissant, et ses performances sont incroyables. Son API est aussi la plus merdique de tout l’univers Python. Apprendre et utiliser Twisted, c’est comme se branler avec des gants de boxe en récitant l’alphabet à l’envers.

tornado : framework Web asynchrone. Techniquement le seul compétiteur de nodejs en pur Python (pas de gevent, d’extension C, etc) si on exclue crossbar encore trop immature. L’API n’est pas incroyable, mais pas trop dur, et les perfs sont bonnes. Les Websocket marchent clé en main.

cyclone : tornado qui tourne dans l’event loop de twisted. Pour pouvoir utiliser l’API de tornado pour le Web, mais les outils de twisted pour le reste. Si vous êtes capable d’utiliser ça à son plein potentiel, qu’est-ce que vous foutez à lire cet article ?

webpy : un ancêtre. Un fantôme du passé. Do not use.

Il existe ensuite tout un tas d’expérimentations de framework avec asyncio, gevent, etc. C’est plus du bricolage qu’autre chose pour le moment, alors je ne recommande pas de base un gros projet dessus à moins d’être déjà très à l’aise avec la prog web python.

flattr this!

Attributs privés en Python

samedi 21 juin 2014 à 03:15

Tout est accessible en Python. Il n’y a pas de variables privées.

Quand on veut une variable à usage interne, la convention est de la nommer avec un underscore devant :

class Monique:
    def __init__(self):
        self._private = "lessons"

Ça n’empêche rien, mais les devs savent qu’il faut éviter d’utiliser cette variable car elle ne fait pas partie de l’API publique de la classe, et l’implémentation pourrait changer plus tard.

Comme self, c’est une convention forte puisque la plupart des libs de completion du code la prennent en compte.

Il existe une fonctionalité moins connue, qui fait que quand on utilise deux underscores, accéder directement à la variable lève un AttributeError:

class Monique:
    def __init__(self):
        self.__private = "lessons"
 
m = Monique()
print(m.__private)
AttributeError: 'Monique' object has no attribute '__private'

Du coup, quelques rares personnes ont utilisé cette feature pour émuler des attributs privés, ignorant le fait qu’on vous dit partout sur la toile que tout est accessible en Python.

C’est une mauvaise idée.

C’est une mauvaise idée car si votre objectif est la sécurité, ça ne sert à rien puisque votre variable est accessible de tas de manières détournées différentes. Par exemple :

print(m.__dict__['_Monique__private'])
# lessons
m.__dict__['_Monique__private'] = None
print(m.__dict__['_Monique__private'])
# None

C’est une mauvaise idée car si votre objectif est de créer une API publique, un seul underscore suffit.

Enfin c’est une mauvaise idée car si vous fournissez une lib qui ne correspond pas à un besoin, quelqu’un peut toujours contourner le problème en monkey patchant votre code le temps que vous trouviez une solution plus propre. En rendant la variable “privée”, vous rendez ceci plus difficile, sans le rendre impossible. Tout le monde y perd.

flattr this!

Mais qui donc hérite de ma classe ?

vendredi 20 juin 2014 à 15:39

Pas une question que l’on se pose souvent, à moins d’avoir une lib qui expose une classe avec beaucoup d’introspection.

Ceci dit pour la culture G, je me suis dit que ça pourrait intéresser quelques geeks de savoir comment lister les enfants d’une classe, récursivement.

def subclasses(cls, _found=()):
    """ Retourne toutes les classes héritant d'une classe """
 
    # On check que la classe est hérite bien de "object".
    # Utile uniquement pour un code 2.x qui utiliserait
    # des old style classes, qui n'ont pas "__subclasses__".
    if not isinstance(cls, type):
      raise TypeError('subclasses works only with new-style classes'
                            ', not %s' % cls)
 
    # On va stocker les classes rencontrées dans un set
    # pour éviter les doublons car on peut tomber sur
    # un héritage en forme de diamant.
    _found = _found or set()
 
    try:
      subs = cls.__subclasses__()
    except TypeError:  # Erreur levée si cls == type, petits vicieux
      subs = cls.__subclasses__(cls)
 
    for sub in subs:
        if sub not in _found:
            _found.add(sub)
            # Un appel récursif comme on les aimes
            subclasses(sub, _found)
 
    return _found

Pas très compliqué, donc, si on connait les piègeounets : ça ne marche pas sur les old syle classes, et il y a un traitement spécial pour type. Celà dit, qui utilise encore des old styles classes ? Et franchement lister les héritiers de type, à par pour satisfaire sa curiosité…

Aller, juste pour le fun, sur un virtualenv vierge:

for x in subclasses(object):
    print x
## <type 'callable-iterator'>
## <class '_abcoll.Sized'>
## <type 'exceptions.EOFError'>
## <class '_abcoll.Mapping'>
## <type 'exceptions.UnicodeWarning'>
## <type 'exceptions.IOError'>
## <type 'memoryview'>
## <type 'code'>
## <type 'exceptions.ImportWarning'>
## <type 'sys.flags'>
## <type 'iterator'>
## <class 'warnings.catch_warnings'>
## <type 'exceptions.Warning'>
## <type 'exceptions.EnvironmentError'>
## <type 'dict'>
## <class 'encodings.utf_8.IncrementalEncoder'>
## <type 'weakproxy'>
## <class '_abcoll.Iterator'>
## <type 'sys.version_info'>
## <type 'posix.stat_result'>
## <type 'frozenset'>
## <class 'codecs.CodecInfo'>
## <class 'zipimport.ZipImportError'>
## <type 'exceptions.StopIteration'>
## <type 'exceptions.ImportError'>
## <class 'site._Printer'>
## <type 'weakref'>
## <class '_abcoll.Sequence'>
## <type 'property'>
## <class 'encodings.utf_8.IncrementalDecoder'>
## <type 'imp.NullImporter'>
## <type 'exceptions.TypeError'>
## <type 'bytearray'>
## <type 'int'>
## <type 'exceptions.UnicodeTranslateError'>
## <type 'instance'>
## <type 'exceptions.BaseException'>
## <type 'exceptions.SyntaxWarning'>
## <type 'file'>
## <class '_abcoll.MappingView'>
## <class '_weakrefset._IterationGuard'>
## <type 'exceptions.UnicodeDecodeError'>
## <type 'classobj'>
## <type 'exceptions.UnicodeError'>
## <class 'abc.abstractproperty'>
## <type 'exceptions.DeprecationWarning'>
## <type 'complex'>
## <type 'set'>
## <type 'buffer'>
## <type 'generator'>
## <type 'exceptions.Exception'>
## <class 'codecs.IncrementalDecoder'>
## <type 'exceptions.UserWarning'>
## <type 'staticmethod'>
## <type 'exceptions.UnicodeEncodeError'>
## <class 'encodings.CodecRegistryError'>
## <class '_abcoll.MutableSequence'>
## <class '_abcoll.KeysView'>
## <type 'cell'>
## <type 'exceptions.StandardError'>
## <type 'enumerate'>
## <type 'exceptions.KeyboardInterrupt'>
## <type 'exceptions.BufferError'>
## <class 'warnings.WarningMessage'>
## <type 'EncodingMap'>
## <class 'codecs.BufferedIncrementalDecoder'>
## <type 'exceptions.ValueError'>
## <class '_abcoll.Set'>
## <type 'exceptions.MemoryError'>
## <type 'exceptions.SystemExit'>
## <type 'reversed'>
## <class '_abcoll.Hashable'>
## <type 'bool'>
## <type 'classmethod'>
## <type 'exceptions.ReferenceError'>
## <type 'exceptions.GeneratorExit'>
## <class '_abcoll.Container'>
## <class '_abcoll.ItemsView'>
## <type 'xrange'>
## <type 'exceptions.PendingDeprecationWarning'>
## <type 'basestring'>
## <type 'exceptions.SystemError'>
## <type 'exceptions.ZeroDivisionError'>
## <class 'site.Quitter'>
## <type 'long'>
## <type 'NotImplementedType'>
## <type 'super'>
## <type 'fieldnameiterator'>
## <class '_abcoll.Callable'>
## <class '_abcoll.MutableSet'>
## <type 'exceptions.ArithmeticError'>
## <class 'abc.ABCMeta'>
## <type 'exceptions.OverflowError'>
## <type 'exceptions.FutureWarning'>
## <type 'sys.float_info'>
## <type 'formatteriterator'>
## <type 'type'>
## <type 'exceptions.AssertionError'>
## <type 'traceback'>
## <type 'exceptions.FloatingPointError'>
## <type 'function'>
## <type 'instancemethod'>
## <type 'sys.long_info'>
## <type 'builtin_function_or_method'>
## <class 'signal.ItimerError'>
## <class 'site._Helper'>
## <type 'exceptions.LookupError'>
## <type 'wrapper_descriptor'>
## <type 'exceptions.KeyError'>
## <class '_abcoll.ValuesView'>
## <type 'slice'>
## <class '_weakrefset.WeakSet'>
## <type 'list'>
## <type 'exceptions.IndexError'>
## <type 'getset_descriptor'>
## <type 'posix.statvfs_result'>
## <type 'exceptions.SyntaxError'>
## <type 'frame'>
## <class 'codecs.BufferedIncrementalEncoder'>
## <type 'PyCapsule'>
## <type 'exceptions.IndentationError'>
## <type 'NoneType'>
## <type 'zipimport.zipimporter'>
## <type 'member_descriptor'>
## <type 'exceptions.AttributeError'>
## <type 'ellipsis'>
## <type 'exceptions.UnboundLocalError'>
## <type 'unicode'>
## <class '_abcoll.Iterable'>
## <type 'exceptions.TabError'>
## <type 'exceptions.NameError'>
## <type 'tuple'>
## <class 'warnings._OptionError'>
## <type 'exceptions.NotImplementedError'>
## <type 'exceptions.RuntimeWarning'>
## <type 'str'>
## <class '_abcoll.MutableMapping'>
## <type 'exceptions.BytesWarning'>
## <type 'module'>
## <type 'exceptions.RuntimeError'>
## <class 'codecs.IncrementalEncoder'>
## <type 'float'>
## <type 'exceptions.OSError'>
## <type 'dictproxy'>
## <type 'weakcallableproxy'>

Putain, ça c’est du remplissage ! 20minutes serait fier de moi.

flattr this!

Où il est présenté une méthode en Python pour afficher de la vidéo 3-bit dans son terminal.

jeudi 19 juin 2014 à 06:36

Ceci est un post invité de 01ivier posté sous licence creative common 3.0 unported.

Il est des choses inutiles plus passionnantes que d’autres.
Il semblerait que j’ai un petit faible pour les choses inutiles que je fais moi-même.

Car, afficher le contenu de sa webcam dans une console, ça ne sert clairement pas à grand chose mais j’ai pourtant crié très fort en serrant les poings quand j’y suis arrivé.

Capturer de la vidéo en Python.

Sous Nunux, il existe un joli petit paquet tout beau tout chaud qui permet de gérer du flux vidéo en Python : python-opencv.

Alors, zou :

sudo apt-get install python-opencv

Et pour afficher sa webcam, seules quelques lignes suffisent :

#-*- coding: utf-8 -*-
 
import cv2
 
# Choix du périphérique de capture. 0 pour /dev/video0.
cap = cv2.VideoCapture(0)
 
while True:
 
    # On capture l'image.
    ret,im = cap.read()
 
    # On l'affiche.
    cv2.imshow('Ma Webcam à moi',im)
 
    # Et on attend 40 millisecondes pour avoir du 25 images par seconde.
    key = cv2.waitKey(40)

GO !!

Je vais bien me garder de vous faire un tuto sur OpenCV tant cette librairie est puissante (comprendre : j’y panne que dalle). Si vous êtes curieux, vous pouvez aller faire un tour ici.

Et, parce que j’avais bien d’autres choses plus intéressantes à faire que d’aller visiter le lien ci-dessus, j’ai fait un petit print du im précédent et découvert une liste toute mignonne dont voici la structure:

# Avec des valeurs pour les niveaux comprises entre 0 et 255.
im[n° de ligne][n° de colonne][niveau de bleu, niveau de vert, niveau de rouge]

J’étais content car j’allais pouvoir récupérer les valeurs de chaque pixel de cette façon:

for ligne in range(hauteur):
 
    for colonne in range(largeur):
 
        niv_bleu = im[ligne][colonne][0]
        niv_vert = im[ligne][colonne][1]
        niv_rouge = im[ligne][colonne][2]

Si, pour un autre projet formidable, je n’avais pas eu à travailler avec une RasberryPi et ses petites cuisses de bébé, je pense que j’utiliserai encore cette méthode de bourrin.

Mais voilà, c’est juste ridicule quand on sait que la liste en question est en fait un array numpy et qu’il est 10, 100 fois plus rapide de faire:

for ligne in range(hauteur):
 
    for colonne in range(largeur):
 
        niv_bleu = im.item(ligne, colonne, 0)
        niv_vert = im.item(ligne, colonne, 1)
        niv_rouge = im.item(ligne, colonne, 2)

Je n’en suis pas à me dire que la prochaine fois que j’achèterai un grille-pain, je lirai la notice avant de l’utiliser, mais presque…

Petite remarque en passant : les valeurs de rouge, de vert et de bleu étant comprise entre 0 et 255, cela nous donne 256 valeurs pour chaque couleur.
Soit 256 x 256 x 256. Soit 2⁸ x 2⁸ x 2⁸. Soit 2²⁴ ==> les couleurs de notre flux sont codées par défaut sur 24 bits.

À noter qu’il est tout à fait possible d’analyser une image sans avoir à l’afficher, ce qui permet d’utiliser OpenCV dans un environnement sans gestionnaire de fenêtre.

Afficher de la couleur dans la console.

Bon. J’avais mes niveaux RVB pour chaque pixel. Il me fallait désormais afficher de la couleur dans la console.

Quelques requêtes Duck Duck Go plus tard, je découvre termcolor qui fait très bien le job mais dont on peut se passer en regardant les codes ANSI de plus près.

Bien entendu, la grande majorité des consoles étant limitées à 8 couleurs, soit 2³, soit 3-bit je me suis restreint à cette qualité.

Démonstration :

Il est possible d’obtenir beaucoup plus de couleurs en combinant les fonds, les caractères et les intensités comme le fait la libcaca, mais on ne joue pas vraiment dans la même cour.

Perso, quand ça s’est affiché en rouge pour la première fois, j’ai eu des frissons partout. Parce qu’il faut bien comprendre que je n’ai toujours aucune idée de ce que “\033[” et autres “m” veulent dire. J’ai copié/collé, c’est tout. Et dans ces cas là, quand ça marche, c’est toujours la fête.

Ce qui pourrait être un problème, on le voit à l’image, c’est qu’une fois que j’ai écrit TATA YOYO en rouge, le prompt devient lui aussi rouge, et ainsi de suite à chaque changement de couleur. Pour remédier à ça, il faut ajouter \033[0m à la fin du texte à afficher pour que le reste soit écrit avec la couleur par défaut du terminal.

Démonstration :

C’est d’ailleurs ce que fait termcolor, sauf que, dans notre cas, nous n’avons pas besoin de revenir à cette valeur par défaut à chaque affichage de pixel vu que le suivant sera lui aussi coloré.

Je vous en parle seulement parce que vous avez l’air sympa.

J’ajouterai qu’après avoir effectué un benchmark de folie exploitant brillamment les deux points qui clignotent à chaque seconde sur mon radio-réveil, il s’est avéré que la solution “maison” était plus performante que termcolor : mon choix était fait.

Du pixel au █

J’ai renoué contact avec le █ il n’y a pas si longtemps. Aussi étonnant que cela puisse paraître, alors que je baigne quasi quotidiennement dans l’informatique depuis 30 ans, il n’est pas impossible que notre dernière rencontre remonte à 1986 sur le Commodore 64 familial.

Pour vous donner une idée de l’émotion qui m’a traversé quand j’ai revu le █, vous pourriez très clairement user de l’expression “le █ d’Olivier” en lieu et place de “la madeleine de Proust” dans vos discussions. Mais, à l’oral, le █ passe mal, et c’est bien dommage.

Pour info, le pseudo unicode de █ c’est \u2588.

Et, pour afficher un █ en couleur, il suffit de faire comme vu au dessus.

Reste à trouver un moyen de passer des millions de couleurs potentielles de notre vidéo aux huit de notre console.

C’est là que vous allez comprendre pourquoi je me suis acharné avec mes captures d’écrans. C’était pour bien vous faire intégrer l’association entre les couleurs et la valeur qui les code. À savoir :

1 : rouge
2 : vert
3 : jaune
4 : bleu
5 : violet
6 : turquoise
7 : blanc

Et là qu’est qu’on remarque ?
Que cela respecte la synthèse additive si on attribue 1 au rouge, 2 au vert et 4 au bleu, bien entendu !

1 + 2 = 3 et en synthèse additive rouge + vert = jaune
1 + 4 = 5 et en synthèse additive rouge + bleu = violet
2 + 4 = 6 et en synthèse additive vert + bleu = turquoise
1 + 2 + 4 = 7 et en synthèse additive rouge + vert + bleu = blanc

Mettez-vous à ma place: je venais de découvrir l’Amérique !

Bon, rétrospectivement, cela ne constitue vraiment rien d’extraordinaire en soi dans la mesure où c’est ce qui découle logiquement d’un codage sur 3 bits mis en place par un être humain qui a juste envie de faire simple plutôt que de faire compliqué.

Mais tout de même, sur le moment…
… L’AMÉRIQUE BORDEL ! L’AMÉRIQUE !

Il devenait alors facile d’évaluer le degré de présence de chaque composante RVB d’un pixel puis de déterminer laquelle des 8 couleurs lui correspondait le plus.

Voilà comment je m’y suis pris :

# On initialise à 0 l'indice du pixel analysé 
indice_couleur = 0
 
# On analyse le niveau de Bleu du pixel.
# S'il est au dessus du seuil...
if img.item(ligne, colonne, 0) > seuil :
 
    #...on ajoute 4 à l'indice.
    indice_couleur += 4
 
# On analyse le niveau de Vert du pixel.
# S'il est au dessus du seuil...
if img.item(ligne, colonne, 1) > seuil :
 
    #...on ajoute 2 à l'indice.
    indice_couleur += 2
 
# On analyse le niveau de Rouge du pixel.
# S'il est au dessus du seuil...
if img.item(ligne, colonne, 2) > seuil :
 
    # ...on ajoute 1 à l'indice.
    indice_couleur += 1

L’indice obtenu correspond alors au code couleur ANSI à utiliser !!

Je veux dire.

Tout de même.

C’est super, non ?

Hum…

Bien, bien…
C’est bientôt fini, il me reste juste…

Quelques remarques supplémentaires.

1) Le noir ANSI est en fait du gris, et c’est bien moche, j’ai donc préféré partir du principe que la console aurait un fond noir et afficher un “espace” pour chaque pixel noir.

2) print(“\033[H\033[2J”) permet d’effacer la console comme le fait os.system(‘clear’).
Mais, j’imagine que ça devait faire trop de 033 dans le script pour moi, parce que, psychologiquement, ça ne passait pas.
J’ai un peu discuté avec moi-même et on a fini par décider d’utiliser le clear.

3) J’ai commencé par utiliser la concaténation pour ajouter mes █ colorés à mon texte_image final :

texte_image += u"\033[3{0}m█".format(indice_couleur))

Mais, Stack Overflow a tapé à la fenêtre et il m’a dit qu’il était beaucoup plus rapide de créer une liste puis d’en joindre les éléments.
J’ai benchmarké avec mon radio-réveil.
Stack Overflow avait raison.

4) Par contre, ce que Stack Overflow s’était bien gardé de me dire, c’est que l’affichage en console avait ses propres limites internes.
Bilan, après avoir optimisé mon code du mieux que je le pouvais, j’ai constaté que le script calculait de toutes façon les texte_image plus vite que la console ne pouvait les afficher.

Ce qui relève un peu du FAIL quand on y pense.

Donc, si vous avez une idée pour que ça ne scintille plus au delà de 25 lignes de hauteur, je suis preneur, sachant que je suis tout à fait à même d’entendre que j’ai fait n’importe quoi dès le début.

ÉDIT: Dans les commentaires, Tmonjalo a proposé une solution qui résout le problème du scintillement en faisant revenir le curseur en haut à gauche plutôt que d’effacer la console. J’ai donc édité le code en conséquence. Merci à lui.

La totale.

Voici le script final. Il est diffusé sous les termes de la très sérieuse WTFPL.

Les variables à modifier pour faire des tests sont le seuil, la largeurOut et la hauteurOut.

À noter aussi que si vous faite un petit…

cap = cv2.VideoCapture("VotreFilm.avi")

… au lieu d’ouvrir la webcam en /dev/video0, et bien vous allez voir VotreFilm.avi dans la console. Super génial !

#-*- coding: utf-8 -*-
 
import cv2
import os
 
# Définition du flux capturé.
# Comme elle sera, de toutes façons, retaillée à la baisse,
# elle est fixée à la valeur la plus petite supportée par la webcam.
# À noter que cette valeur minimale peut varier en fonction de votre cam.
largeurIn = 160
hauteurIn = 120
 
# Définition du flux qui s'affichera en console.
# À savoir le nombre de caractères en largeur et en hauteur.
largeurOut = 60
hauteurOut = 20
 
# Seuil de présence des couleurs rouge, vert, bleu dans un pixel. 
# Entre 0 et 255.
seuil = 120
 
# Choix du périphérique de capture.
# Ici /dev/video0
cap = cv2.VideoCapture(0)
 
# Configuration de la définition du flux.
cap.set(3, largeurIn)
cap.set(4, hauteurIn)
 
# On efface la console.
os.system('clear')
 
# On définit une position de référence pour le curseur.
# En haut à gauche, donc, puisqu'on vient juste d'effacer la console.
print ('\033[s')
 
# Pendant... tout le temps...
while True:
 
    # On capture une image.
    ret, img = cap.read()
 
    # On retaille l'image capturée.
    img = cv2.resize(img,(largeurOut, hauteurOut))
 
    # On initialise une liste qui contiendra tous les éléments de l'image
    liste_image = []
 
    # Pour chaque ligne de l'image.
    for ligne in range(hauteurOut):
 
        # Pour chaque colonne de chaque ligne.
        for colonne in range(largeurOut):
 
            # On initialise à 0 l'indice du pixel analysé 
            indice_couleur = 0
 
            # On analyse le niveau de bleu du pixel.
            # S'il est au dessus du seuil...
            if img.item(ligne, colonne, 0) > seuil :
 
                #...on ajoute 4 à l'indice.
                indice_couleur += 4
 
            # On analyse le niveau de bleu du pixel.
            # S'il est au dessus du seuil...
            if img.item(ligne, colonne, 1) > seuil :
 
                #...on ajoute 2 à l'indice.
                indice_couleur += 2
 
            # On analyse le niveau de bleu du pixel.
            # S'il est au dessus du seuil...
            if img.item(ligne, colonne, 2) > seuil :
 
                # ...on ajoute 1 à l'indice.
                indice_couleur += 1
 
            # Si l'indice obtenu est différent de 0...
            if indice_couleur:
 
                # ...on ajoute un █ coloré à la liste.
                liste_image.append(u"\033[3{0}m█".format(indice_couleur))
 
            # Si l'indice est égal à 0...
            if not indice_couleur:
 
                # ...on ajoute un espace (noir ?) à la liste.
                liste_image.append(" ")
 
        # On fait en sorte que le terminal retrouve sa couleur initiale
        liste_image.append("\n\033[00m")
 
    # On produit un string en mettant bout à bout tous les éléments de la liste
    texte_image = ''.join(liste_image)
 
    # On affiche l'image.
    print(texte_image)
 
    # On replace le curseur à la position de référence.
    print ('\033[u')
 
    # On attend 40 millisecondes pour obtenir du 25 images par seconde.
    key = cv2.waitKey(40)

Des vidéos ! Des vidéos !

Voici une petite vidéo réalisée pour promouvoir un événement à nous. À partir de la 44ème seconde, on peut m’y voir coiffé d’un masque de soudeur en train de faire tenir au plafond un donut géant au moyen d’une batte de base-ball en aluminium :

Pour plus d’information sur cet événement vous pouvez allez voir ici et admirer, par la même occasion, notre magnifique affiche réalisée en pur Python.

Enfin, compte tenu des tauliers du site, je ne pouvais passer à côté de la figure imposée.
Je vous propose donc un extrait de Deep 3-bit, hommage appuyé à Vuk Cosic et son légendaire Deep ASCII.

flattr this!

Comment fonctionne HTTP ?

mercredi 18 juin 2014 à 03:48

Plus je fais du dev Web, plus je m’aperçois que beaucoup de mes collègues n’ont aucune idée de comment fonctionne HTTP sous le capot. Comme vous le savez, ma règle numéro 1 c’est qu’il n’y a rien d’évident, donc petit tuto pour expliquer les bases.

On ne va pas rentrer dans les petits détails, juste une petite intro histoire de savoir ce qui se passe derrière ce script PHP ou cette application bottle.

Article long, vous connaissez la chanson.

Et puis c’est dans le ton de l’actu ^^

La logique de client / serveur

(Je me repompe moi-même)

Se balader sur le Web, c’est comme aller au resto. On est un client, on demande quelque chose au serveur, le serveur va voir en cuisine, et revient avec la bouffe, ou une explication sur l’absence de la bouffe (mais jamais pourquoi la bouffe est dégueulasse, allez comprendre):

Schéma du protocole HTTP, en gros

Le protocole HTTP, en (très) gros

Ce cycle requête/réponse se déroule des centaines de fois quand on parcours un site Web. Chaque lien cliqué déclenche une requête GET, et la page suivante ne s’affiche que parce qu’on a reçu la réponse contenant le HTML de celle-ci. Les formulaires, les requêtes AJAX, la mise à jour de vos Tweets sur votre téléphone, tout ça fonctionne sur le même principe.

Donc, sur votre site Web, Firefox, Chrome et les autres vont faire une requête à une machine, votre machine contient un code (si vous êtes dev, votre code :) qui va recupérer cette requête et générer une réponse.

Tout est texte

Généralement, les développeurs utilisent des outils sophistiqués pour écrire des sites Web. Si bien que quand on écrit du code, on ne voit pas vraiment la requête et la réponse, mais des fonctions, des objets, du HTML… Comment ça arrive et comment ça repart est géré automatiquement.

Regardons ce qui se passe quand ce n’est PAS géré automatiquement.

Voici le code d’un petit serveur HTTP écrit en Python 3. Il accepte n’importe quelle requête sur le port 7777 et retourne toujours une page avec marqué “Coucou”.

import asyncio
 
# Le texte de la réponse qu'on va renvoyer
COUCOU = b"""HTTP/1.1 200 OK
Date: Fri, 16 Jun 2014 23:59:59 UTC
Content-Type: text/html
 
<html>
<body>
<h1>Coucou</h1>
</body>
</html>"""
 
# La fonction qui gère chaque requête et qui renvoie une réponse
# pour chacune d'entre elles. La même réponse à chaque fois pour cet
# exemple, mais on peut fabriquer une réponse différente si on veut.
def handle_request(request, response):
    # On lit la requête
    data = yield from request.read(1000000)
    # On affiche son contenu. Surprise, c'est que du texte !
    print(data.decode('ascii'))
    # On écrit notre réponse, que du texte aussi !
    response.write(COUCOU)
    # On ferme la connexion : le protocole HTTP est stateless,
    # c'est à dire qu'il n'y a pas de maintien d'un état
    # côté client ou serveur et chaque requête est indépendante
    # de toutes les autres.
    response.close()
 
if __name__ == '__main__':
    # Machinerie pour faire tourner le serveur :
    # Récupération de la boucle d'événements.
    loop = asyncio.get_event_loop()
    # Création du serveur qui écoute sur le port 7777
    # et qui va appeler notre fonction quand il reçoit
    # une requête.
    f = asyncio.start_server(handle_request, port=7777)
    # Installation du serveur dans la boucle d'événements.
    loop.run_until_complete(f)
    # On démarre la boucle d'événement.
    print("Serving on localhost:7777")
    loop.run_forever()
 
# Si vous êtes dev, vous commencez à comprendre combien
# les libs et frameworks qui gèrent tout ce bordel pour
# vous sont fantastiques.

Si on lance le serveur et qu’on visite la page http://localhost:7777 sur un navigateur, on va alors voir ceci dans le terminal où tourne le serveur :

$ python3 server.py
Serving on localhost:7777
GET / HTTP/1.1
Host: 127.0.0.1:7777
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:30.0) Gecko/20100101 Firefox/30.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
DNT: 1
Connection: keep-alive
Cache-Control: max-age=0

C’est la requête envoyée par notre client, et reçue par notre serveur. “Aller” sur l’adresse veut dire en fait que votre navigateur envoie ce morceau de texte.

Juste après, mon serveur renvoie aussi du texte. Toujours le même dans notre cas :

HTTP/1.1 200 OK
Date: Fri, 16 Jun 2014 23:59:59 UTC
Content-Type: text/html
 
<html>
<body>
<h1>Coucou</h1>
</body>
</html>

Le navigateur l’interprète, et vous fabrique cette page :

Vous contemplez le Web de 1995

Comme vous pouvez le voir, ce n’est que du texte tout simple qui est reçu et envoyé.

Quand quelqu’un développe un site Web, ce qu’il fait vraiment, c’est ça. La plupart du temps il ne le voit pas car ses outils lui facilitent la tâche en analysant le texte et en lui donnant des fonctions et des objets pour le manipuler. Mais derrière, c’est juste du texte.

Quand quelqu’un surf le Web, ce qu’il fait vraiment, c’est ça. Mais le navigateur se charge de le cacher derrière des jolis contrôles, et affiche un résultat bien plus agréable à regarder.

Structure des requêtes

Le texte d’une requête est divisé en 3 parties :

Prenons notre précédente requête d’exemple :

GET / HTTP/1.1
Host: 127.0.0.1:7777
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:30.0) Gecko/20100101 Firefox/30.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
DNT: 1
Connection: keep-alive
Cache-Control: max-age=0

L’action demandée sur la ressource est toujours sur la première ligne :

GET / HTTP/1.1

Elle se divise en 3 parties :

Notez bien que le chemin de la ressource n’a pas à correspondre à un chemin réel d’un fichier sur l’ordinateur. Une ressource est quelque chose de complètement virtuel, quelque chose que mon programme met à disposition selon mes désirs. Si c’est un fichier, très bien, mais ce n’est pas obligatoire, et je peux générer n’importe quelle réponse qui me plait.

En effet, si je vais sur l’adresse http://127.0.0.1:7777/user/monique/profile/, vous notez que mon serveur continue de marcher. Il reçoit simplement la requête :

GET /user/monique/profile/ HTTP/1.1
...

C’est à mon serveur de décider ce qu’il choisit de faire avec le chemin /user/monique/profile/. Ici il est un peu con et continue de renvoyer “coucou”.

Bien, on vient de voir “L’action demandée sur la ressource”, voyons maintenant les headers :

Host: 127.0.0.1:7777
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:30.0) Gecko/20100101 Firefox/30.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
DNT: 1
Connection: keep-alive
Cache-Control: max-age=0

Les headers sont des informations supplémentaires que le client fourni à mon serveur sous la forme :

Nom-De-L-Information: contenu

Les sauts de ligne sont très importants. La première ligne de la requête est “L’action demandée sur la ressource”, puis on met un saut de ligne, ensuite vient un header, puis un saut de ligne, puis un header… Chaque header doit tenir sur une ligne.

En HTTP/1.1, seul le header Host est obligatoire, mais comme aucun header n’était obligatoire en HTTP/1.0, beaucoup de serveurs acceptent l’absence de tout header.

Les headers contiennent généralement des informations sur le client (ex: Accept-Language vous dit quelles langues le client accepte), le contenu de la requête (ex: Content-Length indique la taille de la requête) ou demande un comportement du server (Cache-Control précise comment gérer les ressources qu’on peut mettre en cache).

Pour “Le corps de la requête”, il nous faut ajouter du contenu à notre requête.

Pour cela, faisons une page avec un petit formulaire HTML :

<html>
<head>
    <title>Test post</title>
</head>
<body>
<form method="post" action="http://127.0.0.1:7777/">
<p><input type="test" value="coucou, tu veux voir mon POST ?" name="salut">
<input type="submit"></p>
</form>
</body>
</html>

Ce qui nous donne cette page :

Capture d'écran d'un simple formulaire web sous firefox

Techniquement, on peut poster un formulaire avec une requête GET, mais shut...

Si on active le formulaire, notre serveur affiche cette requête :

POST / HTTP/1.1
Host: 127.0.0.1:7777
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:30.0) Gecko/20100101 Firefox/30.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: fr,fr-fr;q=0.8,en-us;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
DNT: 1
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 41

salut=coucou%2C+tu+veux+voir+mon+POST+%3F

Déjà, vous notez que le verbe d’action a changé : POST / HTTP/1.1.

Ensuite, à la fin de la requête, on laisse une ligne vide, puis on a de nouveau du texte :

salut=coucou%2C+tu+veux+voir+mon+POST+%3F

C’est le corps de la requête.

C’est comme ça que le serveur reçoit le contenu des données des formulaires, et c’est ce qu’on retrouve dans la variable $_POST en PHP ou request.POST en Django

Encore une fois, les outils de programmation évitent au codeur de travailler avec du charabia, et le transforme en quelque chose de facile à comprendre et à manipuler.

Structure des réponses

C’est pareil, ma bonne dame. Reprenons notre exemple de réponse :

HTTP/1.1 200 OK
Date: Fri, 16 Jun 2014 23:59:59 UTC
Content-Type: text/html
 
<html>
<body>
<h1>Coucou</h1>
</body>
</html>

Première ligne, on précise le protocole, puis le code de réponse, qui stipule la nature de votre réponse (tout va bien, une erreur, une redirection, la page n’existe pas, etc).

Ensuite les headers, puis on saute une ligne, et on met le corps de la réponse. Ici, le code HTML qui va donner notre jolie page “Coucou”.

Tout tient là dedans

Tout le reste, toutes les fonctionnalités fantastiques du Web (les liens hypertextes, les fichiers statiques JS et CSS inclus, les cookies, les redirections, le cache, la compression…) ont pour point d’entrée un de ces 3 éléments de la requête ou de la réponse. C’est que c’est un protocole bien foutu.

Enfin, disons, presque tout ? :)


Télécharger le code de l’article

flattr this!