Sam & Max: Python, Django, Git et du cul » python http://sametmax.com Deux développeurs en vadrouille qui se sortent les doigts du code Wed, 05 Feb 2014 14:20:37 +0000 en hourly 1 http://wordpress.org/?v=3.3.1 Python a le don d’Ubiquité : Multiprocessing http://sametmax.com/python-ubiuite-multiprocessing/ http://sametmax.com/python-ubiuite-multiprocessing/#comments Sun, 02 Feb 2014 16:41:23 +0000 foxmask http://sametmax.com/?p=8939

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

Tout récemment j’ai voulu donner un coup de fouet à mon script de traitement de Trigger Happy (que je fais tourner sur ma “raspberry pi” parce que chuis un w4rl0rdZ:P) que j’estimais être trop long dans ses traitements de données.

Actuellement avec Trigger Happy, j’ai 30 sources de données (essentiellement des flux rss), que je parcours, et quand un nouvel item arrive, je l’envoi à pétaouchnock (Evernote ;) Le tout prend 7min, soit 14secondes par source. La loose totale.

Voici le corps du délit :

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import os
import datetime
import time
 
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_th.settings")
from django_th.services import default_provider
from django_th.models import TriggerService
from django.utils.log import getLogger
 
# create logger
logger = getLogger('django_th.trigger_happy')
 
def go():
    """
        run the main process
    """
    trigger = TriggerService.objects.filter(status=True)
    if trigger:
        for service in trigger:
[...]
    else:
        print "No trigger set by any user"
 
 
def main():
    default_provider.load_services()
    # let's go
    go()
 
if __name__ == "__main__":
 
    main()

Mais avant que je ne me penche sur le code du script pour l’améliorer, je me suis dit que plutôt que de chercher à corriger un problème, autant chercher la source de celui-ci d’abord (normal hein).

Un HTOP m’a révélé :

  1. que le CPU était à 100% tout le temps, que le script tourne ou pas
  2. quye la raison était double : rabittmq et celery…

Une fois shootés ces derniers, tout va pour le mieux :P
Je ne dis pas que ceux ci sont de la merde, mais que, pour mon cas, la crontab se suffit à elle-même.

Donc une fois désinstallés c’est 2 (sur)consommateurs de ressources, je relance le script pour tomber à un temps de traitement à 5mn

2014-02-02 14:40:51,693 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - News Sam et Max nothing new
2014-02-02 14:40:53,865 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - News de Numerama nothing new
2014-02-02 14:40:56,013 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux de La Ferme du Web nothing new
2014-02-02 14:41:01,005 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - flux de Paulds nothing new
2014-02-02 14:41:20,098 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux d'un Odieux Connard nothing new
2014-02-02 14:41:22,142 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux de Strict minimum nothing new
2014-02-02 14:41:25,868 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Humeurs Illustrées nothing new
2014-02-02 14:41:33,497 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux du Journalisme Total nothing new
2014-02-02 14:41:35,658 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux de Kernel Panic nothing new
2014-02-02 14:41:44,897 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux de AngularJS nothing new
2014-02-02 14:41:49,016 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux de Odeon nothing new
2014-02-02 14:41:54,186 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux de Nicolargo nothing new
2014-02-02 14:42:12,525 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux de JEEK nothing new
2014-02-02 14:42:21,349 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux de Recher nothing new
2014-02-02 14:42:31,266 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux de Un blog d'adminsys Libres nothing new
2014-02-02 14:42:35,824 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - flux de Le bloc-notes de Gee nothing new
2014-02-02 14:42:36,647 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux de mere code (atom) nothing new
2014-02-02 14:42:39,616 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux de Alex Mac Caw nothing new
2014-02-02 14:42:42,985 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux de Yearofmoo Articles nothing new
2014-02-02 14:43:42,732 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - News Novapost nothing new
2014-02-02 14:43:46,722 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - News Les Numériques nothing new
2014-02-02 14:43:58,303 INFO fire 5142 date 2014-02-02 13:00:00 >= date triggered 2014-02-02 09:02:36 title Test du Quechua Phone 5, le smartphone des montagnards ?
2014-02-02 14:44:08,010 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - News Frandroid = 1 new data
2014-02-02 14:44:17,624 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux de TechCrunch Mobile nothing new
2014-02-02 14:44:20,339 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux Django annonces nothing new
2014-02-02 14:44:20,744 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - La Hyène - Python nothing new
2014-02-02 14:44:24,237 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - News de PCInpact nothing new
2014-02-02 14:44:29,055 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - PointGPhone nothing new
2014-02-02 14:44:31,299 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - HumanCoders Python nothing new
2014-02-02 14:44:52,751 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - HauteFeuille Lab (python) nothing new
2014-02-02 14:44:58,850 INFO fire 5142 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux de Python Plone Planet nothing new

Comme je suis un éternel insatisfait de bibi, j’ai cherché des moyens un peu partout, jusqu’à ce que Sam me souffle une suggestion ;)

A présent donc une version modifiée pour exploiter le multiprocessing est la suivante :

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import os
import datetime
import time
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "th.settings")
 
from django_th.services import default_provider
from django_th.models import TriggerService
from django.utils.log import getLogger
 
# create logger
logger = getLogger('django_th.trigger_happy')
 
def go(service):
    """
        run the main process
    """
    [...]
 
 
def main():
    default_provider.load_services()
    # let's go
    trigger = TriggerService.objects.filter(status=True)
    if trigger:
        from multiprocessing import Pool
        pool = Pool(processes=5)
        result = pool.map(go, trigger)
    else:
        print "No trigger set by any user"
 
if __name__ == "__main__":
 
    main()

fait tomber le temps de traitement à … 1min …:

$ date && ./fire.sh && date 
dimanche 2 février 2014, 14:58:38 (UTC+0100)
2014-02-02 14:58:48,221 INFO fire 5334 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux de La Ferme du Web nothing new
2014-02-02 14:58:48,243 INFO fire 5336 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Humeurs Illustrées nothing new
2014-02-02 14:58:48,256 INFO fire 5337 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux de Kernel Panic nothing new
2014-02-02 14:58:48,283 INFO fire 5333 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - News Sam et Max nothing new
2014-02-02 14:58:48,907 INFO fire 5335 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux d'un Odieux Connard nothing new
2014-02-02 14:58:49,267 INFO fire 5334 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - flux de Paulds nothing new
2014-02-02 14:58:49,446 INFO fire 5336 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux du Journalisme Total nothing new
2014-02-02 14:58:49,713 INFO fire 5333 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - News de Numerama nothing new
2014-02-02 14:58:49,847 INFO fire 5335 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux de Strict minimum nothing new
2014-02-02 14:58:50,209 INFO fire 5334 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux de Odeon nothing new
2014-02-02 14:58:50,353 INFO fire 5337 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux de AngularJS nothing new
2014-02-02 14:58:50,830 INFO fire 5333 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux de Un blog d'adminsys Libres nothing new
2014-02-02 14:58:51,338 INFO fire 5336 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux de JEEK nothing new
2014-02-02 14:58:51,396 INFO fire 5334 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux de Nicolargo nothing new
2014-02-02 14:58:51,476 INFO fire 5337 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux de Yearofmoo Articles nothing new
2014-02-02 14:58:51,735 INFO fire 5333 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - flux de Le bloc-notes de Gee nothing new
2014-02-02 14:58:52,148 INFO fire 5335 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux de mere code (atom) nothing new
2014-02-02 14:58:52,640 INFO fire 5336 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux de Recher nothing new
2014-02-02 14:58:52,971 INFO fire 5335 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux de Alex Mac Caw nothing new
2014-02-02 14:58:53,416 INFO fire 5334 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - News Les Numériques nothing new
2014-02-02 14:58:53,474 INFO fire 5333 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux de TechCrunch Mobile nothing new
2014-02-02 14:58:53,870 INFO fire 5337 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - News Novapost nothing new
2014-02-02 14:58:54,072 INFO fire 5335 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - PointGPhone nothing new
2014-02-02 14:58:54,316 INFO fire 5333 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux Django annonces nothing new
2014-02-02 14:58:54,853 INFO fire 5336 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - La Hyène - Python nothing new
2014-02-02 14:58:55,111 INFO fire 5335 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - HumanCoders Python nothing new
2014-02-02 14:58:55,222 INFO fire 5334 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - News Frandroid nothing new
2014-02-02 14:58:55,380 INFO fire 5337 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - HauteFeuille Lab (python) nothing new
2014-02-02 14:58:55,696 INFO fire 5336 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - News de PCInpact nothing new
2014-02-02 14:59:02,214 INFO fire 5337 user: foxmask - provider: ServiceRss - consummer: ServiceEvernote - Flux de Python Plone Planet nothing new
dimanche 2 février 2014, 14:59:02 (UTC+0100)

