PROJET AUTOBLOG


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

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

⇐ retour index

Le déploiement par conteneurs avec Docker

jeudi 15 mai 2014 à 21:10

Mettre son projet en production, c’est la galère. Tellement que mille méthodes ont vu le jour pour automatiser tout ça. Chef, salt, fabric, des script bash, virtualenv, git hooks, etc.

Après, il y a ceux qui utilisent des VM, qui elles, ont leur propres outils d’automatisation type Vagrant.

Et comme ça ne suffit pas, des services ce sont mis en place pour faciliter la mise en prod dans le cloud comme Heroku, Gondor, dotCloud…

Malgré ça, Max fait encore beaucoup de trucs à la main parce que “ça marche jamais comme prévu”. Pas très scalable.

Dernièrement, grâce à notre cher Cortex, j’ai découvert un projet écrit en Go nommé Docker, qui propose encore une autre approche du problème.

J’ai été intrigué après avoir visionné une conf sur le sujet car le principe est très cool, mais aussi parce que j’ai bossé à une époque avec un des mecs de chez docker. Et ce gars est un monstre. Mais vraiment. Une brute. Le genre qui n’a pas besoin de souris ni de gestionnaire de fenêtre, mais qui peut vous régler votre problème en tapant d’une main tout en vous expliquant ce qu’il fait dans votre domaine d’expertise alors que ce n’est pas le sien. Je l’ai vu faire. C’est énervant.

Pour le moment, je dois dire que Docker est vraiment sympa. Petit tour du propriétaire.

C’est comme si chaque process avait sa mini VM

Pour faire simple, docker, c’est de la virtualisation légère.

Ça marche comme cela : on prend une image d’un Linux de base qu’on fait tourner dans docker, on lui installe de quoi faire tourner un process – par exemple redis – et on obtient une nouvelle image qui contient juste la différence avec l’image précédente. On fait ça avec plein d’autres process, et quand on a besoin de l’un d’entre eux, on lance l’image qui contient l’install de ce celui-ci.

Ce sont des images très légères, on peut en lancer 100 sur une même machine si on le souhaite. Mais elles sont parfaitement isolées : elles ont leur propre système de fichier, leurs propres arbres de processus, utilisateurs, permissions, ports… Donc si par exemple je fais un nginx compilé avec des extensions exotiques et une config zarb, je peux en faire une image puis l’utiliser sur n’importe quel serveur qui contient Docker.

Le but, c’est de créer plein de petits conteneurs légers et isolés de votre machine. Et au lieu de configurer chaque serveur, on envoie juste les conteneurs dont on a besoin, et on est certain qu’ils marchent partout pareil, peu importe la config à l’arrivée. On virtualise des services, qu’on combine, et non une machine complète.

Quand je vous dis que c’est léger, c’est VRAIMENT léger. A peine plus gourmand que le process sans la VM. En fait, un conteneur docker démarre presque instantanément, il n’y a pas de “boot” comme pour une vraie VM. Mais on en a les avantages car votre redis dockerisé marchera pareil sur une Fedora et une Ubuntu.

Docker ne marche que sur Linux pour le moment, et encore, sur un Linux pas trop vieux car il utilise une technologie récente, dont vous avez probablement peu entendu parlé : LXC.

Détail amusant : Docker est pas mal utilisé dans la communauté des devs sous Mac, qui du coup ont une VM Ubuntu dans laquelle ils font tourner Docker pour travailler. N’est-ce pas merveilleux ?

La démonstration obligatoire

Je ne vais pas vous laisser comme ça et vous demander de vous la mettre derrière l’oreille, donc exemple d’utilisation sous Bubuntoune.

Je suis en 14.04, mais je pense que ça marche pareil depuis la 12.04.

Installation :

$ sudo apt-get install docker.io

Cela va mettre la commande docker.io à votre disposition, mais sous d’autres systèmes, elle s’appelle juste docker. Perso je fais un

alias docker="sudo docker.io"

puisque de toute façon il faut toujours lancer cette commande avec les droits admin.

Ensuite, on va télécharger une image de base sur laquelle baser toutes nos images :

$ docker pull ubuntu

Docker.io, ce n’est pas juste un software, c’est aussi un service qui héberge les images. Vous pouvez bien entendu créer vos propres images de base, et héberger votre dépôt personnel d’images, mais on ne va pas se faire chier à faire ça ici. Prévoyez quelques gigas de place, Docker ne prend pas beaucoup de ressources CPU/RAM, mais par contre ça bouffe en espace disque.

Donc là, je demande la récupération des images “ubuntu”, qui vont nous servir d’images de base. Elles sont toutes faites et prêtes à l’emploie, que demande le peuple ?

