You are currently browsing the tag archive for the ‘python’ tag.

Depuis que j’utilise des logiciels libres, je me suis tourné vers la programmation mais c’est aussi parce que je voulais faire cette apprentissage que je suis passé à GNU/Linux. De cette expérience, je tire différents savoirs nouveaux et au final, je me demande si je n’ai pas appris d’avantage concernant les éléments périphériques que sur la programmation elle-même… Je fais cette introduction pour dire que l’efficacité sur une machine, telle que je la conçois, je la tire de cette expérience et des logiciels qui y sont liés.

Je vais donc faire une liste des logiciels qui me semblent décupler mon efficacité mais aussi mon plaisir à travailler.

ZSH

L’un de mes premiers apprentissages, lorsque je suis passé à un OS libre, fût d’apprendre à utiliser la console. Comme je pense la majorité des gens, j’ai commencé par Bash. Bash, c’est bien, mais c’est peu palpitant. J’ai lu beaucoup d’avis positifs sur ZSH alors j’ai testé et aujourd’hui, je ne peux plus me passer de

  • sa complétion
  •  l’usage de jokers récursifs

GNU-Screen/Tmux

Nous voilà doté d’un shell digne de ce nom, passons maintenant à la dimension du dessus, l’organisation du terminal.
Ouvrir des fenêtres à chaque fois que l’on a besoin d’un prompt, c’est rapidement fatiguant. Des multiplexeurs de terminaux comme screen ou Tmux (fonctionnalités très proche, l’un ou l’autre fait l’affaire généralement) nous facilitent la vie. Je le considère pour ma part comme étant un gestionnaire de fenêtre, tout aussi important que d’avoir Gnome ou openbox. Je peux ainsi avoir un équivalent de bureaux virtuels, que je peux nommer pour me repérer. Par exemple, une fenêtre avec le fichier de conf, une avec le code, et une autre pour parcourir les répertoires où sont écrites les données. D’autres arguments dans la suite.
Avec Screen, je n’accorde plus vraiment d’importance à l’émulateur de terminal parce que je n’utilise aucune de ses fonctionnalité, si ce n’est une seule : qu’il puisse apparaître en plein écran et disparaître par l’action d’une touche (console top/down). J’utilise tilda, mais ça pourrait être autre chose.

VIM

Editer des fichiers, en console, c’est pour le moins fréquent. Dès mes premiers pas, j’ai voulu apprendre autre chose que nano. Pour couper court au débat, je n’ai jamais utilisé emacs et avec vim, je suis certain qu’il sera disponible sur 99,99% des machines que je rencontrerai. Il a une large panoplie de greffons, les possibilités sont infinies.
Ca permet de faire de l’adminsys, de la rédaction, du développement…

SSH

SSH est juste un outil incontournable. Bien sûr, ça permet de joindre une machine distante, mais pas que. C’est aussi le moyen d’envoyer des fichiers, d’établir un proxy ou de passer à travers un réseau NATé. C’est rapide et sécurisé.
Lorsqu’on utilise SSH, on apprécie de savoir utiliser Screen ou Tmux.

  • si la connexion lâche, screen se détache et la mise à jour critique en cours n’est pas plantée au milieu, et là, on est heureux.
  • si on a travaillé sur une machine localement et qu’on souhaite s’y reconnecter par le réseau plus tard, on peut récupérer la session screen restée ouverte. Autrement dit, je retrouve exactement le même environnement qu’en local.

Git

Ca, c’est un gestionnaire de version qu’on ne présente plus. Je gère mes projets avec, mais aussi ma synchronisation de fichiers et un wiki comme ça (j’en ai parlé ici déjà). Je ne connais même pas le 1/100e de ses possibilités et il m’est néanmoins indispensable.

Python

J’ai codé dans un certain nombre de langage. Je ne peux pas dire que je les maîtrise (même python) car je ne suis pas un développeur expert par manque de temps plus que d’envie. Après avoir codé pendant un certain temps en bash mes scripts systèmes, j’ai découvert Python, et aujourd’hui c’est mon couteau suisse :

  • scripts systèmes
  • code scientifique
  • petits utilitaires

La syntaxe est claire, ce qui fait du code maintenable. Les bibliothèques sont très variées ce qui permet de couvrir un grand nombre de besoin. Enfin, c’est suffisamment haut niveau pour écrire rapidement un code fonctionnel et c’est important ; on n’est rarement là pour faire des exercices de style. En clair, investir dans un langage polyvalent comme celui-ci, ça permet de se sortir de problèmes lourds rapidement et sans trop d’effort.

Conclusion