Comme on l’aura remarqué la différence entre les 2 versions est l’appel fait à a fonction go

avant :

def main():
    default_provider.load_services()
    # let's go
    go()

après :

def main():
    default_provider.load_services()
    # let's go
    trigger = TriggerService.objects.filter(status=True)
    if trigger:
        from multiprocessing import Pool
        pool = Pool(processes=5)
        result = pool.map(go, trigger)
    else:
        print "No trigger set by any user"

du coup l’appel de la fonction “go” implique de changer sa signature en lui filant comme argument “trigger” (le QuerySet de l’appli Django)

A présent donc pool.map fait l’itération des données trouvées dans le modele TriggerService et exécute tout le tintouin. *<:o)

ps : @Sam : chose promise chose dûe ;)

flattr this!

]]>
http://sametmax.com/python-ubiuite-multiprocessing/feed/ 6
Un objet proxy : ce que c’est et à quoi ça sert http://sametmax.com/un-objet-proxy-ce-que-cest-et-a-quoi-ca-sert/ http://sametmax.com/un-objet-proxy-ce-que-cest-et-a-quoi-ca-sert/#comments Sat, 01 Feb 2014 17:18:13 +0000 Sam http://sametmax.com/?p=8076 autre objet en paramètre et le sauvegarde dans un de ses attributs. Quand on appelle les méthodes du proxy, le proxy appelle la même méthode de l'objet qu'il a en attribut, et retourne le résultat. Quand on set/get/delete un attribut du proxy, il fait la même chose sur l'autre objet.]]> Un objet proxy est un objet qui prend un autre objet en paramètre et le sauvegarde dans un de ses attributs. Quand on appelle les méthodes du proxy, le proxy appelle la même méthode de l’objet qu’il a en attribut, et retourne le résultat. Quand on set/get/delete un attribut du proxy, il fait la même chose sur l’autre objet.

Voilà une implémentation très basique d’un objet proxy en Python :

class Proxy(object):
 
    def __init__(self, obj):
        # L'objet passé en paramètre est
        # sauvegardé dans un attribut.
        # On le fait en utilisant 
        # object.__setattr__, qui est le
        # __setattr__ du parent, et non directement
        # en faisant self._obj = obj
        # afin d'éviter une boucle infinie car
        # nous écrasons __setattr__ plus bas.
        object.__setattr__(self, "_obj", obj)
 
    # On écrase les méthodes magiques __getattribute__
    # (qui est appelée quand on faire self.nom_attribut), 
    # __delattr__ (qui est appelée quand on fait 
    # del self.nom_attribut) et __setattr__ (qui est 
    # appelée quand on fait self.nom_attribut = truc)
    def __getattribute__(self, name):
        return getattr(object.__getattribute__(self, "_obj"), name)
    def __delattr__(self, name):
        delattr(object.__getattribute__(self, "_obj"), name)
    def __setattr__(self, name, value):
        setattr(object.__getattribute__(self, "_obj"), name, value)

Ca s’utilise comme ceci :

class UnObjetOrdinnaire(object):
 
    attribut = 'VALEUR !'
 
    def methode(self, param):
        return param * 2
 
 
objet_ordinnaire = UnObjetOrdinnaire()
# on passe l'objet ordinnaire au proxy
objet_proxy = Proxy(objet_ordinnaire)
 
# Accéder à des méthodes et attribut
# du proxy accède à ceux de l'objet
# derrière le proxy
print(objet_proxy.attribut)
## VALEUR !
 
print(objet_proxy.methode(3))
## 6
 
# Modifier l'objet proxy modifie
# l'objet derrière le proxy
objet_proxy.attribut = 'une autre valeur'
 
print(objet_ordinnaire.attribut)
## une autre valeur

Pour un exemple vraiment à l’épreuve des balles, il faut prendre en compte tout un tas de cas particuliers, ce qui fait qu’il est bien plus rentable d’utiliser une lib solide pour ça.

Attention, un objet proxy peut très bien avoir des méthodes qui n’appellent pas celles de l’objet derrière. On même avoir des méthodes qui appellent des méthodes qui n’ont pas le même nom, ou plusieurs méthodes… Ce que vous voyez en exemple est un proxy très basique.

Pourquoi voudrait-on obtenir ce résultat ?

Plusieurs design patterns font appel à des objets proxy. Par exemple, le design pattern “adapter” consiste à créer un objet proxy qui accepte plusieurs types d’objets en paramètres afin de présenter toujours la même interface.

Imaginez que vous ayez plusieurs objets qui servent à s’authentifier :

 
class Login(object):
 
    _is_logged = False 
 
    def is_logged(self):
        return self._is_logged
 
    def login(self, email, password):
        self._is_logged = True
 
 
class LoginWithUsername(object):
 
    _is_logged = False 
 
    def is_logged(self):
        return self._is_logged
 
    def login(self, username, password):
        self._is_logged = True
 
 
class SignIn(object):
 
    _is_logged = False 
 
    def is_logged(self):
        return self._is_logged
 
    def signin(self, email, password):
        self._is_logged = True
 
 
class LoginWithKey(object):
 
    _is_logged = False 
 
    key = "jfjkdlmqfjdmqsjdk"
 
    def is_logged(self):
        return self._is_logged
 
    def login(self, username):
        self._is_logged = True

Et vous avez un algo de parsing, qui attend un objet d’authentification. Si vous mettez le code qui permet de choisir la bonne API dans l’algo de parsing, vous liez l’algo (qui n’a rien à voir avec l’authentification) à toutes ces implémentations.

Une des manières de faire, est d’utiliser un adaptateur, dont voici une esquisse :

class AuthAdapter(object):
 
 
    def __init__(self, obj):
        object.__setattr__(self, "_obj", obj)
 
    def __getattribute__(self, name):
 
        try:
            # Si l'attribut existe sur le proxy, on l'utilise
            return object.__getattribute__(self, name)
        except AttributeError:
            # Sinon on tente le coup sur l'objet derrière le proxy
            return getattr(object.__getattribute__(self, "_obj"), name)
 
 
    def login(self, id_=None, secret=None):
        # Le login est différent pour chaque classe,
        # donc on s'arrange avec.
        try:
            self._obj.login(id_, secret)
        except AttributeError:
            self._obj.signin(id_, secret)
        except TypeError:
            self._obj.login(id_)