Ça va downloader pendant pas mal de temps, puis vous aurez plusieurs images à votre disposition, que l’on peut lister ainsi :

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
ubuntu              12.04               8dbd9e392a96        4 months ago        131.5 MB (virtual 131.5 MB)
ubuntu              12.10               b750fe79269d        5 months ago        24.65 kB (virtual 180.1 MB)
ubuntu              latest              8dbd9e392a96        4 months ago        131.5 MB (virtual 131.5 MB)
ubuntu              precise             8dbd9e392a96        4 months ago        131.5 MB (virtual 131.5 MB)
ubuntu              quantal             b750fe79269d        5 months ago        24.65 kB (virtual 180.1 MB)

Vous faites votre marché : quelle image de base voulez-vous utiliser ?

La 12.04 est une LTS qui est déjà bien field testée, donc je vais prendre celle là.

Ensuite, pour lancer une commande avec, il faut faire docker run id_image commande. Ceci va lancer l’image, lancer la commande, et dès que la commande se termine, arrêter l’image :

$ docker run 74fe38d11401 echo 'YEAHHHHHHHHHHHHHH'
WARNING: Local (127.0.0.1) DNS resolver found in resolv.conf and containers can't use it. Using default external servers : [8.8.8.8 8.8.4.4]
YEAHHHHHHHHHHHHHH

Mesurons le temps pris en tout pour faire cela :

$ time -f "%e seconds" sudo docker.io run 74fe38d11401 echo 'YEAHHHHHHHHHHHHHH'
WARNING: Local (127.0.0.1) DNS resolver found in resolv.conf and containers can't use it. Using default external servers : [8.8.8.8 8.8.4.4]
YEAHHHHHHHHHHHHHH
0.45 seconds

Comme vous le voyez, démarrer un conteneur, c’est vraiment rapide. Mais faire un

echo

, ça ne sert pas à grand chose :)

Faisons quelque chose de plus utile : lançons un terminal bash et demandons un accès à stdin/stdout (-i) ainsi qu’un tty (-t) :

$ docker run -i -t 74fe38d11401 bash
WARNING: Local (127.0.0.1) DNS resolver found in resolv.conf and containers can't use it. Using default external servers : [8.8.8.8 8.8.4.4]
root@8195e92a5e62:/#

Et hop, comme le process ne se termine pas tant qu’on est connecté au tty, on a le conteneur qui continue de tourner, mais on a un terminal dessus, nous permettant d’entrer n’importe quelle commande.

Si on installait 0bin ?

D’abord, on a besoin de pip dans le conteneur:

# apt-get install python-pip

Notez qu’on est toujours root dans son conteneur. Après tout, c’est virtuel et isolé, donc pas de raison de se faire chier.

Ensuite, on tape dans pypi :

# pip install zerobin

Pas besoin de virtualenv, on est déjà dans un environnement isolé.

Faisons un petit fichier de config. On va avoir besoin de vim :

# apt-get install vim
# cd /home
# vi settings.py

On met des settings perso pour zerobin, histoire de montrer le principe de faire un conteneur avec un setup sur mesure (on peut, bien entendu, faire 1000 fois plus compliqué, c’est un exemple bande de bananes) :

# on le fait ecouter sur l'exterieur
HOST = "0.0.0.0"
# on change le menu de la page d'accueil
MENU = (
    ('Home', '/'),
    ('Contact', 'mailto:mysupermail@awesomesite.com')
)

Et on lance notre petit zerobin :

# zerobin --settings-file=settings.py

Et voilà, on a un zerobin qui tourne dans notre conteneur isolé.

On va sauvegarder tout ça. Sur notre machine hôte (donc pas dans le conteneur), on va sauvegarder notre nouvelle image. D’abord, on va trouver l’ID du conteneur qui tourne :

$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                     NAMES
8195e92a5e62        ubuntu:12.04        bash                37 minutes ago      Up 37 minutes                                 boring_mccarthy

Ensuite on commit ce conteneur pour obtenir une nouvele image. Pour faire simple, on va l’appeller “zerobin” :

$ docker commit 8195e92a5e62 zerobin

On peut alors tranquillement arrêter notre conteneur :

$ docker stop 8195e92a5e62

Maintenant on peut pusher notre image tout neuve sur un repo distant avec docker push pour pouvoir la puller plus tard. Il faut ouvrir un compte sur leur service ou créer son propre depot, je vous laisse trouver comment faire. Plus simplement, on peut juste dumper l’image avec docker save id_image > nom_image.tar, la copier sur une clé USB, et la recharger avec docker load < nom_image.tar.

Dans tous les cas, une fois sur le serveur, vous pouvez lancer votre image "zerobin", ici encore une fois avec la commande bash:

# docker run -t -i -p 7777:8000 zerobin bash

Cette fois, on rajoute une option de plus : -p 7777:8000 dit à docker de relier le port 7777 de ma machine au port 8000 (port de 0bin par défaut) de mon conteneur. Ainsi, si une app va sur le port 7777 de ma machine, elle va en fait parler au port 8000 du conteneur sans s'en rendre compte.

Du coup, je peux lancer mon petit zerobin depuis mon contenur :

# cd /home
# zerobin --settings-file=settings.py