Ces outils, je les utilise tous les jours, sans exception. Il n’y a que pour le courriel et le web où je suis resté sur des outils "populaires" avec Firefox et Thunderbird.

Le dénominateur commun à tous ces outils :

  • c’est austère. Oui, mais c’est épuré, et ça a l’avantage que le contenant ne distrait pas du contenu.
  • ça demande un apprentissage qui peut être parfois long. En contre-partie, ce sont des outils que l’on verra encore probablement pendant quelques décennies.

 

Rédigeant mon manuscrit, je ne me voyais compiler toutes les cinq minutes mon documents pour voir si je n’ai pas fait une erreur. En effet, détecter une erreur rapidement permet de la corriger facilement surtout que les compilateurs latex et consorts donnent parfois des erreurs peu facile à décrypter.

L’idée est donc de compiler le document automatiquement à chaque dois que je l’enregistre. Une bulle (lib notify) est affichée de manière à prévenir du succès ou de l’échec. Faire ça en python, c’est très simple avec watchdog et pynotify. Quelqu’un avant moi a déjà pensé à faire quelque chose comme ça.

Dans mon cas, je n’utilise pas de makefile. Je fais donc mon appel xelatex directement. J’utilise subprocess.check_call() afin d’avoir le code de retour de xelatex.

Avec pynotify, j’ai une exception gobject.GError qui est parfois levée. Je n’ai pas d’idée de la raison. Si quelqu’un a une idée… sinon, je chercherai plus tard (ça reste Quick & Dirty).

import subprocess
import os
import time

import pynotify
from gobject import GError

from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler


class ChangeHandler(FileSystemEventHandler):
    """React to modified .tex files."""
    def on_any_event(self, event):
        """If a file or folder is changed."""
        if event.is_directory:
            return
        if os.path.splitext(event.src_path)[-1].lower() == ".tex":
            pynotify.init("Mon appli python")
            try:
                subprocess.check_call(['xelatex', '-halt-on-error' ,'thesis.tex'])
            except subprocess.CalledProcessError:
                message = pynotify.Notification("Build failed.")
            else:
                message = pynotify.Notification("Build done.")
            try:
                message.show()
            except GError:
                pynotify.uninit()  # methode brute !
                print('Gerror')
                pynotify.init("Mon appli python")



def main():
    handler = ChangeHandler()
    observer = Observer()
    observer.schedule(handler, '.')
    observer.start()
    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        observer.stop()
    observer.join()


if __name__ == '__main__':
    main()

Prenons ce cas d’école où on est en possession d’une large quantité de photographies que l’on doit traiter. Dans mon cas, ces photos sont prises avec un Nikon D300 dans un format tif. Je prends une image par minutes sur 7 à 48 heures. Le script présenté ici ne fait qu’un découpage des photos sur une zone d’intérêt et les enregistre sous un format png. D’autres actions peuvent être ensuite enchainées, selon les besoins. Le code est donné en fin d’article, son contenu me semblait suffisamment intéressant pour en faire une billet.

Le "crop" se fait avec la bibliothèque PIL. Puisqu’elle n’est disponible qu’en python2, on écrira le script dans cette version. Cette partie est réalisée par la fonction crop_pic()

L’étape suivante est de récupérer la liste des images à traiter et le répertoire de destination. J’utilise la bibliothèque argparse même si quelque chose de plus léger comme cliz pouvait suffire. Ainsi, je spécifie un répertoire source et un répertoire de destination.

Dans le répertoire source, je récupère à l’aide de glob la liste des fichiers. On pourrait être plus restrictifs en imposant une extension tif. Le nommage est plus ou moins complexe, mais il comporte au moins un numéro. La liste des fichiers est donc triée avec sorted (Vous allez comprendre dans un instant pourquoi). Il faut faire attention, car si les fichiers sources comportent des numéros comme 1, 2, … 11, 12, …, un tri sur cette liste mettra 11 avant 2. On spécifie donc une clé pour avoir un tri naturel. J’ai dû récupérer ce bout de code un jour sur stack overflow.

Une fois ceci fait, il faut générer de nouveaux noms, le nombre se référant au temps. J’ai choisi d’écrire un générateur dont je peux spécifier la première valeur. Mes numéros sont sous la forme 00001 pour avoir un tri naturel avec un ls par ex. :) On pourrait spécifier un pas dans le cas où la période d’acquisition aurait été différente de 1 minute pour avoir ainsi un indicateur temporelle correct dans le nom.

On passe enfin à l’action en lançant les crop sur chaque image. Le processus étant un peu lent avec une alternance de charge processeur et d’IO, il est plus efficace de traiter des images en parallèle. Pour cela, j’ai choisi d’utiliser une pool de tâches. On la remplit, et une fois vidé, on admire nos belles images traitées.