En gros, si on essaye d’appeler login(), il va lisser les contours et nous donner toujours la même interface, même si derrière l’objet peut marcher complètement différement. En revanche, si on appelle n’importe quel autre attribut ou méthode (par exemple is_logged, mais il pourrait y en avoir des dizaines d’autres dans la vrai vie vivante), ça tape directement dans l’objet derrière le proxy.

Donc si j’applique l’adaptateur systématiquement, quelle que soit la classe derrière, le comportement est toujours le même : j’appelle login(un id, un secret), et il se logge.

all_auth = (Login, LoginWithUsername, SignIn, LoginWithKey)
 
for auth in all_auth:
    # 'auth' est une des 4 classes de login. On l'instancie et
    # on met l'instance derrière le proxy
    auth = AuthAdapter(auth())
    print("Testing '%s'" % auth.__class__.__name__)
    print("Is logged : %s" % auth.is_logged())
    # Le login se passe toujours de la même manière, quelle que soit la classe
    auth.login('id', 'secret')
    print("Is logged after logging : %s" % auth.is_logged())

Comme d’habitude, ceci est un exemple naval. Il est bateau quoi. Mais cela vous démontre le principe.

Le design pattern façade ressemble à l’adapter, en fait c’est une spécialisation de l’adapter. Il s’agit juste d’exposer une interface plus simple, de l’objet derrière le proxy.

Un proxy peut aussi servir à hacker une lib. Par exemple, la lib attend un objet d’une ancienne version d’une autre lib dont l’auteur a déprécié un attribut. Avec un proxy, vous pouvez toujours faire semblant que l’attribut est toujours là : enrobez l’objet dans un proxy qui possède cet attribut, tout le reste de l’API sera la même.

Un proxy peut également être utile si vous voulez effectuer des actions quand l’objet est manipulé.

import logging
 
class ProxyLogger(object):
 
    def __init__(self, obj):
        object.__setattr__(self, "_obj", obj)
 
    def __getattribute__(self, name):
        obj = object.__getattribute__(self, "_obj")
        # On lance un warning à chaque accès à un attribut de l'objet
        # derrière le proxy
        logging.warning("%s.%s has been called" % (obj.__class__.__name__, name))
        return getattr(obj, name)

Maintenant, supposons que vous avez un objet d’une lib externe (que vous ne pouvez donc pas modifier) sur lequel vous avez besoin d’infos :

class ObjetExterne(object):
    def ahahah(self):
        pass
 
o = ProxyLogger(ObjetExterne())
o.ahahah()
## WARNING:root:ObjetExterne.ahahah has been called

Vous pouvez refiler le proxy à n’importe quel objet de sa lib d’origine, si le proxy est bien fait, elle ne vera pas la différence.

On finit sur une note culture, puisque le pattern decorator utilise souvent aussi un proxy. Cette fois d’une fonction sur un autre objet (en générale une autre fonction). Mais le principe est le même. Donc quand vous voyez @un_decorateur, il est peut être en train d’appliquer un proxy à la fonction.

D’ailleurs on dit souvent que le proxy décore l’objet qui est derrière.

flattr this!

]]>
http://sametmax.com/un-objet-proxy-ce-que-cest-et-a-quoi-ca-sert/feed/ 1
Python, meilleur nul part, excellent partout http://sametmax.com/python-meilleur-nul-part-excellent-partout/ http://sametmax.com/python-meilleur-nul-part-excellent-partout/#comments Fri, 31 Jan 2014 13:57:51 +0000 Sam http://sametmax.com/?p=8328 Je ne sais pas si vous l’aviez compris, mais j’aime bien Python. J’ai essayé de vous le cacher jusqu’ici, mais je sais que mon secret ne tiendra pas longtemps, alors j’avoues tout.

Je n’aime pas Python parce que c’est le meilleur langage pour X. Je ne pense pas que Python soit le meilleur langage pour quoi que ce soit. Regex ? Perl est meilleur ? IA ? Lisp est meilleur. Embarqué ? C est meilleur. Asynchrone ? Javascript est meilleur.

Non. J’aime Python parce qu’il est très bon à X. Presque tous les X. En fait, il existe très peu de X pour lequel Python n’est pas bon.

Python est excellent pour analyser du texte, pour faire un site Web (même asynchrone), de l’administration système, des interfaces graphiques, du calcul scientifique, de la 3D, des crawlers, etc. Pas le meilleur. Mais très bon.

Et ceci est fantastique. Cela veut dire que si je commence un projet, n’importe quel projet, je sais que je peux le commencer en Python. Je ne serai pas bloqué plus tard par tel ou tel besoin. Je sais que je pourrai m’en sortir agréablement avec Python.

Vous faites un site en JS ? Pour le sysadmin, il va falloir un autre langage.

Des scripts de maintenance en Perl ? J’espère que vous n’aurez pas besoin d’une UI.

Un crawler en PHP ? Pour l’analyse des données récupérer, vous aller vous amuser…

De l’extraction de data en Ruby ? Pourvu qu’il ne faille pas traiter plusieurs To.

Ce n’est pas que vous ne pourrez pas. Ils sont tous Turring complet après tout. Mais vous allez vraiment galérer.

Aucun langage n’est parfait, et Python n’est pas une exception.

Mais ce qui est génial, c’est que dans 90% des cas, vous pouvez avoir une solution propre et efficace en Python au problématique que vous ne savez même pas encore que vous aurez. Et le jour où vous atteindrez la limite, alors oui, vous pourrez utiliser une autre techno pour le faire. Mais juste pour cette partie. La plupart du temps ce sera une partie très réduite. En en plus, Python pourra probablement s’interfacer nativement avec le code de l’autre langage.

Il y a des tas de raisons de ne pas choisir Python bien entendu, mais probablement la meilleure raison de le choisir, c’est qu’il y a peu de chance de se planter.

Bon, évidement si vous voulez écrire un driver de carte graphique…

flattr this!

]]>
http://sametmax.com/python-meilleur-nul-part-excellent-partout/feed/ 26
Solution de l’exercice d’hier sur shadow http://sametmax.com/solution-de-lexercice-dhier-sur-shadow/ http://sametmax.com/solution-de-lexercice-dhier-sur-shadow/#comments Wed, 29 Jan 2014 15:25:53 +0000 Sam http://sametmax.com/?p=8917 Ça va de soit, mais ça va mieux en le disant, ceci n’est pas la solution unique de l’exercice d’hier, mais une solution possible parmi d’autres.

On note l’usage de crypt, qui évite de se faire chier à trouver le bon algo de hashing et gère le salt automatiquement. spwd, c’est vraiment pour le grosses larves comme moi qui veulent même pas faire un split.

Et c’est du Python 3, yo dog !

import io
import os
import crypt
import spwd
 
from urllib.request import FancyURLopener
from zipfile import ZipFile
 
PASSWORDS_SOURCE = "http://xato.net/files/10k%20most%20common.zip"
PASSWORDS_LIST = '10k most common.txt'
 
# Le fichier ZIP est derrière cloudflare, qui vous ferme la porte au nez si
# vous n'avez pas de User-Agent. On va donc créer un UrlOpener, un objet qui
# ouvre des ressources en utilisant leurs URLs, qui a un User-Agent 'TA MERE'.
# CloudFlare ne check pas que le UA est valide.
class FFOpener(FancyURLopener):
   version = 'TA MERE'
 