Et voilà ! J'ai un zerobin qui marche, qui est préconfiguré, isolé et portable. Si je change de serveur, ou même d'OS, je peux juste reprendre le même conteneur et le relancer tel quel. Et je peux faire ça avec plein de process et les faire communiquer entre eux.

Capture d'écran de zerobin

J'accède à 7777 sur la machine, mais ça tape sur 8000 dans mon conteneur

Docker est un outil très riche, et vous vous doutez bien que je ne vous ai montré que le début.

Par exemple, vous voulez sans doute ne pas avoir à lancer bash, puis un cd, puis zerobin. Ça peut s'automatiser avec un dockerfile. Vous voulez aussi peut être lancer le conteneur en arrière plan, ça se fait avec l'option -d. Ou peut être voulez-vous un dossier partagé entre tous les conteneurs ? C'est possible avec l'option volume.

Parmi les usages vraiment intéressants de dockers : packager les services très custos comme les nginx ou ffmpeg compilés avec des options cryptiques, deployer son app django avec toutes les libs Python et les settings qui vont bien, remplacer une app à chaud en redirigeant juste le load balancer sur le nouveau conteneur, ou encore fournir un env de test à un client. Perso, rien que pour tester des nouveaux logiciels sans pourir ma machine, je trouve ça pratique : on a bien moins peur de faire un make install.

Je vous laisse prendre en main le nouveau joujou, j'ai des serveurs à planter.

flattr this!

Peut-on compiler un programme Python ?

dimanche 11 mai 2014 à 18:58

Sans répit, des hordes d’OP ont demandé sur Stackoverflow comment compiler un programme Python. La plupart du temps, pour des raisons d’obfuscation ou pour faire un joli .exe. Et inlassablement, les devs bienveillants leur répondaient que Python était un langage interprété, et donc non compilable.

Commença alors la lente fuite des impatients vers Go qui permettait de le faire si facilement.

Et puis arriva Cython, qui promettait de permettre de transformer du Python en C, afin de l’embarquer dans un autre code C, ou permettre des appels de C vers Python et inversement plus facile.

Cela visait les perfs, l’intégration, faire des dll/so en Python, et du coup, dans l’excitation, tout le monde a loupé un petit détail minuscule.

Les mecs avaient implémenté un compilateur Python => C complet.

En fait, Cython permet – et c’est la que c’est fun car c’est un effet de bord presque involontaire – de créer un programme compilé autonome en Python. Et avec la toolchain classique en plus.

Ça peut gérer les dépendances, et ça marche même avec des trucs compilés balèzes du genre PyQT.

Je sens le chapiteau se dresser sous la braguette, alors voici la démo…

Pour montrer qu’on ne fait pas que du “Hello World”, je vais utiliser des dépendances assez complexes : lxml (qui contient une extension C compilée), path.py, requests et docopt :

"""
    Download a file and save it to a location.
 
    Usage:
        downloader.py <target> [<location>]
"""
 
import requests
import docopt
 
from lxml.html import parse
from path import path
 
args = docopt.docopt(__doc__)
 
p = path(args['<location>']).realpath()
 
if p.isdir():
    p = p / path(args['<target>']).namebase
 
p.write_bytes(requests.get(args['<target>']).content)
 
title = parse(p).getroot().find('head').find('title').text
print('Downloaded "%s"' % title)

Je ne m’encombre pas de gestion d’erreurs, juste suffisamment d’appels pour faire travailler les dépendances.

Qui dit compilation, dit environnement. Pour ma part, je suis sous Ubuntu 14.04 et je vais me compiler un petit programme en Python 3, avec lesquels il me faut installer Cython et pip pour Python 3, de quoi compiler du C, et les dépendances de notre script :

$ sudo apt-get install gcc cython3 python-pip3 python3-lxml
$ pip3 install requests docopt path.py

Je n’ai même pas eu besoin d’installer les headers de lxml. Vive le Dieu des geeks.

L’utilisation de Cython dans notre cas est assez simple : on lui dit de transformer notre module en module C. --embed lui dit d’inclure l’interpréteur Python dedans.

$ cython3 downloader.py -o downloader.c --embed

On obtient un fichier C bien copieux :

$ head downloader.c
/* Generated by Cython 0.20.1post0 (Debian 0.20.1+git90-g0e6e38e-1ubuntu2) on Sun May 11 22:53:18 2014 */
 
#define PY_SSIZE_T_CLEAN
#ifndef CYTHON_USE_PYLONG_INTERNALS
#ifdef PYLONG_BITS_IN_DIGIT
#define CYTHON_USE_PYLONG_INTERNALS 0
#else
#include "pyconfig.h"
#ifdef PYLONG_BITS_IN_DIGIT
#define CYTHON_USE_PYLONG_INTERNALS 1

Il ne reste plus qu’à compiler ce dernier :

$ gcc -Os -I /usr/include/python3.4m  downloader.c -o download -lpython3.4m -lpthread -lm -lutil -ldl