#!/usr/bin/env python2
# -*- coding: utf-8 -*-


from PIL import Image

import glob
import os
from multiprocessing import Pool

import re
import argparse


def get_name(path, start=1):
    """
    Generator for pictures
    :param path: dir path for the picture  
    :param start: first number
    :returns: filename
    """
    i = start
    while True:
        pngfile = os.path.join(path, str(i).zfill(5) + '.png')
        yield(pngfile)
        i += 1

def crop_pic(filename, output, box):
    """
    :param filename: name of the picture   
    :param output: destination file
    :param box: crop box (tuple)  (x_1,y_1,x_2,y_2)
    """
    print(" Processing... %s \n\t to... %s" % (filename, output))
    basename, ext = os.path.splitext(filename)
    name, ext = os.path.splitext(filename) 
    im = Image.open(filename)
    region = im.crop(box)
    region.save(output)


def tryint(s):
    try:
        return int(s)
    except:
        return s

def alphanum_key(s):
    """
    Turn a string into a list of string and number chunks.
    >>> alphanum_key("z23a")
    ['z', 23, 'a']
    """
    return [ tryint(c) for c in re.split('([0-9]+)', s) ]


if __name__ == '__main__':

    box = (1130,220,3600,2450)
    start = 1

    #Arg parsing
    parser = argparse.ArgumentParser(description='Crop!', epilog='')
    parser.add_argument('-s', help='Source dir', metavar='SOURCE', required=True)
    parser.add_argument('-d', help='Dest dir', metavar='DEST', required=True)
    args = parser.parse_args()

    #Dirs
    cwd = os.getcwd()
    source_dir = os.path.join(cwd, args.s)
    dest_dir = os.path.join(cwd, args.d)
    os.makedirs(dest_dir)

    #List of pictures
    pic_list = glob.glob(source_dir + '/*')
    pic_list = sorted(pic_list, key=alphanum_key)

    #Create a pool
    pool = Pool(processes=4)
    generator = get_name(dest_dir, start)
    for im in pic_list:
        output = generator.next()
        pool.apply_async(crop_pic, args = (im, output, box, ))

    pool.close()
    pool.join()

 

Il est fréquent d’avoir à manipuler des données qui sont trop nombreuses pour être traitées à la calculatrice, et pas assez pour mettre en place des fichiers binaires complexes. A vrai dire, c’est 99% des cas chez moi. Le big data est à la mode, les petites quantités sont mon quotidien. Je n’ai rien contre netcdf, mais c’est franchement sur dimensionné, sans compter que les formats binaires, ce n’est jamais pratique pour les petits usages.

Il y a les gens qui utilisent des tableurs, et il y a les autres comme moi qui n’aiment pas ce genre d’outil trop étriqué. Alors, les premières fois (il y a toujours une première fois), j’ai écris mes données dans un fichier texte, j’ai écris un parseur qui lit ligne par ligne, et je stockais ça dans un tableau. Je travaillais avec les données pour ensuite écrire les résultats dans stdout ou un fichier. C’est bien, mais trop long et trop fastidieux.

Ensuite, j’ai découvert les fichiers .ini. C’est normalement destiné à la configuration, mais ça peut faire son job. Pour ceux qui voudraient essayer : non, je n’écrierai pas de l’xml à la main.

Entre temps, j’ai découvert YAML pour yet another markup language. C’est lisible par tout un tas de langage dont python, ce qui est bien quand on veut faire du calcul scientifique rapide et efficace. Il y a sans doute tout un tas d’autres usages qui ne sont pas les miens.

Voici un fichier avec des données :

lame1:
    epaisseur: 3
    largeur: 10
    longueur: 100
    masse: 50
lame2:
    epaisseur: 4
    largeur: 20
    longueur: 300
    masse: 200

Le problème est simple : j’ai des lames au labo, je dois les caractériser. J’ai pris les dimensions et les poids. Je commence par deux, mais je serai amené à en ajouter d’autre au fur et à mesure de mes pérégrinations. Disons que je veux connaitre la densité.

import yaml
with open('data.ml', 'r') as data:
    doc =  yaml.load(data)
    for lame in doc:
        masse = (doc[lame]['masse'])
        epaisseur = (doc[lame]['epaisseur'])
        largeur = (doc[lame]['largeur'])
        longueur = (doc[lame]['longueur'])

        densite = masse/(epaisseur*largeur*longueur)

        print("Densité de la {0}: {1}".format(lame, densite))
 

Je vois pas mal d’avantage. Mon fichier est bien structuré, donc lisible. C’est du texte, donc versionable, facilement éditable, interopérable, durable, mangeable