# Si le dictionnaire de passwords n'est pas là, on le télécharge
# via FFOpener().open(PASSWORDS_SOURCE).read(). C'est verbeux, c'est urllib.
# Normalement je ferais ça avec requests. Ensuite on lui donne une interface
# file-like object avec io.BytesIO pour que ZipFile puisse le traiter en mémoire
# sans avoir à le sauvegarder dans un vrai fichier sur le disque, et on
# extrait le ZIP.
if not os.path.isfile(PASSWORDS_LIST):
    ZipFile(io.BytesIO(FFOpener().open(PASSWORDS_SOURCE).read())).extractall()
 
# On extrait les mots de passe de la liste sous forme de tuple car c'est rapide
# à lire. Un petit rstrip vire les sauts de ligne.
passwords = tuple(l.rstrip() for l in open(PASSWORDS_LIST))
 
# spwd.getspall() nous évite de parser le fichier shadow à la main.
for entry in spwd.getspall():
    print('Processing password for user "%s": ' % entry.sp_nam, end='')
 
    # Pas de hash ? On gagne du temps avec 'continue'
    if not '$' in entry.sp_pwd:
        print('no password hash to process.')
        continue
 
    # On teste chaque password avec la fonction crypt, qui accepte en deuxième
    # paramètre le hash du mot de passe complet. Pas besoin de se faire chier
    # à le spliter, il va analyser les '$' et se démerder avec ça. On a juste
    # à comparer le résultat avec le hash d'origine.
    for pwd in passwords:
        if crypt.crypt(pwd, entry.sp_pwd) == entry.sp_pwd:
            print('password is "%s".' % pwd)
            # On break pour gagner quelques tours de boucles, et pouvoir
            # utiliser la condition 'else'.
            break
    else:
        print('fail to break password.')

Télécharger le code.

flattr this!

]]>
http://sametmax.com/solution-de-lexercice-dhier-sur-shadow/feed/ 9
C’est l’heure de faire de l’exercice http://sametmax.com/cest-lheure-de-faire-de-lexercice/ http://sametmax.com/cest-lheure-de-faire-de-lexercice/#comments Tue, 28 Jan 2014 16:19:48 +0000 Sam http://sametmax.com/?p=8910 Le dernier exercice avait bien été apprécié, alors je remet ça.

Consigne :


Créer un script de brute force de passwords Unix par dictionnaire.

Puisqu’on est pas non plus des Kevin Mitnick en puissance, on va supposer que vous êtes connectés sur la machine, que vous avez les droits root dessus et que vous avez localisé les mots de passe comme étant dans “/etc/shadow”.

Votre script va vérifier si il possède un dictionnaire de mots de passe. Si ce n’est pas le cas, il va télécharger celui-ci et le décompresser : http://xato.net/files/10k%20most%20common.zip.

Ensuite vous parcourez le fichier shadow, et vous essayez de trouver quel mot de passe se cache derrière chaque hash. Si il n’y a pas de hash, vous pouvez ignorer l’utilisateur.

Exemple de sortie:

Processing password for user "root": no password hash to process.
Processing password for user "daemon": no password hash to process.
Processing password for user "bin": no password hash to process.
Processing password for user "sys": no password hash to process.
Processing password for user "www-data": no password hash to process.
Processing password for user "sam": fail to break password.
Processing password for user "test": password is "cheese".
Processing password for user "messagebus": no password hash to process.
Processing password for user "avahi-autoipd": no password hash to process.
Processing password for user "avahi": no password hash to process.
...

Afin de simplifier l’exercice, il n’est pas demandé de gestion d’erreur ou de passage en a paramètre du script.

Comme d’habitude, il n’y a pas de solution ultimate de la mort qui tue, c’est juste pour le fun.

La soluce demain.

flattr this!

]]>
http://sametmax.com/cest-lheure-de-faire-de-lexercice/feed/ 28
Cog, l’anti langage de template Python http://sametmax.com/cog-lanti-langage-de-template-python/ http://sametmax.com/cog-lanti-langage-de-template-python/#comments Mon, 27 Jan 2014 17:45:38 +0000 Sam http://sametmax.com/?p=8903 Cog est un outil Python en ligne de commande qui permet d'insérer du code Python dans un fichier, afin qu'il génère une partie de ce fichier]]> Cog est un outil Python en ligne de commande qui permet d’insérer du code Python dans un fichier, afin qu’il génère une partie de ce fichier.

A priori, ça ressemble à un langage de template. Là où ça diffère, c’est que cog ne cherche pas à générer un fichier différent, il insère le code généré dans le fichier original, et garde le code de génération.

Exemple, vous avez envie d’insérer un warning en haut de plusieurs fichiers de code. Vos choix :

  • Copier-coller le warning.
  • Créer un script de build qui insère le warning.

L’alternative des bricoleurs qui ont juste besoin de quelques insertions, c’est Cog. Par exemple, votre fichier contient :

# [[[cog for l in open('warning.txt'): cog.out("# " + l) ]]]
# [[[end]]]
 
import vostrucs
 
vostrucs.faire_vos_machins()

Après un cog.py -r votre_fichier.py, votre fichier sera :

# [[[cog for l in open('warning.txt'): cog.out("# " + l) ]]]
# Attention, ceci est un avertissmement super important.
# Ce logiciel ne vient avec aucune garantie. Il vomira dans votre salon.
# Violera votre femme, lui collera l'hépatite C et offrira un CD de one direction
# à vos enfants.
# [[[end]]]
 
import vostrucs
 
vostrucs.faire_vos_machins()

cog vient avec plusieurs options, par exemple la possibilité de retirer le code de génération ou de lire le précédent texte généré depuis le code de génération.

La véritable force de l’outil c’est qu’il peut utiliser n’importe quel module Python, et donc générer du texte à partir d’un contenu en ligne ou un fichier CSV. Il est d’ailleurs né parce que l’auteur était codeur C et avait besoin de générer du code à partir d’un fichier XML.

Le seul défaut de Cog, c’est que c’est verbeux. Et moche.

flattr this!

]]>
http://sametmax.com/cog-lanti-langage-de-template-python/feed/ 4
Wheel installs require setuptools >= 0.8 for dist-info support. http://sametmax.com/wheel-installs-require-setuptools-0-8-for-dist-info-support/ http://sametmax.com/wheel-installs-require-setuptools-0-8-for-dist-info-support/#comments Wed, 22 Jan 2014 19:24:51 +0000 Sam http://sametmax.com/?p=8745 Je ne sais pas pourquoi, mais une fois j’ai eu cette erreur en essayant d’installer un truc avec pip.

La solution est de lancer :

sudo pip install setuptools --no-use-wheel --upgrade

Et on peut réutiliser pip sans problème.

Zarb.

flattr this!

]]>
http://sametmax.com/wheel-installs-require-setuptools-0-8-for-dist-info-support/feed/ 2
La fin du mystère du binaire (nananère) http://sametmax.com/la-fin-du-mystere-du-binaire-nananere/ http://sametmax.com/la-fin-du-mystere-du-binaire-nananere/#comments Tue, 21 Jan 2014 15:01:56 +0000 Sam http://sametmax.com/?p=8808 Quand mon voisin de table en cours de SVT a commencé à m’expliquer le binaire (il programmait en assembleur sur sa TI89), j’ai pas bien pigé. Des années plus tard, en cours d’archi, un prof nous a fait un cours magistral sur la question. Je n’ai définitivement rien pigé. J’ai regardé des tutos sur le net. RIEN P-I-G-É.

J’ai mis presque 10 ans (littéralement) avant que mon cerveau tilte. Non pas que je n’avais pas compris le principe, mais le détails, comment on faisait la conversion, ce qu’on pouvait faire avec, etc… Ca me passait au dessus de la tête.