J’ai mis -I /usr/include/python3.4m et -lpython3.4m car les headers de Python sont là dedans sur ma machine. Le reste, ce sont des options que j’ai copier / coller sur le Web car GCC a plus de flags qu’une ambassade américaine et que j’ai des choses plus importantes à retenir, comme par exemple la recette du guacamole.

On obtient un exécutable tout à fait exécutablatoire :

$ chmod u+x downloader
$ ./downloader # docopt marche :)
Usage:
       downloader.py <target> [<location>]

Et ça télécharge comme prévu :

$ ./downloader http://sametmax.com index.html
Downloaded "Sam & Max: Python, Django, Git et du cul | Deux développeurs en vadrouille qui se sortent les doigts du code"

Le script Python original fait 472 octets, le binaire obtenu 28,7 ko. Mais ce n’est pas standalone, puisque je n’ai pas demandé la compilation des dépendances (je viens de le tester sur une autre Ubuntu, il pleure que requests n’est pas installé). Je vous laisse vous faire chier à trouver comment faire pour répondre à votre cas de figure exact, mais ça implique d’utiliser cython_freeze.

Après tout, cet article est intitulé “Peut on compiler un programme Python ?” et non “Tuto pour rendre un script Python stand alone”. Je ne suis pas fou.

Apparemment ça marche sous Windows et Mac, même si je n’ai pas de quoi tester sous la main (bon, si j’ai une partition Windows, mais faut rebooter, tout réinstaller, merde quoi…).

Donc si vous voulez faire une application en Python et la rendre stand alone, l’obfusquer, pondre un exe pour rendre le téléchargement facile, rassurer les gens ou simplement ignorer les mises à jours des libs de l’OS, Cython est une bonne piste.

Petit bonus, votre programme sera plus rapide, car il saute l’étape d’interprétation. Bien entendu, vous récoltez les galères qui viennent avec la compilation, à savoir les différentes architectures, le linking vers les libs qui peuvent changer de place ou de versions, etc.

flattr this!

Le pandas c’est bon, mangez en

samedi 10 mai 2014 à 08:30

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

Bonjour à tous, jeunes tailleurs de bambou, suite à un article d’introduction à numpy par le grand maître Sam Les bases de Numpy, je m’en vais vous présenter une lib qui roxx du poney dans le calcul numérique : Pandas.

Pour faire simple, Pandas apporte à Python la possibilité de manipuler de grands volumes de données structurées de manière simple et intuitive, chose qui faisait défaut jusqu’ici. Il y a bien eu quelques tentatives comme larry, mais rien n’avait jamais pu égaler les fonctionnalités du langage R. Aujourd’hui Pandas y arrive en fournissant notamment le célèbre type dataframe de R, avec en prime tout un tas d’outils pour agréger, combiner, transformer des données, et tout ça sans se casser le cul. Que du bonheur!

Donc pour commencer, on installe le bousin par un simple : pip install pandas qui va si vous ne l’avez pas déjà fait, aussi télécharger/compiler/installer tout un tas de librairies dont numpy. Je vous conseille aussi d’utiliser ipython afin d’avoir une meilleure interaction avec les libs, notamment avec matplotlib en utilisant le switch ipython --pylab afin d’avoir directement les graphiques en mode interactif, ainsi que toute la bibliothèque numpy directement importée (en interne, ipython fera un import numpy as np).
On appelle la bête d’un simple:

In [1]: import pandas as pd

Oui je sais, la grande classe…

Tout est Series

Le type de base en Pandas et la Series. On peut le voir comme un tableau de données à une dimension:

In [2]: pd.Series(np.arange(1,5))
Out[2]: 
0    1
1    2
2    3
3    4
dtype: int64

La colonne de gauche représente l’index de la Series, normalement unique pour chaque entrée. La colonne de droite correspond à nos valeurs sur lesquelles nous voulons travailler.
L’index n’est pas forcément une suite d’entiers, et la Series peut être nommée:

In [3]: s=pd.Series([1,2,3.14,1e6], index=list('abcd'), name='ma_series')
In [4]: s
Out[4]: 
a          1.00
b          2.00
c          3.14
d    1000000.00
Name: ma_series, dtype: float64

A noter qu’un type-casting est systématiquement appliqué afin d’avoir un tableau de type uniforme (ici le data-type est du float64) qui peut être modifié (dans une certaine mesure) via Series.astype.

Le slicing c’est comme du fisting avec une bonne dose de vaseline, ça glisse tout seul:

In [5]: s['b':'d']
Out[5]: 
b          2.00
c          3.14
d    1000000.00
Name: ma_series, dtype: float64