L’exemple est ici simplet, le format yaml et son utilisation avec python sont illustrés ici.

Je dois bien réécrire mon script de sauvegarde tous les deux ans. Le premier s’était inspiré d’un article d’artisan Linux et je l’ai publié quelque part mais j’ai oublié où… C’était en 2008, avant je n’avais pas de sauvegarde. Puis, j’ai apporté quelques améliorations qui devaient faire l’objet d’un article en 2010. J’ai encore le brouillon de l’article dans ce blog. Au final, ce billet ne sortira jamais car j’ai écrit une nouvelle version.

Précédemment, mes scripts étaient en bash et basés sur rsync. Comme je n’aime pas réinventer la roue, j’ai cherché des solutions toute faite. Mais je n’ai jamais trouvé chaussure à mon pied. Soit ça ne remplissait pas les critères, soit c’était d’une façon ou d’une autre une usine à gaz. Je garde notamment un mauvais souvenir de rsync-backup dont l’utilisation CPU m’a effrayé.

Rsync est un outil puissant et robuste. J’ai choisi de continuer à l’utiliser. Par contre, le "wrapper" n’est plus un script shell. En effet, la maintenance est vraiment casse-pied car ça devient toujours plus ou moins illisible au coup d’oeil et je n’aimais pas le manque de modularité du code. J’avais plusieurs versions modifiées du même code sur mes divers machines pour tenir compte de telle ou telle spécificité, et ça ce n’est pas une bonne pratique.

Comme j’aime de plus en plus python, j’ai franchi le pas de réécrire le tout. C’est facile à écrire, mais toujours long à mettre en production car je considère ce bout de code comme critique. Cependant, l’étape est sur le point d’être franchie ; il ne me reste que quelques améliorations à faire et les tests sont concluants pour le moment. Voici les spécifications de départ :

  • Sauvegarde locale ou distante (disque dur externe ou serveur ssh)
  • Incrémentale ou non
  • Les incréments sont compressés
  • Log propres
  • Réglage de la fréquence (sous la forme du temps minimal pouvant séparer deux sauvegardes)
  • Réglage du nombre minimal d’archives incrémentales à conserver ET de l’ancienneté de ces archives.
  • Faible consommation CPU/IO
  • Minimum de sécurisation : empêcher les doubles processus, gérer au mieux les signaux.
Le code se présente sous la forme d’une bibliothèque qui pourra être utilisée par divers scripts. Chez moi, mes scripts sont sur un git privé et j’ai entre autre home_sauvegarde.py, labo_sauvegarde.py… chacun étant utilisé sur une machine donnée. Ainsi, aucune duplication de code.
L’idée est que le script est lancé fréquemment par une tâche cron. C’est le script qui gère si une sauvegarde doit être réalisée ou non. Selon la source visée, j’ai des besoins différents. En effet, je ne sauvegarde pas de la même façon mes courriels qui bougent sans cesse et mes documents administratifs par ex. Dans le premier cas, je dois copier souvent mais pas garder longtemps car il est très probable que je remarque une corruption rapidement. Pour le second cas, je n’ouvre pas ces documents souvent ; une fausse manipulation peut être détectée des semaines plus tard.
Notez que la suppression des archives se fait sur deux critères. Dans mes premières versions, j’utilisais tmpwatch pour supprimer X jours après création. Le hic survient lorsqu’on part en vacances 15 jours et que X vaut 10 jours. On se retrouve sans historique. Ceci explique la conservation d’un minimum d’archives.
Par la suite, il faut que je dépose une couche de brebis là dessus. Par ailleurs, je dois me pencher sur la question de l’auto-hébergement, à savoir s’il y a des choses intelligentes à ajouter (dump de base de données…)

Si ça peut donner des idées, mon code est déposé ici sous le nom de Vitalus. Oui, les sauvegardes, c’est vital :)

Situation

J’utilise zim tous les jours, notamment afin de conserver ma veille. Cette veille est issue du traitement de mes flux RSS. Lorsqu’une page contient une information intéressante sur un sujet, j’ajoute le lien dans la page zim qui correspond. J’y ajoute parfois des commentaires. Je fais de même lorsque je navigue (recherche d’astuces, comparatif pour achats…). Je pense que la majorité des personnes utilisent des marques pages dans leurs navigateurs. Je ne trouve pas cela pratique car il est difficile de les ranger, de les manipuler, de les commenter. Zim m’enlève cette difficulté.

Problématique