Je râle beaucoup sur le manque de pédagogie d’autrui, et après tout, d’autres ont compris le truc à partir de ces explications. Mais je vais me répéter : leurs explications étaient merdiques.

Le binaire c’est simple. Tout le monde peut le comprendre.

Si vous ne pigez pas, c’est que le mec en face de vous est une grosse pine.

Par contre, il y a beaucoup de choses à savoir, alors à long article, loooooooooooongue musique :

Long article car j’ai vu des connards mélanger tout et n’importe quoi dans leurs explications du binaire, et je vais devoir démêler le sac de nœuds qu’ils ont fait dans votre tête.

Pourquoi apprendre le binaire ?

Pour le sport, essentiellement. La culture G. Parce qu’en toute honnêteté, aujourd’hui, ça ne sert plus à grand chose, tellement vous avez de couches d’abstraction entre les manipulations binaires et vous.

Il y a toujours quelques grincheux qui vous diront des trucs de grincheux comme “oui, mais pour comprendre les erreurs de flottant en arithmétique, il faut comprendre le binaire” comme mon père qui me disait qu’il fallait apprendre le latin au lycée. Meh.

D’abord, on a pas plus besoin d’apprendre le binaire pour comprendre les erreurs de flottant qu’on a besoin de comprendre la composition moléculaire de l’essence pour savoir pourquoi une voiture tombe en panne quand il y en a plus. Ensuite, vous n’avez même pas besoin de comprendre pourquoi il y a des erreurs de flottant, juste à savoir comment les éviter (ce que des tas de gens “qui les comprennent” ne sont pas foutu de faire).

Bref, c’est l’éternel bataille de l’arrière garde qui veut défendre le savoir aujourd’hui obsolète – qu’il a accumulé parce qu’a son époque c’était indispensable – à grand renfort de pédanterie. On voit ça dans tous les domaines. Généralement avec force exemples de niches et enculage de mouche.

Mais au delà de ça, le binaire est encore utilisé dans certains domaines, comme la programmation de micro-contrôleurs (si vous planifiez de bosser chez Intel, Nvidia ou la NASA, ça peut servir), la cryptographie ou la programmation de calculatrices en cours de bio. Ce dernier point étant semble-t-il, un sujet intemporel qui ne se démode jamais.

Néanmoins ça reste 0.00001 % de l’informatique actuelle. Vous n’aurez pas besoin du binaire pour vos tâches de Sys Admin, vos GUI, votre app mobile, votre site Web, votre algo de simulation, etc. Bref, les trucs pour lesquels les gens sont payés dans les années 2000.

Maintenant que j’ai bien été désagréable avec une partie du lectorat, précisons tout de même que savoir 2, 3 trucs sur le binaire, ça ne fait pas de mal. Vous êtes informaticiens, ça fait un peu partie du folklore.

Valeur et représentation

En informatique, il y a le problème sans fond de la dualité valeur / représentation. L’écriture des nombres est un exemple parfait de cela.

Par exemple, j’ai ce nombre d’étoiles :

* * * * *

Il y a la valeur du nombre d’étoiles. Cette valeur ne peut pas être manipulée, elle existe, c’est tout. On ne peut que constater le nombre d’étoiles en interprétant ce que l’on voit.

Si on veut manipuler cette valeur (parler d’une valeur, c’est déjà manipuler la valeur), il faut lui donner une représentation.

Les arabes nous ont donné une représentation : 5.

Les romains avaient une autre représentation : V.

La valeur derrière ces deux représentations est strictement la même.

Le binaire est juste une autre représentation. Il n’y a rien de magique derrière le binaire.

Par exemple, la valeur que l’on représentante par 5, se représente ainsi en binaire : 101.

C’est la même valeur de nombre d’étoiles. Juste écrit différemment. Cela n’a aucun impact sur la réalité, ne change rien sur la nature des étoiles ou la valeur manipulée. C’est une manière de transmettre une information.

A la base de la base

La raison pour laquelle on s’embrouille facilement avec le binaire, c’est que la plupart d’entre nous ont appris à lire les nombres par cœur. On lit un nombre comme un tout.

En vérité, un nombre est une notation qui obéit à des règles très strictes, et non seulement chaque chiffre donne une information, mais sa place dans le nombre donne une information. Ce qu’on appelle “centaine”, “millier”, etc, sont des notions qu’on manipule sans y penser, mais derrière, il y a en fait un système.

Le système des bases.

Quand on utilise les chiffres arabes pour écrire un nombre, on utilise une série de symboles, et on les aligne dans un ordre précis. Selon la base utilisée, l’ordre implique une valeur différente.

On ne s’en rend pas compte, mais on utilise tous les jours l’ordre de la base 10. On pense en base 10. On a tellement l’habitude de tout calculer en base 10 qu’on ne se rend pas compte qu’on suit une règle précise pour cela.

Que signifie le fait d’utiliser une base 10 ?

Deux choses.

La première, c’est que l’on a DIX symboles pour représenter DIX valeurs. On ne sait représenter directement que ces DIX valeurs là.

          0
*         1
**        2
***       3
****      4
*****     5
******    6
*******   7
********  8
********* 9

On a des valeurs (un nombre d’étoiles), et on fait correspondre complètement arbitrairement dix symboles pour ces valeurs. On connait ces symboles par cœur, ce sont des conventions. Ils ne signifient rien par eux-même, nous leur avons donné cette signification.

Ça c’est la première règle. La deuxième règle, c’est que quand on tombe à cours de symboles parceque la valeur est trop grande, on utilise la position des symboles pour dire combien de groupes de dizaines de symboles il y a.

Par exemple avec ce nombre :

10

10 veut dire (en lisant de droite à gauche, ce sont des chiffres arabes, je le rappelle), 0 unité, et 1 groupe d’une dizaine.

Si on a 587, on dit (toujours de droite à gauche) : il y a 7 unités et 8 groupes d’une dizaine et 5 groupes d’une dizaine de dizaines.

La position du chiffre dit si l’on parle d’un groupe de 1, de 10, de 10 groupes de 10 (cent), de 10 groupes de 10 groupes de 10 (1000), etc.

C’est pour ça qu’on parle de base 10. Tout est en groupe de 10. On fait des paquets de 10. Puis des paquets de 10 paquets de 10. Puis des paquets de 10 paquets de 10 paquets de 10…

Exemple avec le nombre 1982 :

[1][9][8][2]
 |  |  |  |
 |  |  |  2 unités
 |  |  8 groupes de 10
 |  9 groupes de 10 groupes de 10 (10 x 10 = 100)
1 groupe de 10 groupes de 10 groupes de 10 (10 x 10 x 10 = 1000)

Si on lit un chiffre comme un tableau en informatique (donc en commençant par 0), mais de droite à gauche, on peut utiliser les puissances pour noter ça de manière propre :

[1][9][8][2]
 |  |  |  |
 |  |  |  2 est à la place 0, sa "valeur" est 2 x 10^0 = 2 * 1 = 2
 |  |  8 est à la place 1, sa "valeur" est 8 x 10^1 = 8 * 10 = 80
 |  9 est à la place 2, sa "valeur" est 9 x 10^2 = 9 x 10 x 10 = 9 x 100 = 900
1 est à la place 3, sa "valeur" est 1 x 10^3 = 1 x 10 x 10 x 10 = 1 x 1000 = 1000

Ça vous parait évident sans avoir à faire le calcul ? C’est parce que vous êtes tellement habitué à compter en base 10 que c’est automatique pour vous. Pourtant, en base 2, c’est exactement la même chose.

On a DEUX symboles pour représenter DEUX valeurs. On ne sait représenter que ces DEUX valeurs là.

  0
* 1