Et oui, la sélection par indexation se fait sur… l’index de la Series. Ainsi s['a'] renverra la ligne dont l’index est ‘a’, mais Pandas est assez intelligent pour reconnaître si on lui demande de nous renvoyer des valeurs suivant l’ordonnancement du tableau de valeurs (comme numpy). Ainsi s[0] renverra la première valeur du tableau, qui ici est égale à s['a'].
Là où ça peut poser problème c’est quand notre index est une suite d’entiers, comme par exemple avec x=pd.Series(np.arange(1,5), index=np.arange(1,5)). Si vous demandez x[1], Pandas ne retrouve pas ses petits et vous retournera une zolie KeyError. Pour ces cas ambigus, il existe l’indexation stricte sur le nom de index de la Series via x.loc[nom_d'index], et l’indexation stricte sur le numéro d’ordre dans le tableau via x.iloc[numéro_d'ordre]. Essayez x.loc[0] et x.iloc[0] pour vous rendre compte de la différence.
Comme pour les préliminaires où il est bon de tâter un peu de tout avec de pénétrer dans le vif du sujet, laissons pour le moment l’indexation sur laquelle nous reviendrons plus tard, pour regarder d’un peu plus près comment faire joujou avec nos valeurs.

Un peu à la manière des arrays de numpy, on peut appliquer des fonctions mathématiques directement sur la Serie, ou passer par des fonctions raccourcis:

In [6]: s.sum()
Out[6]: 1000006.14

Ce qui revient au même que de faire np.sum(s) (rappelez vous, ipython avec –pylab a importé numpy dans la variable np).

La fonction describe est bien utile pour avoir un aperçu rapide de ce à quoi on a affaire:

In [7]: s.describe()
Out[7]: 
count          4.000000
mean      250001.535000
std       499998.976667
min            1.000000
25%            1.750000
50%            2.570000
75%       250002.355000
max      1000000.000000
Name: ma_series, dtype: float64

ce qui donne le nombre de données, la moyenne globale, la déviation standard, le minimum, les quartiles et le maximum de la Serie.

Le truc à retenir est que c’est l’index qui est primordial dans un grand nombre d’opérations. Ainsi si l’on veut additionner 2 Series ensemble, il faut que leurs index soient alignés :

In [8]: s2=pd.Series(np.random.rand(4), index=list('cdef'), name='autre_serie')
 
In [9]: s+s2
Out[9]: 
a               NaN
b               NaN
c          4.021591
d    1000000.401511
e               NaN
f               NaN
dtype: float64

Ici, seuls les index ‘c’ et ‘d’ étaient présents dans les 2 Series, Pandas effectuant avant l’opération d’addition une union basée sur l’index. Les autres entrées sont marquées en NaN, soit Not a Number. Une possibilité pour contrer ce phénomène et de dire à Pandas de remplacer les résultats manquants lors de l’union par 0:

In [10]: s.add(s2, fill_value=0)
Out[10]: 
a          1.000000
b          2.000000
c          4.021591
d    1000000.401511
e          0.563508
f          0.655915
Name: ma_series, dtype: float64

Mais si ce sont uniquement les valeurs qui nous intéressent, et non les indexations, il est possible de les supprimer:

In [11]: s.reset_index(drop=True)+s2.reset_index(drop=True)
Out[11]: 
0          1.881591
1          2.401511
2          3.703508
3    1000000.655915
dtype: float64

Oh joie, oh bonheur, je peux faire ce que je veux avec mes cheveux, enfin mes données…

Et PAN! dans ta frame

La DataFrame est l’extension en 2 dimensions des Series. Elle peut être vue comme un empilement de Series dont les index sont partagés (et donc intrinsèquement alignés), ou comme dans un tableur où les index sont les numéros de lignes et les noms des Series les noms des colonnes. Je ne vais pas décrire toutes les manières de créer une DataFrame, sachez juste qu’on peut les obtenir à partir de dictionnaires, de liste de liste ou de liste de Series, d’arrays ou de records numpy, de fichier excel ou csv et même depuis des bases de données, de fichier JSON ou HTML, et depuis le presse-papiers.

In [14]: genre=[['femme','homme'][x] for x in np.random.random_integers(0,1,100)]
 
In [15]: lateral=[['droite','gauche'][x] for x in np.random.random_integers(0,1,100)]
 
In [16]: age=np.random.random_integers(1,100,100)
 
In [17]: df=pd.DataFrame({'Genre':genre, 'Lateral':lateral, 'Age':age})
 
In [18]: df
Out[18]: 
    Age  Genre Lateral
0    69  femme  droite
1    46  homme  droite
2    89  homme  droite
3    14  homme  droite
4    74  homme  droite
5     5  femme  gauche
6    66  femme  droite
7    73  homme  gauche
8    99  homme  gauche
9    17  homme  gauche
    ...    ...     ...
 
[100 rows x 3 columns]

L’affichage par défaut depuis la version 0.13 est en mode ‘truncate’ où la fin de la DataFrame est tronquée suivant la hauteur de votre terminal, mais ça peut se changer via les divers paramètres à regarder sous pd.options.display.

Là donc nous avons une DataFrame de 3 colonnes (plus un index), chaque colonne étant en réalité une Serie :

In [20]: type(df['Age'])
Out[20]: pandas.core.series.Series

La sélection peut se faire de plusieurs manières, à chacun de choisir sa préférée (moi c’est Dafnée avec ses gros nénés). Ainsi pour avoir les 3 premières lignes des âges