Cependant, chacun sait que le web bouge sans cesse. Le problème vient lorsque vous décidez de vous pencher sur un sujet. Vous ouvrez la page zim en relation afin de retrouver toutes les informations accumulées aux fils des mois ou des années. Vous retrouvez vos liens mais certains sont morts. Plusieurs raisons à cela : le site peut être inaccessible, avoir changé de CMS (liens cassés), de domaine ou simplement avoir disparu, voir le contenu retiré… dommage le lien était intéressant.

Première tentative

Nous ne sommes pas les premiers à faire ce constat, d’autres ont anticipé en créant des archives du web. La plus connue est sans doute archive.org. D’ailleurs, je vous conseille le module resurrect pages pour firefox.

Il y a toujours un mais… La solution n’est pas idéale, car nous sommes dépendant d’un service qui peut ne pas avoir archivé la page souhaitée : le crawler n’est pas passé au bon moment, le crawler n’est pas parvenu jusqu’à la page ou le site n’a jamais été visité…

Seconde tentative

L’idée est de mettre en place sa propre archive. On n’est jamais mieux servi que par soi-même, pas vrai ? Les outils utilisés par ces sites d’archives me semblent démesurés pour mes besoins. Un wget de la page me suffirait amplement dans 95% des cas. Bien sûr, on ne va pas faire ça à la main, ce serait trop long, et rapidement on ne le ferait plus.

Un script python va donc nous sauver la mise. En scannant à la recherche d’url toutes nos pages zim, il va récupérer la page avec le module urllib.request. Cette page est sauvée dans l’endroit de son choix. On ajoute un lien (comme le fait wikipedia dans les références via wikiwix) vers notre fichier téléchargé. Zim gère l’ouverture des fichiers avec xdg-open, chez moi c’est géré dans

~/.local/share/applications/mimeapps.list

Un simple clic sur l’archive ouvre la page dans le navigateur.

Le script peut être automatisé à l’aide d’une tâche cron ou via la fonctionnalité "outil personnalisé" de zim.

Quelques remarques

Comme je l’ai spécifié, j’utilise urllib.request. Je n’ai pas trouvé mieux dans la récupération de page. Si vous connaissez une bibliothèque plus performantes, je suis toujours preneur.

J’ai du changer le user-agent. Pour ceux qui ne connaissent pas, c’est la façon dont est identifié le visiteur du site. Par défaut, c’est python, je l’ai changé pour Firefox car des sites comme wikipedia me jetais. Sans doute parce qu’ils préfèrent qu’on utilise l’API.

La solution est implémentée pour zim, mais l’idée est générique pour ceux qui utiliseraient d’autres solutions de prise de note.

Idées d’amélioration

Il serait peut être pertinent de rafraîchir les archives tous les mois en conservant les versions anciennes (au cas où). Typiquement, certains commentaires sont très utiles et il peut être bon de les avoir. Une autre bonne chose serait d’avoir la possibilité d’empêcher délibérément l’archivage. Parfois, j’ai des liens vers des dépôts pour lesquels il n’y a aucun intérêt à avoir une copie ! Si vous avez d’autres propositions…

Code

C’est sous GPLv3, nécessite python3. J’ai déposé le code ici, plus pour information que pour production pour le moment, le code est relativement chaud. Si des gens sont intéressés par des versions stables dans le futur, je les proposerai.

Je vous annonce la sortie d’inforevealer en version 0.4. une version importante pour le logiciel (il a vu sa taille doubler :) ). Il y a quelques temps, je vous présentais le concept d’inforevealer. En quelques mots l’idée est de faciliter l’entraide aux débutants en ayant un outils capable de récupérer les informations pertinentes vis à vis d’un problème. L’objectif est de minimiser le temps passer à expliquer des choses ‘annexes’ comme les concepts de bases des commandes, l’ouverture de fichier, les éditeurs textes…

Comme je le disais, il fallait qu’inforevealer se dote d’une interface graphique (en plus de la ligne de commande) puisque très fréquemment elle est fonctionnelle chez l’utilisateur ‘en panne’, donc on ne pouvait s’en passer dans notre volonté de simplification.

Cette interface graphique est codée grâce à pygtk. En voici une capture d’écran.

Ce n’est pas du grand art, mais ça fonctionne bien ;) (chez moi en tout cas).

Dans la première fenêtre qui s’affiche, on choisi la catégorie "qui va bien", on peut enclencher le mode verbeux (pour avoir des contenus longs en plus) et changer le fichier de sortie.

Le fichier généré (après demande de mot de passe root ou utilisateur (sudo) si nécessaire) est affiché. On a alors la possibilité de le parcourir (mais pas de le modifier, je ne pense pas que ce soit nécessaire…), de le copier dans le presse papier et de l’envoyer sur le pastebin de son choix.