Quand on tombe à cours de symbole parce qu’une valeur est trop grande, on utilise la position des symboles pour dire combien de groupes de deux symboles il y a.

Par exemple avec ce nombre :

10

10, qui ne se prononce PAS dix, veut dire (en lisant de droite à gauche), 0 unité, et 1 groupe d’une paire. Donc deux en base 10 :-).

Pour les chiffres plus longs, c’est kiff, kiff :

[1][1][1][0]
 |  |  |  |
 |  |  |  0 unité
 |  |  1 groupes de 2
 |  1 groupes de 2 groupes de 2 (2 x 2 = 4 en base 10)
1 groupe de 2 groupes de 2 groupes de 2 (2 x 2 x 2 = 8 en base 10)

Donc en base 10 : 8 + 4 + 2, soit 14.

Si on le représente en puissance de 2 :

[1][1][1][0]
 |  |  |  |
 |  |  |  0 est à la place 0, sa "valeur" est 0 x 2^0 = 0 * 1 = 0
 |  |  1 est à la place 1, sa "valeur" est 1 x 2^1 = 1 * 2 = 2
 |  1 est à la place 2, sa "valeur" est 1 x 2^2 = 1 x 2 x 2 = 1 x 4 = 4
1 est à la place 3, sa "valeur" est 1 x 2^3 = 1 x 2 x 2 x 2 = 1 x 8 = 8

En gros pour convertir du binaire en base 10, il suffit de réciter les puissances de deux dans sa tête :

2, 4, 8, 16, 32… (2 fois 2 = 4, 2 fois 4 = 8, 2 fois 8 = 16, 2 fois 16 = 32…)

Et les appliquer de droite à gauche :

[1][0][1][1][1][0] => [1 * 32] + [0 * 16] + [1 * 8] + [1 * 4] + [1 * 2] + [O * 1] => 46 en base 10

Une illustration graphique ?

Voici quarante-six étoiles :

**********************************************

Si je les représente en base 10, je noterais ça 46, soit 4 groupes de dizaines, et 6 unités :

    4          6

**********   ******
**********
**********
**********

Si je le représente en base 2, je le note 101110: soit 1 groupe de trente-deux, 0 groupe de seize, 1 groupe de huit, 1 groupe de quatre, 1 groupe de deux, et 0 unité.

                1                  0      1        1     1    0

********************************   -   ********   ****   **   -

On peut faire ça avec n’importe quelle base. On peut compter en base 3 (ternaire) ou en base 4, 5, 6… On utilise notamment parfois en informatique la base 8 (octale) et la base 16 (hexadécimale). Pour cette base, il y a 16 symboles : 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F, représentant chacun une valeur, puis, quand on a plus de symbole, on utilise la position pour signaler un paquet de 16.

Passons au code

En Python on peut écrire un nombre en binaire en le préfixant de ’0b’:

>>> 0b101110
46

Mais par défaut Python représente toujours à l’affichage ce nombre en base 10. La valeur derrière est la même de toute façon (qui, de manière amusante, est sockée en binaire).

Si vous voulez forcer l’affichage du nombre en binaire, vous pouvez utilisez bin() ou format() :

>>> 'Le nombre {0} en binaire donne : {0:b}'.format(46)
u'Le nombre 46 en binaire donne : 101110'
 
>>> bin(46)
'0b101110'

A l’inverse, si vous avez une représentation textuelle d’un chiffre binaire, vous pouvez préciser la base via le second paramètre de int() :

>>> int('101110') # la base 10 est le réglage par défaut...
101110
>>> int('101110', 10) # ...donc on récupère cent un mille cent dix.
101110
>>> int('101110', 2) # si on précise la base 2, on récupère quarante-six.
46

Mais si on n’a pas codé sa petite fonction pour faire pareil à la main à 50 ans, on a raté sa vie. Donc, voici un exemple simple d’une fonction qui convertit une représentation textuelle d’un chiffre binaire en int Python :

def bin2dec(s):
 
    # on rejette les chaînes vides
    if not s:
        raise ValueError('The string cannot be empty')
 
    total = 0
 
    # on itère sur la chaîne, de droite à gauche
    reversed_number = s[::-1]
    for pos, num in enumerate(reversed_number):
 
        # on multiplie le chiffre en cours par la puissance de 2
        # qui correspond à sa position
        total += int(num) * (2 ** pos)
 
    return total
 
print(bin2dec('0'))
print(bin2dec('1'))
print(bin2dec('10'))
print(bin2dec('101'))
print(bin2dec('10011100'))
 
## 0
## 1
## 2
## 5
## 156

Et la fonction inverse :

 
from __future__ import division # pour être tous d'accord sur la division entière
 
def dec2bin(i):
 
    numbers = []
 
    if i == 0:
        return i
 
    # On divise (avec la division entière) par deux jusqu'à ce qu'il ne reste
    # plus rien à diviser (on fait des paquets de 2, quoi).
    # Avant chaque division par deux, on regarde si il y aura un reste
    # pour la prochaine division. Si oui, on met 1 pour le paquet de 2 actuel,
    # sinon on met 0.
    # C'est comme pour la multiplication à la main ("je met 2 et je retiens 1"),
    # mais à l'envers.
    while i != 0:
        numbers.append(str(i % 2))
        i = i // 2
 
    # Ensuite on inverse tout ça et on join() tout en une belle string
    return (''.join(numbers[::-1]))
 
print(dec2bin(0))
print(dec2bin(1))
print(dec2bin(2))
print(dec2bin(5))
print(dec2bin(156))
 
## 0
## 1
## 10
## 101
## 10011100

Et les opérateurs bitwise ?

Dans les tutos on voit souvent mélanger la notion de binaire, avec la représentation des nombres, du texte, et les opérateurs spécialisés dans la manipulation du binaire :

|, ^, ~, >>, <<, >>>, &, etc

C’est le bordel.

On les appelle ces opérateurs “bitwise”, et ils sont essentiellement de deux types : les shifts, et les opérateurs logiques.

Les shifts, souvent notés >>, <<, etc, décalent d’un bit. Un bit, c’est une case dans un nombre binaire, une d’un chiffre position, qui prend donc la valeur 0 ou 1.

Ex: 1010 est un nombre de 4 bits. 10100 est un nombre de 5 bits.

Par décaler, j’entends qu’ils poussent littéralement les bits dans un sens ou dans l’autre :

1010 >> 1 va donner 101 (les bits se sont décalés vers la droite d’un cran, c’est le rshift). Ca fait disparaitre un bit à droite.
1010 << 1 va donner 10100 (les bits se sont décalés vers la gauche d’un cran, c’est le lshift). Ca rajoute un bit à droite.

Si on met un plus grand nombre, on décale de plusieurs bits :

1010 << 3 va donner 1010000 (les bits se sont décalés vers la gauche de 3 crans)

C’est une opération très rapide, puisqu’il suffit de décaler les chiffres, sans faire de calculs complexes. Pourtant, c’est l’équivalent de certaines opérations mathématiques.

Par exemple, un lshift peut être équivalent à une multiplication par une puissance de 2.

Ainsi, si je prends le chiffre 10 en (binaire 1010), et que je lui applique un lshift de 3, c’est comme si je le multipliais par 2^3 (soit 8). Démo en Python :

>>> 0b1010
10
>>> 0b1010 << 3
80

Mais l’opération prend beaucoup moins de cycles CPU qu’une multiplication. C’est une astuce d’optimisation qui a été beaucoup utilisée au paléolithique et qui reste en vigueur chez les cryptonerds. Néanmoins ne l’utilisez pas en Python, les gains sont minimes. C’est surtout utile pour les codes C ou assembleur qui sont déjà sur optimisés et ont besoin de ce petit coup de pouce supplémentaire.