In [21]: df['Age'][0:3]
Out[21]: 
0    69
1    46
2    89
Name: Age, dtype: int64
 
In [22]: df[0:3]['Age']
Out[22]: 
0    69
1    46
2    89
Name: Age, dtype: int64
 
In [23]: df.Age[0:3]
Out[23]: 
0    69
1    46
2    89
Name: Age, dtype: int64
 
In [24]: df.loc[0:3, 'Age']
Out[24]: 
0    69
1    46
2    89
3    14
Name: Age, dtype: int64

et oui, les noms de colonnes peuvent aussi être utilisés comme des attributs de la DataFrame. Pratique (qu’on n’attend pas).

L’une des forces de Pandas est de nous proposer tout un tas de solutions pour répondre à des questions existentielles tel que “quel est l’âge moyen par genre et par latéralité?”. Comme en SQL où la réponse sortirait du fondement d’une clause GROUP BY et d’une fonction d’agrégation, il en va de même ici :

In [25]: df.groupby(['Genre','Lateral']).aggregate(np.mean)
Out[25]: 
                     Age
Genre Lateral           
femme droite   45.476190
      gauche   49.208333
homme droite   41.571429
      gauche   55.823529
 
[4 rows x 1 columns]

OMG! c’est quoi c’t'index de malade? Un MultiIndex jeune padawan, qui te permettra d’organiser tes données par catégorie/niveau, et d’y accèder par le paramètre level dans pas mal de fonctions, mais ça je te laisse le découvrir par toi-même. Je ne vais pas non plus m’étendre plus sur toutes les possibilités offertes par les DataFrame, il y a tellement à dire qu’il faudrait plusieurs articles pour en faire le tour. Juste conclusionner sur la facilité d’intégration Pandas/matplotlib en vous disant que les Series et DataFrame ont une fonction plot permettant directement de visualiser les données, et ça, c’est juste jouissif.

Datetime dans les index

Je vous avez dit qu’on reviendrait sur les indexes, et là c’est pour rentrer dans le lourd (mais non pas toi Carlos). Pandas donc supporte l’indexation sur les dates, en reprenant et en élargissant les possibilités offertes par feu le module scikits.timeseries.
Prenons l’exemple de données (complètement bidons) fournies par un capteur à intervalle régulier sur un pas de temps horaire:

In [26]: dtindex=pd.date_range(start='2014-04-28 00:00', periods=96, freq='H')
 
In [27]: data=np.random.random_sample(96)*50
 
In [28]: df=pd.DataFrame(data, index=dtindex, columns=['mesure'])
 
In [29]: df.head()
Out[29]: 
                        mesure
2014-04-28 00:00:00  49.253929
2014-04-28 01:00:00   1.910280
2014-04-28 02:00:00   7.534761
2014-04-28 03:00:00  39.416415
2014-04-28 04:00:00  44.213409
 
[5 rows x 1 columns]
 
In [30]: df.tail()
Out[30]: 
                        mesure
2014-05-01 19:00:00  25.291453
2014-05-01 20:00:00  26.520291
2014-05-01 21:00:00  33.459766
2014-05-01 22:00:00  44.521813
2014-05-01 23:00:00  28.486003
 
[5 rows x 1 columns]

dtindex est un DatetimeIndex initialisé au 28 avril 2014 à 0 heure comportant 96 périodes de fréquence horaire, soit 4 jours. La fonction date_range peut aussi prendre en arguments des objets datetime purs au lieu de chaine de caractère (manquerait plus que ça…), et le nombre de périodes peut être remplacé par une date de fin.
Si l’on veut calculer, disons le maximum (horaire) par jour, rien de plus simple, il suffit de “resampler” en données journalières (‘D’ pour Day) et de dire comment aggréger le tout:

In [31]: df.resample('D', how=np.max)
Out[31]: 
               mesure
2014-04-28  26.298282
2014-04-29  28.385418
2014-04-30  26.723353
2014-05-01  24.106092
 
[4 rows x 1 columns]

Mais on peut aussi convertir en données quart-horaire (upsampling) en remplissant les données manquantes par celles de l’heure fixe:

In [32]:  df[:3].resample('15min', fill_method='ffill')
Out[32]: 
                        mesure
2014-04-28 00:00:00  49.253929
2014-04-28 00:15:00  49.253929
2014-04-28 00:30:00  49.253929
2014-04-28 00:45:00  49.253929
2014-04-28 01:00:00   1.910280
2014-04-28 01:15:00   1.910280
2014-04-28 01:30:00   1.910280
2014-04-28 01:45:00   1.910280
2014-04-28 02:00:00   7.534761
 
[9 rows x 1 columns]

Cependant, Pandas propose aussi d’autres possibilités non dépendantes des DatetimeIndex mais qu’il est bon de connaître, notamment celle pour remplacer les données manquantes avec fillna ou celle pour interpoler entre les données valides avec interpolate

In [52]:  df[:3].resample('15min')
Out[52]: 
                        mesure