Je suis preneurs de vos tests (l’un d’entre vous m’a bien aidé à chasser les quelques bugs restant la dernière fois, encore merci à lui).

La suite sera consacrée à des améliorations de l’existant et quelques "petits" ajouts tels que :

  • Inspection des fichiers d’un répertoire en plus d’une commande et d’un fichier : ce sera utile pour le xorg 1.8 : /etc/X11/xorg.conf.d/. J’ai implémenté dans cette version la récupération de fichiers du type ~/foobar pour avoir des fichiers comme ~/.xsession.
  • Traduction : le problème majeur étant que j’ai dans le fichier de configuration la description des catégories. Donc impossible d’utiliser gettext. La seule méthode que je vois pour le moment est de dupliquer le fichier pour chaque langue et de lire le bon…
  • Intégration : création d’un .desktop, d’une méthode d’installation (via un setup script ?)
  • Compléter les catégories. Je pense qu’il y a pas mal d’oublis.
  • et les bugs à corriger ! :D

Télécharger la dernière version. Ne pas oublier les dépendances :

pygtk 2.6 ou sup.
python-configobj 4.6.0 ou sup.
python-pexpect

Même pendant les vacances, on peut réfléchir à optimiser un travail futur. Voici un exemple que je viens de mettre en place.

Problématique

Il arrive souvent d’avoir à faire de l’organisation de planning. Typiquement, la problématique est la suivante :

On se donne une liste de plages horaires.
On se donne une liste de personnes qui ont des préférences/contraintes.

Je suppose que les deux listes sont de même tailles, taille que je note N. (ie tout le monde aura une place, et il ne restera pas de places vacantes)

Là, on a trois catégories d’organisateurs :)

  1. Ceux qui trouve quelqu’un a qui déléguer le travail :P
  2. Ceux qui y vont manuellement : "Alors, lui il veut ca, bon , on le mets, et lui… mince la même chose…"
  3. Ceux qui vont réfléchir un peu

Je décide d’appartenir à cette 3ème catégorie. ;)

Priorité

Plaçons-nous dans le cas où on doit placer une personne par jour de la semaine. Je vais envoyer à chaque personne un petit fichier contenant ceci :

[nom]
Lundi=0
Mardi=0
Mercredi=0
Jeudi=0
Vendredi=0

Les valeurs représentent les priorités. Je fixe une petite règle du jeu (pour que toutes les personnes soient égales). Par exemple :
Chaque personne peut utiliser zéro fois ou une fois seulement chacune des valeurs suivantes : 1,2,3. Les autres seront des zéros (0).
Toto me renverra ce fichier par ex :

[Toto]
Lundi=1
Mardi=0
Mercredi=3
Jeudi=2
Vendredi=0

Méthode déterministe

Une méthode évidente est, une fois que l’on a récupéré les 5 fichiers, de passer en revue toutes les arrangements possibles. Le nombre d’arrangement est facile à déterminer :

N!

Pour ceux qui se souviennent de leurs cours de maths du secondaire, c’est l’arrangement A_n^pp=n :

A_n^p = \frac{n!}{(n-p)!} = n(n-1)...(n-p+1)

On peut retrouver ce résultat simplement avec le raisonnement suivant, on a N possibilité pour le premier choix, puis N-1 etc.

Dans notre exemple, 5!=120, ce qui est abordable à toutes les machines. Mais dès que l’on passe la 20ène, le nombre est déjà plus conséquent. Il serait donc bon d’utiliser une autre méthode pour les cas de grosses organisations.

Approche statistique

Plutôt que de passer en revue "naïvement" toutes les possibilités, nous allons utiliser un algorithme de Monte-Carlo et nous promener dans l’espace des possibilités. J’utiliserai ce que l’on appelle l’algorithme de Metropolis, ça devrait être largement suffisant. Pour plus de détails, je vous renvoi au cours de Pascal Viot.

L’idée est de définir au système un potentiel et de trouver le minimum global de ce potentiel (en évitant au mieux les minima locaux). Définissons donc le Hamiltonien (énergie) suivant :

\Large{ H(\{j_i\}) = - \sum_{i=0}^N f_i(j_i) }

f_i(j_i) désigne la priorité de la personne i pour la date j_i. L’indice i permet de garder en mémoire que la collection des \{ j_i\} ne doit pas comporter deux éléments identiques (deux personnes ne peuvent avoir le même rendez-vous). La notation est un peu subtile, mais une autre écriture plus univoque aurait demandé une double sommation et l’utilisation d’un terme répulsif (delta de Dirac) et aurait finalement compliqué l’affaire.

Le signe moins permet (dans mon cas) d’avoir des énergies toutes négatives ou nulles. On va donc bien chercher un minium.