Ensuite il y a les opérateurs logiques. Ils appliquent tout simplement la logique booléenne à chaque bit d’un nombre avec chaque bit d’un autre nombre dont la position correspond. Par exemple :

Si je fais “1101 ET (représenté souvent par ‘&’) 110″, je vais obtenir :

[1][1][0][1]
 |  |  |  |
 &  &  &  &
 |  |  |  |
[ ][1][1][0]
 |  |  |  |
 v  v  v  v
 0  1  0  0

1 et rien => vrai ET faux ? => faux => 0
1 et 1 => vrai ET vrai ? => vrai => 1
0 et 1 => faux ET vrai ? => faux => 0
1 et 0 => faux ET vrai ? => faux => 0

C’est de la logique booléenne de base, vous pouvez la tester avec True, False, and et or dans votre shell si ça ne vous parle pas.

En code Python :

>>> bin(0b1101 & 0b100)
'0b100'

On l’utilise parfois pour faire ce qu’on appelle des “masques”, c’est à dire représenter une série d’états sous forme de grille de bits, et changer un état de cette grille en appliquant une opération logique avec une autre grille.

Je sais, ça fait flou comme ça. Un exemple peut-être ?

Les permissions d’un fichiers Unix sont Read (lire), Write (écrire) et Execute (exécuter). Soit “rwx”. Elles s’appliquent à User (utilisateur), Group (groupe), Other (autre), soit “ugo”.

Cela est peut être représenté par 3 groupes de 3 bits, soit 9 bits :

    U         G         O
[r][w][x] [r][w][x] [r][w][x]

1 veut dire que la permission est donnée, 0 veut dire qu’elle n’est pas donnée.

Ainsi :

[1][1][1] [1][0][1] [1][0][1]

Veut dire que l’utilisateur à le droit de lire, écrire et exécuter le fichier. Le groupe et les autres n’ont que le droit de le lire et exécuter le fichier.

Si vous mettez ça en base 10, ça donne 7/5/5 (et NON sept-cent-cinquante-cinq car il y a 3 valeurs). Cela explique le fameux chmod 755 que vous voyez dans tous les tutos bash.

Si vous faites :

chmod g+w fichier

Le programme va rajouter la permission d’écrire au groupe. On peut représenter cette opération par l’application d’une opération “OU” (représentée par ‘|’) :

En effet on a 101 pour les permissions du groupe et on veut obtenir 111. Il suffit d’appliquer le masque 010 (qui correspond pile poil à un 1 à la case de la permission voulue) :

>>> bin(0b101 | 0b010)
'0b111'

Représentation des nombres dans un ordinateur

Cette partie c’est vraiment pour vos beaux yeux hein, ne vous en faites pas si vous ne pigez pas tout. Je ne vais pas dans le détail car ça demanderait un autre article complet, plus un comparatif des archis, des notions, des systèmes de typages des langages, etc. Et j’ai vraiment pas envie de le faire.

Bon, déjà, la représentation des nombres dans un ordi, c’est une grande source de confusion : en effet, l’ordinateur ne stocke pas la représentation binaire directe d’un nombre.

On pourrait se dire : l’ordinateur manipule du binaire, on traduit nos nombres en binaire, on fout tout tel quel dedans, et yala.

Mais un ordinateur a des limitations techniques. Notamment, il possède des plages de mémoires d’un nombre de bits limité. Il faut donc faire tenir la représentation de la valeur sur cet espace limité.

Si votre espace de mémoire pour représenter une valeur est limité à 16 bits et que vous devez stocker un nombre entier dedans, il pourra être au maximum 2^16 – 1 = 65535.

C’est pour cela qu’en C on déclare le type d’une variable qui va contenir un nombre qu’on va manipuler. On dit clairement, “alloue moi cet espace mémoire, mon nombre ne sera jamais plus grand que ça”. En Python, c’est automatiquement et transparent.

(C’est aussi pour ça qu’on parle de processeur 16bits, 32bits, 64bits, etc. C’est la taille des jeux d’instructions qu’ils sont capables de traiter en un cycle.)

Mais il se rajoute une autre complexité : un entier peut avoir un signe plus ou un signe moins. Il faut bien le représenter quelque part. On peut le faire en utilisant le premier bit, mais aussi avec un format appelé “le complément à deux”. Comme cet article est déjà super long, je vais pas me lancer dans l’explication. J’ai mal aux doigts. Sachez juste qu’on utilise en quelque sorte “l’inverse” du nombre.

Encore une fois, en C, on déclare que l’on va utiliser un “signed int” ou un “unsigned int” à l’initialisation d’une variable.

Enfin, il y a les nombres à virgule, aussi appelés “flottants” (d’où le type “float” en Python).

On pourrait se dire qu’on utilise les puissances de deux négatives :

0.92 se lit en base 10 “0 x 1 + 9 x 10^-1 + 2 x 10^-2″

On garderait la logique des positions.

Donc :

0.11 se lit en base 2 “0 x 1 + 1 x 2^-1 + 1 x 2^-1 + 1 x 2^-2″

A priori on pourrait stocker le nombre en utilisant ce principe. Mais ça ne permet pas de présenter les nombres très grands (tendant vers l’infini) ou très petit (tendant vers zéro). Alors on utilise un autre principe : on représente un nombre avec la formule m x 2^n, très couramment sur 64 bits (ce que fait le type float Python, mais on peut avoir plus de choix de manoeuvre en utilisant numpy).

m est un nombre à virgule compris entre 1 et 2 (ce dernier étant exclus). On appelle ça la mantisse. Ce sera stocké sur 52 bits en utilisant une représentation binaire littérale.

n est l’exposant, c’est un nombre entier compris entre -1022 et 1023, stocké sur 11 bits.

On garde le premier bit pour le signe.

Et il y a des exceptions pour 0, l’infini et NaN.

(et je n’ai pas la moindre putain d’idée de comment sont représentés les complexes)

Si vous êtes en manque d’aspirine, tout ça est codifié dans la norme IEEE 754, que Python respecte scrupuleusement.

Mais ça vous fait une belle jambe pas vrai ?

Tout ça pour dire que même avec une belle précision, le nombre de chiffres stockés après la virgule est limité. Du coup, on a une valeur approximative stockée dans l’ordi, ce qui fait que les calculs se font sur des approximations :

>>> "{0:.35f}".format(0.9999999999999999999999999999999999999999999999999999999 + 0.000000000000000000001)
u'1.00000000000000000000000000000000000'

En Python on peut gagner en précision en utilisant le module decimal:

>>> from decimal import Decimal
>>> Decimal("0.9999999999999999999999999999999999999999999999999999999") + Decimal("0.000000000000000000001")
Decimal('1.000000000000000000001000000')

C’est plus lent, mais plus intuitif. Quand on manipule des sous, du temps, des unités de poids, de température, de longueur, etc, c’est utile.

Représentation du texte

Pour le texte, c’est encore autre chose. Et là je vous renvoie à l’article sur les encodings.

En résumé, le stockage du texte n’a rien à avoir avec l’arithmétique binaire, c’est juste qu’on fait correspondre un nombre (on montre souvent sa représentation binaire pour l’ASCII et sa représentation hexa pour l’unicode, mais ça reste un nombre, point) à un caractère. Il y a un grand tableau en mémoire qui fait cette correspondance de manière totalement arbitraire.

C’est une norme arbitraire de correspondance, c’est tout.

Donc quand on vous dit “ecrivez les mots ‘glory hole’ en binaire”, ça ne veut rien dire.