2014-04-28 00:00:00  49.253929
2014-04-28 00:15:00        NaN
2014-04-28 00:30:00        NaN
2014-04-28 00:45:00        NaN
2014-04-28 01:00:00   1.910280
2014-04-28 01:15:00        NaN
2014-04-28 01:30:00        NaN
2014-04-28 01:45:00        NaN
2014-04-28 02:00:00   7.534761
 
[9 rows x 1 columns]
 
In [53]:  df[:3].resample('15min').fillna(df.mean())
Out[53]: 
                        mesure
2014-04-28 00:00:00  49.253929
2014-04-28 00:15:00  26.378286
2014-04-28 00:30:00  26.378286
2014-04-28 00:45:00  26.378286
2014-04-28 01:00:00   1.910280
2014-04-28 01:15:00  26.378286
2014-04-28 01:30:00  26.378286
2014-04-28 01:45:00  26.378286
2014-04-28 02:00:00   7.534761
 
[9 rows x 1 columns]
 
In [54]:  df[:3].resample('15min').interpolate()
Out[54]: 
                        mesure
2014-04-28 00:00:00  49.253929
2014-04-28 00:15:00  37.418016
2014-04-28 00:30:00  25.582104
2014-04-28 00:45:00  13.746192
2014-04-28 01:00:00   1.910280
2014-04-28 01:15:00   3.316400
2014-04-28 01:30:00   4.722520
2014-04-28 01:45:00   6.128641
2014-04-28 02:00:00   7.534761
 
[9 rows x 1 columns]

Voilà, j’espère que vous aurez plaisir à travailler avec cette librairie, il manquait vraiment un outil de cette trempe en Python pour l’analyse de données et je pense qu’on n’a plus trop grand chose à envier maintenant par rapport à des langages spécilisés. Je n’ai pas parlé de Panel qui est le passage à la troisième dimension, ni des possibilités d’export, notamment la df.to_html que je vous laisse le soin de découvrir.

A plus, et amusez vous bien avec votre bambou.

\o/

flattr this!

………………………………………… Arg

vendredi 9 mai 2014 à 12:11

Je suis en train de rendre visite à Max. Il va bien, il a toutes ses dents, la truffe humide et le poil soyeux.

Par contre :

⟩ ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=36 time=384 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=36 time=1134 ms
64 bytes from 8.8.8.8: icmp_seq=4 ttl=36 time=1351 ms
64 bytes from 8.8.8.8: icmp_seq=5 ttl=36 time=873 ms
64 bytes from 8.8.8.8: icmp_seq=6 ttl=36 time=1258 ms
64 bytes from 8.8.8.8: icmp_seq=7 ttl=36 time=1299 ms
64 bytes from 8.8.8.8: icmp_seq=8 ttl=36 time=1431 ms
64 bytes from 8.8.8.8: icmp_seq=9 ttl=36 time=1461 ms
64 bytes from 8.8.8.8: icmp_seq=10 ttl=36 time=1474 ms
64 bytes from 8.8.8.8: icmp_seq=11 ttl=36 time=1178 ms
64 bytes from 8.8.8.8: icmp_seq=12 ttl=36 time=1932 ms
64 bytes from 8.8.8.8: icmp_seq=13 ttl=36 time=2023 ms
64 bytes from 8.8.8.8: icmp_seq=14 ttl=36 time=1818 ms
64 bytes from 8.8.8.8: icmp_seq=15 ttl=36 time=1546 ms
64 bytes from 8.8.8.8: icmp_seq=16 ttl=36 time=1188 ms
64 bytes from 8.8.8.8: icmp_seq=17 ttl=36 time=1110 ms
64 bytes from 8.8.8.8: icmp_seq=18 ttl=36 time=998 ms
^C
--- 8.8.8.8 ping statistics ---

Et je vous raconte pas comme c’est chiant avec WordPress.

flattr this!

Quelle est la différence entre un bug et un virus ?

mercredi 7 mai 2014 à 15:25

Article ultra débutant, mais hey, encore un truc très peu expliqué sur Internet pour l’excuse bidon que “c’est évident”.

Un virus, c’est un programme malveillant

Il a été conçu dans le but de nuire en utilisant la machine qu’il infecte. C’est donc un logiciel en plus sur votre machine, et qui pose des problèmes. Attention, un virus ne va pas forcément ralentir votre ordinateur ou le faire planter. Les virus modernes vont justement, en général, essayer de garder votre ordinateur en bon état (parfois en désinstallant d’autres virus !), afin d’avoir confortablement l’usage de votre machine.

Le virus entre dans la catégorie des programmes dits “malwares”, c’est à dire des logiciels nuisibles. Mais il n’est pas le seul, il y en a un paquet d’autres qui ne sont pas des virus : spywares (logiciels qui vous espionnent), adwares (logiciels qui vous affichent de la publicité tout le temps), ransomwares (logiciels qui prennent vos données en otage). Ce sont tous des malwares, c’est à dire des programmes nuisibles, mais pas des virus.