L’algorithme de Metropolis est le suivant.

On se donne une configuration de départ. On calcule son énergie avec notre Hamiltonien : U_{init}

On échange les dates de deux personnes et on calcule à nouveau l’énergie  U_{fin}. Deux cas se présentent :

  1. Si U_{fin} < U_{init}, on adopte la nouvelle configuration plus favorable.
  2. Sinon, on tire au hasard un nombre entre 0 et 1, que l’on compare à e^{-\beta(U_{fin} - U_{init})} et on acceptera ou non la configuration selon. Ceci nous permet de sortir d’un minimum local.

On va enregistrer l’état de plus basse énergie trouvé et on considérera que c’est notre optimum.

Implémentation

J’ai implémenté ce petit algorithme en python. OK, coté performance, il y a sans doute mieux, mais disons que c’était vite fait. Par ailleurs, je ne connais pas de bibliothèque équivalente à ConfigObj en C++ (ou en C), si vous connaissez, n’hésitez pas ! :)

Par ailleurs, j’ai implémenté aussi le méthode déterministe (ce qui, accessoirement, me permet de valider mon code).

Je mets à disposition le code à cette adresse. (GPLv2)

A l’heure où j’écris ces lignes, il me reste à gérer les cas où plusieurs possibilités existent pour un même taux de satisfaction.

Je vous présente le logiciel que je code en ce moment à l’occasion de la sortie de sa version 0.2. Ce logiciel, codé en python, est distribué sous licence libre GNU GPL v2.

Qu’est ce qu’inforevealer ?

La bonne question est sans doute pourquoi  un nouveau logiciel ? Avant tout pour le fun. J’avais envie de me faire la main, et j’ai déjà appris plein de choses. Mais outre cela, je voulais coder quelque chose qui aurait une petite chance d’être utile à quelques uns.

Ceux qui ont déjà effectué de l’aide à un tout jeune débutant qui rencontre des problèmes avec sa distribution sait qu’il est parfois pénible d’obtenir les informations pertinentes. En effet, avec le temps, on acquière de l’aisance sous notre OS préféré et donner le résultat d’une commande ou le contenu d’un fichier (même accessible que par root) ne nous est plus difficile. Mais aux débutants, on va passer la moitié du temps d’entraide à lui expliquer comment taper une commande, comment se logguer en root, ou nous donner le résultat d’un fichier. Sans compter les command not found parce que la personne ne sait pas recopier une ligne, ça finit par lasser.

L’idée est donc de se débarrasser de ces soucis de base pour s’attaquer au réel problème rencontré par l’utilisateur. L’idée est fort simple. Un logiciel va produire un log, ciblé sur une catégorie "type", regroupant divers commandes et contenus de fichiers. La manipulation du log produit devra être aisée.

Et il fait quoi ce inforevealer ?

Inforevealer n’est disponible qu’en ligne de commande pour le moment. On peut spécifier une catégorie selon la panne comme par exemple : disk (relatifs aux partitions etc), display (pour les problèmes d’affichage…), package (soucis de paquets), bootloader, sound et j’en passe.

A partir de là, un log est généré en local. un unique fichier que l’utilisateur peut récupérer. (le chemin est précisable, par défaut /tmp/inforevealer)

Le log peut aussi être envoyé sur un pastebin de son choix (parmis ceux supportés). L’url apparait directement dans le terminal à l’image de pasteinit. Ca tombe bien, j’ai réutilisé une partie du code dispo en GPLv2 pour cela. Je dirai que cette méthode de pastebin est encore plus facile pour le débutant.

Certaines commandes/fichiers sont spécifiques à la distribution (packages). Le logiciel est prévu pour s’en accommoder  en détectant automatiquement la distribution. Ce logiciel a donc pour vocation d’aider toutes les distributions (dans la limite de la configuration existante… là, toute aide est appréciée :) )

Certains fichiers/commandes ne sont accessibles que par root. Si des droits privilégiés sont nécessaires, on demandera à l’utilisateur s’il veut effectivement augmenter ses droits pour générer un log le plus complet possible. su et sudo sont supportés.

Et le futur ?

Les fonctionnalités futures reposent surtout sur l’interface graphique. L’interface CLI (ligne de commande) ne sera pas abandonné (on serait bien mal en cas de plantage de X…), mais fréquemment on a quand même une interface graphique qui tourne, et un débutant préfèrera cliquer, donc il faut lui donner cette possibilité. Je dirai que c’est le dernier gros morceau en codage que je vois (outre un souci pour traduire les descriptions qui pointe son nez).