Une bonne consigne serait de dire : “donnez moi la suite de nombres, représentés sous forme binaire, qui corresponde aux caractères qui compose les mots ‘glory hole’ selon l’encoding ASCII”.

On peut obtenir la valeur numérique ASCII en utilisant ord() en Python :

>>> for x in 'glory hole':
    print(ord(x))
...
103
108
111
114
121
32
104
111
108
101

Après, c’est juste du formatage pour afficher le binaire :

>>> for x in 'glory hole':
...     print("{:b}".format(ord(x)))
...
1100111
1101100
1101111
1110010
1111001
100000
1101000
1101111
1101100
1100101

flattr this!

]]>
http://sametmax.com/la-fin-du-mystere-du-binaire-nananere/feed/ 46
Django, une app à la fois – faire une app de base http://sametmax.com/django-une-app-a-la-fois-faire-une-app-de-base/ http://sametmax.com/django-une-app-a-la-fois-faire-une-app-de-base/#comments Sat, 18 Jan 2014 15:22:24 +0000 Sam http://sametmax.com/?p=8789 Ca faisait longtemps que je n’avais pas fait avancer le projet django, an app at a time.

Voici donc un nouveau morceau qui montre comment écrire une petite app de base avec un template et une vue réutilisables qui serviront donc dans les prochaines apps du projet.

Comme d’hab, vous pouvez récupérer le code sur la teub de Guy et il y a une version française et anglaise.

flattr this!

]]>
http://sametmax.com/django-une-app-a-la-fois-faire-une-app-de-base/feed/ 2
En attendant asyncio http://sametmax.com/en-attendant-asyncio/ http://sametmax.com/en-attendant-asyncio/#comments Fri, 17 Jan 2014 14:09:59 +0000 Sam http://sametmax.com/?p=8781 La programmation asynchrone arrive en force avec la version 3.4, mais celle-ci n’est pas encore en version stable. En attendant, Python 3 possède déjà de quoi faire de la programmation asynchrone, et même parallèle, avec une bien plus grande facilité qu’en Python 2.

Si vous avez oublié le principe ou l’intérêt de la programmation asynchrone, il y a un article pour ça ©.

Pour montrer l’intérêt de la chose, nous allons utiliser un bout de code pour télécharger le code HTML de pages Web.

Sans programmation asynchrone

Le code est simple et sans chichi :

# -*- coding: utf-8 -*-
 
import datetime
from urllib.request import urlopen
 
start_time = datetime.datetime.now()
 
URLS = ['http://sebsauvage.net/',
        'http://github.com/',
        'http://sametmax.com/',
        'http://duckduckgo.com/',
        'http://0bin.net/',
        'http://bitstamp.net/']
 
for url in URLS:
    try:
        # j'ignore volontairement toute gestion d'erreur évoluée
        result = urlopen(url).read()
        print('%s page: %s bytes' % (url, len(result)))
    except Exception as e:
        print('%s generated an exception: %s' % (url, e))
 
elsapsed_time = datetime.datetime.now() - start_time
 
print("Elapsed time: %ss" % elsapsed_time.total_seconds())

Ce qui nous donne:

python sans_future.py
http://sebsauvage.net/ page: 9036 bytes
http://github.com/ page: 12582 bytes
http://sametmax.com/ generated an exception: HTTP Error 502: Bad Gateway
http://duckduckgo.com/ page: 8826 bytes
http://0bin.net/ page: 5551 bytes
http://bitstamp.net/ page: 51996 bytes
Elapsed time: 25.536095s

Erreur 500 sur S&M… Mon script qui se fout de ma gueule en plus…

Avec programmation asynchrone

On utilise le module future, qui, comme sont nom l’indique, implémente des outils pour manipuler des “futures” en Python. Il inclut notamment un context manager pour créer, lancer et arrêter des workers automatiquement, et leur envoyer des tâches, puis récupérer les résultats de ces tâches sous forme de “futures”.

Pour rappel, une “future” est juste un objet qui représente le résultat d’une opération asynchrone (puisqu’on ne sait pas quand elle se termine). Cet objet contient des méthodes pour vérifier si le résultat est disponible à un instant t, et obtenir ce résultat si c’est le cas.

# -*- coding: utf-8 -*-
 
import datetime
import concurrent.futures
 
from urllib.request import urlopen
from concurrent.futures import ProcessPoolExecutor, as_completed
 
start_time = datetime.datetime.now()
 
URLS = ['http://sebsauvage.net/',
        'http://github.com/',
        'http://sametmax.com/',
        'http://duckduckgo.com/',
        'http://0bin.net/',
        'http://bitstamp.net/']
 
 
def load_url(url):
    """
        Le callback que vont appeler les workers pour télécharger le contenu
        d'un site. On peut appeler cela une 'tâche'
    """
    return urlopen(url).read()
 
# Un pool executor est un context manager qui va automatiquement créer des
# processus Python séparés et répartir les tâches qu'on va lui envoyer entre
# ces processus (appelés workers, ici on en utilise 5).
with ProcessPoolExecutor(max_workers=5) as e:
 
    # On e.submit() envoie les tâches à l'executor qui les dispatch aux
    # workers. Ces derniers appelleront "load_url(url)". "e.submit()" retourne
    # une structure de données appelées "future", qui représente  un accès au
    # résultat asynchrone, qu'il soit résolu ou non.
    futures_and_url = {e.submit(load_url, url): url for url in URLS}
 
    # "as_completed()" prend un iterable de future, et retourne un générateur
    # qui itère sur les futures au fur et à mesures que celles
    # ci sont résolues. Les premiers résultats sont donc les premiers arrivés,
    # donc on récupère le contenu des sites qui ont été les premiers à répondre
    # en premier, et non dans l'ordre des URLS.
    for future in as_completed(futures_and_url):
 
        # Une future est hashable, et peut donc être une clé de dictionnaire.
        # On s'en sert ici pour récupérer l'URL correspondant à cette future.
        url = futures_and_url[future]
 
        # On affiche le résultats contenu des sites si les futures le contienne.
        # Si elles contiennent une exception, on affiche l'exception.
        if future.exception() is not None:
            print('%s generated an exception: %s' % (url, future.exception()))
        else:
            print('%s page: %s bytes' % (url, len(future.result())))
 
 
elsapsed_time = datetime.datetime.now() - start_time
 
print("Elapsed time: %ss" % elsapsed_time.total_seconds())

Et c’est quand même vachement plus rapide :

python3 avec_future.py # notez qu'on utilise Python 3 cette fois
http://duckduckgo.com/ page: 8826 bytes
http://sebsauvage.net/ page: 9036 bytes
http://github.com/ page: 12582 bytes
http://sametmax.com/ page: 50998 bytes
http://0bin.net/ page: 5551 bytes
http://bitstamp.net/ page: 52001 bytes
Elapsed time: 3.480596s

Même si vous retirez les commentaires, le code est encore très verbeux, ce qui explique pourquoi j’attends avec impatience asyncio qui, grâce à yield from, va intégrer l’asynchrone de manière plus naturelle au langage.

Mais ça reste beaucoup plus simple que de créer son process à la main, créer une queue, envoyer les tâches dans la queue, s’assurer que le process est arrêté, gérer les erreurs et le clean up, etc.

Notez qu’on peut remplacer ProcessPoolExecutor par ThreadPoolExecutor si vous n’avez pas besoin d’un process séparé mais juste de l’IO non bloquant.


Télécharger le code de larticle : avec future / sans future.

flattr this!

]]>
http://sametmax.com/en-attendant-asyncio/feed/ 14