Généralement un virus a pour but de prendre le contrôle de votre machine, soit pour vous voler des données, soit pour se propager, soit pour l’utiliser comme machine zombie. Dans ce dernier cas, votre machine servira à un pirate qui pourra l’utiliser pour attaquer d’autres machines, envoyer du spam avec ou simplement vendre sa puissance sur Internet.

Ne pensez jamais que votre machine n’est “pas intéressante” pour les pirates sous prétexte que vous ne faites rien de particulier avec votre ordinateur. À partir du moment où votre ordinateur est connecté à Internet, il est intéressant, il peut rapporter de l’argent de bien des façons à un attaquant, même sans que l’argent vienne (directement) de vous.

Pour se protéger des virus, il faut avoir TOUS ses logiciels à jour, mais aussi éviter d’utiliser des systèmes trop sensibles aux virus. Si vous devez utiliser l’un d’entre eux (par exemple Windows), il faut rajouter un antivirus ET un firewall, même si vous ne faites qu’afficher des pages web sur des sites de confiance.

Conficker est un virus.

Un bug, c’est une erreur humaine

Un bug n’est pas un programme en plus, c’est une erreur de conception qui existe dans un des programmes qui est déjà sur votre machine.

Les machines sont stupides, elles ne font que ce qu’on leur dit. Quand vous voyez une application fonctionner, cela n’est que le résultat de millions d’instructions écrites à la main par les hommes qui ont créé vos logiciels, et ces hommes sont faillibles. Parfois, ils font une erreur dans le programme, et cette erreur peut avoir tout un cas de conséquences inattendues : le programme ne fait pas ce que vous voulez, il le fait mais mal, il est lent, il plante, etc.

Tout, absolument TOUT sur votre machine, est un programme. Et TOUT peut contenir un bug. En fait, aujourd’hui les systèmes informatiques sont tellement complexes qu’il y a forcément des bugs sur votre machine. Mais souvent ce sont des bugs discrets, qui ne sont pas très graves.

Dites-vous qu’un téléphone ou une tablette, si cela vous parait simple à utiliser, est bien plus complexe qu’un avion ou une fusée dans son fonctionnement en détail. Pas parce que c’est techniquement plus poussé, mais parce qu’il y a tellement de couches et de parties qui interagissent entre elles. Et dans ce gros fouillis il y a des erreurs, les fameux bugs.

On peut difficilement se protéger des bugs. Garder vos logiciels à jour peut aider à corriger les bugs précédents, mais parfois en ajouter des nouveaux. Les bugs font partie de la vie, aucun système n’est parfait. En revanche, plus un système est simple et maitrisé, moins il a de chance de contenir de bug. Si les bugs vous font peur, choisir un produit qui ne fait qu’une seule chose, mais bien, ou choisir un produit éprouvé, vous permet d’espérer une expérience avec moins de bugs.

Heartbleed est un bug.

Subtilités

Un virus utilise souvent un bug pour attaquer votre système. Le virus a en effet un objectif (au moins celui de se reproduire), et le logiciel qu’il attaque n’est généralement pas conçu pour lui permettre de l’atteindre. Le virus va donc souvent utiliser un bug du système pour obtenir ce qu’il veut.

A l’inverse, un bug n’a pas d’objectif, c’est une erreur, et donc il ne peut pas “causer un virus”. Ça ne veut rien dire.

Généralement, tout ce petit monde va donc ensemble, un virus peut exploiter un bug, dans le but d’installer un adware sur votre machine, afin de gagner de l’argent via la publicité, un keylogger pour récupérer votre numéro de carte bleue et un rootkit pour garder votre machine sous contrôle. Il y a donc 5 choses ici : le virus, le bug, l’adware, le spyware et le rootkit. Ce sont 5 choses différentes, mais la raison pour laquelle vous en entendez souvent parler en même temps, c’est qu’elles sont liées.

Mythes

Mon ordinateur n’a pas de bug

C’est impossible. C’est comme dire qu’il n’y a pas de fourmis dans vote jardin. Ce qui est possible, c’est qu’un bug ne vous gène pas et que vous n’en ayez vu aucun.

Je ne peux pas attraper de virus, parce que je ne fais rien de dangereux

Vous n’avez aucune idée de ce qui est dangereux. Croyez-moi, si auparavant vous ne saviez pas la différence entre un bug et un virus, vous n’avez pas la compétence (ce n’est pas une insulte) pour savoir ce qui met en danger votre machine. Et donc, vous le faites.

Mon ordinateur tourne bien, je n’ai pas de virus

Les virus modernes essayent le plus souvent d’être discrets et de ne pas vous causer de souci pour éviter que vous ayez envie de les supprimer.

Mon ordinateur affiche plein de pubs, j’ai un virus

Il y a des tas d’autres malwares qui causent cela. Il y a très peu de chance que ce soit un virus. Ça ne veut pas dire qu’il ne faut pas traiter votre ordinateur, mais un anti-virus ne suffira pas. Il vous faut des programmes anti-malwares plus généralistes.

flattr this!