Une autre chose majeure, et chacun peut contribuer en donnant ses idées, concerne la configuration des catégories. Toutes les idées de commandes plus performantes, de fichiers pertinents seront ajoutés. J’utilise un fichier de conf pour cela, ce qui permet aussi aux packagers de ne pas attendre mon retour upstream pour effectuer des adaptations à leurs distributions.

Enfin, tout cela se fera si des gens y voient un réel intérêt :) Toutes autres idées géniales (ou pas) sont les bienvenues.

Le dépôt du projet est sur github (tant que gitorious ne gère pas les tickets) Rendez-vous dans la section téléchargement.

Il y a des jours où l’on rencontre de ces petites perles sur le web et on se demande pourquoi ce n’est pas plus connu que cela.

C’est le sentiment que j’ai eu en visitant le blog stackp où l’auteur (visiblement français, mais le blog est en anglais) propose de petits scripts qui ne dépassent pas les quelques centaines de lignes et qui facilitent grandement la vie. Ces scripts sont publiés sous la licence Python Software Foundation License, proche de la BSD.

Je vais citer les plus remarquables à la fin de ce billet, mais avant cela, je vous présente deux scripts qui devraient faire des adeptes.

Qui n’a jamais eu à envoyer ou à recevoir de la part d’un(e) ami(e) un fichier qui ne passe pas par email ni par la messagerie instantanée ? Là, on sort généralement le fichier zip découpé en plusieurs morceaux, ou encore les sites d’hébergement souvent pavés de publicités et autres joyeusetés flashiennes. Et pourtant… ne serait-il pas plus simple de laisser le fichier sur la machine et de permettre à la personne de le télécharger directement ? ou encore d’avoir un moyen de recevoir le dit fichier directement sur son disque ?

Weblink

weblink permet en quelques secondes de partager tel ou tel fichier de son disque en permettant au destinataire de l’obtenir via son navigateur.

Une fois le script rendu exécutable, son utilisation est enfantine :

./weblink ma_photo.png

Vous pouvez le "voir" directement depuis votre navigateur à l’adresse http://localhost:8888/
et votre destinateur le verra à http://11.22.33.44:8888/ où 11.22.33.44 est votre IP publique.
Il ne faut pas oublier, si vous avez un routeur, de le configurer correctement et d’ouvrir le port de votre machine.
Il est possible de changer le port par défaut à l’aide de l’option -p suivi du port. Une fonctionnalité de "mot de passe" existe aussi :
./weblink –randompass photo1.jpg photo2.jpg
produira un lien du type http://localhost:8888/ob2epHNC01hQsB3946VwnGv8IDixJA. L’option –pass permet de spécifier le mot de passe.

Droopy

Maintenant, si on doit vous envoyer un fichier, c’est à droopy que vous aurez affaire.  A nouveau, il faut télécharger, rendre exécutable et paramétrer firewall et routeur (port par défaut 8000) et on lance l’executable :
./droopy /home/fr/var
et vous verrez à http://localhost:8000 une interface web (traduite dans de nombreuses langues dont le français) permettant de sélectionner et d’envoyer le fichier.

Autres scripts

Petites sélections d’autres scripts utiles :

massmailer qui permet d’envoyer un courriel à une liste d’adresse, emails adressées individuellement.

imapsave pour sauvegarder les emails de sa boite imap.

Dans une moindre mesure pour moi, mais certains y trouveront peut être un intérêt, Scalpel sound editor dont le nom dit tout.

Conclusion

J’espère vous avoir fait découvrir des choses utiles. Pour ma part, outre le coté fonctionnel, je pense que ces courts scripts sont aussi de bons exemples pédagogiques. Etant en apprentissage python en ce moment, c’est comme cela que je les ai vu aussi.

Entrer votre adresse e-mail pour vous inscrire à ce blog et recevoir les notifications des nouveaux articles par courriel.

Joignez-vous à 15 followers

April

Promouvoir et soutenir le logiciel libre

Licence

Le contenu textuel de ce site est mis à votre disposition sous les termes de la licence Creative Commons CC BY-SA

Vous êtes libres :

* de reproduire, distribuer et communiquer cette création au public
* de modifier cette création

Selon les conditions suivantes :

* Paternité — Vous devez citer le nom de l'auteur original de la manière indiquée par l'auteur de l'oeuvre ou le titulaire des droits qui vous confère cette autorisation (mais pas d'une manière qui suggérerait qu'ils vous soutiennent ou approuvent votre utilisation de l'oeuvre).
* Partage des Conditions Initiales à l'Identique — Si vous modifiez, transformez ou adaptez cette création, vous n'avez le droit de distribuer la création qui en résulte que sous un contrat identique à celui-ci.

Suivre

Recevez les nouvelles publications par mail.

%d bloggers like this: