PROJET AUTOBLOG


Sam et Max

source: Sam et Max

⇐ retour index

Les nombres en Python

lundi 24 décembre 2012 à 18:43

Aller, un petit article pour débutant pour changer: la représentation des nombres en Python.

Les types de base

Il y a plus de types et notations numériques de base que vous ne le pensez:

>>> type(1)
<type 'int'>
>>> type(1.0)
<type 'float'>
>>> type(11111111111111111111)
<type 'long'>
>>> type(1j)
<type 'complex'>
>>> type(1e10)
<type 'float'>

1 est celui que tout le monde connaît le mieux. Il représente le type entier, c’est à dire sans virgule. C’est aussi celui qu’on utilise le plus puisqu’il sert aux indices, aux slicings, aux boucles, au compteurs, etc.

Il peut être négatif:

>>> -2 + 1
-1

Et permet la plupart des opérations mathématiques ordinaires:

>>> 1 + 1 # addition
2
>>> 1 - 1 # soustraction
0
>>> 4 / 2 # division
2
>>> 2 * 3 # multiplication
6
>>> 2 ** 3 # puissance
8

Ça va sans dire, mais ça va mieux en le disant, Python n’applique pas de transtypage automatique en dehors des types numériques. Comme disait ma maîtresse, on ne peut pas additionner des choux et des carottes, ni des strings et des bas nylons int:

>>> 1 + 1
2
>>> 1 + "1"
Traceback (most recent call last):
  File "<ipython-input-132-db0423a23ce2>", line 1, in <module>
    1 + "1"
TypeError: unsupported operand type(s) for +: 'int' and 'str'
 
>>> 1 + int("1")
2
>>> str(1) + "1"
'11'

Quand un entier est trop gros, il est transformé automatiquement en type long. Il n’y a rien à faire, Python gère ça tout seul. Un long, c’est juste un gros entier, un peu plus lent.

>>> import sys
>>> sys.maxint # la limite de transformation d'un int en long
9223372036854775807
>>> type(9223372036854775807)
<type 'int'>
>>> type(9223372036854775807 + 1)
<type 'long'>

Mais on peut en faire un soi-même si on se sent l’âme d’un créateur de long, même un tout petit. Il suffit de rajouter un L:

>>> 1L
1L
>>> type(1L)
<type 'long'>

Ce type n’existe plus en Python 3.

Les int et long sont limités à ce qui est entier. Pour tout ce qui a une virgule, on utilise les flottants, qui accepte les mêmes opérations:

>>> 0.1 - 2.3 * 4.5 ** 6.7 / 8.9
-6149.7096223012195
>>> type(-6149.7096223012195)
<type 'float'>

Tout nombre qui contient un . est automatiquement un float.

D’ailleurs, si on fait une opération entre un int et un float, on se retrouve avec un résultat en flottant:

>>> 1 + 1. # même pas besoin de chiffre après la virgule
2.0

En fait, la plupart des opérations mathématiques un peu avancées retournent un float:

>>> import math
>>> math.cos(math.pi)
1.0

Les floats ont aussi une autre particularité: on peut les représenter en notation scientifique:

>>> 6.02214078e23 # j'aimerais avoir une mole d'euros pour Noel
6.02214078e+23

Je ne l’ai jamais utilisé, et je n’ai aucune idée de si c’est utile pour nos amis scientifiques (y en a des biens). En tout cas, c’est là.

D’ailleurs, puisqu’on parle science, on est pas limité aux réels avec Python. On peut taper dans les nombres imaginaires (la notation utilise “j” et non pas “i”) :

>>> 1j + 1
(1+1j)
1
(1+1j)

Et il y a même un module math dédié:

>>> math.sqrt(-1) # racine carrée FAIL
Traceback (most recent call last):
  File "<ipython-input-99-0c96a175a174>", line 1, in <module>
    math.sqrt(-1) 
ValueError: math domain error
>>> import cmath # WIN
>>> cmath.sqrt(-1)
1j

Et tout ce petit monde est compatible ensemble:

>>> 0 + 1.0 + 9999999999999999999999999999 + 1j
(1e+28+1j)

La seule chasse gardée des entiers, c’est l’indexing et le slicing:

>>> l = range(10)
>>> l[1]
1
>>> l[1.0]
Traceback (most recent call last):
  File "<ipython-input-138-3837752eec1a>", line 1, in <module>
    l[1.0]
TypeError: list indices must be integers, not float
 
>>> l[1j]
Traceback (most recent call last):
  File "<ipython-input-139-7a8d62312841>", line 1, in <module>
    l[1j]
TypeError: list indices must be integers, not complex

L’éternel problème de la virgule qui part en couille

Généralement, un débutant apprenant la programmation va commencer comme ça:

>>> 4 / 2 # COOOL !
2
>>> 3 / 2 # WTF ?!!
1

Donc là il va sur les forums, ne fait aucune recherche, et pose la même question que tous les débutants ont posé. Et une bonne âme répond patiemment que si il avait pris le temps de taper 3 secondes sur Google, espèce de connard d’étudiant fumeur de joint qui joue à League of Legend dans l’amphi, il aurait su que l’opération de division en Python 2.7 est une opération de division entière. Elle vire donc tout ce qu’il y a après la virgule.

Pour y remédier, il y a plusieurs possibilités.

1 – Utiliser des floats :

>>> 3. / 2.
1.5

2 – Utiliser truediv :

>>> from operator import truediv
>>> truediv(3, 2)
1.5

3 – utiliser le comportement de Python 3 :

>>> from __future__ import division # c'est quand même la classe
>>> 3 / 2 # comportement naturel
1.5
>>> 3 // 2 # division entière
1

Utilisez la solution 1 dans le shell pour faire vite fait, la 2 si vous modifiez un programme existant, et la 3 quand vous créer un nouveau programme.

Bien, revenons à notre connard de joint qui fume de l’amphi en cours de League of Legend. Il va maintenant vouloir se faire la main, et se lancer dans la création d’applications qui manquent comme des annuaires, des todo lists et des gestionnaires de budget. Sur ces derniers, il va bien entendu manipuler des sous, et les sous, il faut que ça soit précis. Alors quand il tombe là dessus:

>>> 0.1 + 0.1 + 0.1 - 0.3 # ZERO !!!
5.551115123125783e-17
>>> print "O_o dah FUCK ?"
O_o dah FUCK ?

Il retourne sur le forum où on l’avait si gentiment aidé, pour refaire exactement ce qu’on lui avait dit de pas faire. Et le bon con de service (le geek de niveau intermédiaire qui est pas encore lassé de répondre aux débutants car il l’était il y a encore pas si longtemps alors il comprend, lui, pas comme ces snobs élitistes agressifs qu’il jure ne jamais devenir ce qu’il deviendra bien entendu après 6 mois de hotline gratuite sur le forum de commentcamarche.com) va lui répondre que si il avait cherché sur Google…

Il aurait donc vu que les ordinateurs représentent tout en base 2, et que certains nombres à virgules ne peuvent être qu’approximés.

Stupeur, effroi, affolement. Comment calculer le budget litière de sa coloc dans son super programme ?

Dans un autre langage, on lui recommanderait de tout multiplier par 100000 puis de tout diviser par 100000, pour être bien sûr.

En Python, il y a un module pour ça™

Décimaux et fractions

>>> from decimal import Decimal
>>> Decimal("0.1") + Decimal("0.1") + Decimal("0.1") - Decimal("0.3")
Decimal('0.0')

Joie. Bonheur. Victoire de l’homme sur la machine et le boudin blanc.

On peut même définir jusqu’à quel point on veut être précis (en gros à partir de quand il arrondit) :

>>> from decimal import getcontext
>>> getcontext().prec = 6 # largement suffisant si vous n'êtes pas Paypal ou le Fisc

Pour les nostalgiques de Javascript ou de Buzz l’éclair, on peut même créer Not A Number et l’infini:

>>> Decimal('NaN')
Decimal('NaN')
>>> Decimal('-Infinity')
Decimal('-Infinity')

(float() le permet aussi, mais je vous le déconseille : le résultat est dépendant de l’implémentation)

Je vous invite à lire la doc, car il y a pas mal de choses à savoir si on veut éviter certains pièges.

Bref, le module Decimal permet de calculer avec le minimum d’approximation, évitant les erreurs d’arrondie et vous infusant de ce sentiment (fugace) que vous maîtrisez ce que vous faites. Vous pouvez manipuler des sous. Enfin presque. Car il y a évidement ensuite la douloureuse question des devises, mais la réponse à ce problème est un à pip install de là et dépasse les considérations de cet article.

Donc, pour tout ce qui a beson d’être précis au poil de cul, vous allez utiliser Decimal. Ou pas.

Car Decimal est lent. 100 fois plus lent qu’un float environ. Alors, pour la compta, ça va. Pour le séquençage ADN d’une patate, qui je le rappelle, a plus de chromosomes que nous, la biatch, c’est moyen.

Et là, pour pallier ce genre de truc, il faut taper dans l’extension en C, donc installer un module comme cdecimal (qui a la même API, il suffit ensuite de remplacer les imports) ou encore plus rapide, gmpy (API différente).

Bon, j’en ai jamais eu besoin dans toute ma vie, Decimal me suffit largement. Il calcule toujours plus vite que moi sur Excel OpenOffice LibreOffice. Mais je suis sûr qu’il y a des biologistes qui ne peuvent pas faire sans, hein. Vu la communauté qu’il y a sur scipy, la lib de calculs scientifiques haute performance, je ne doute pas de la question.

Pour finir sur une note plus légère, sachez que Python permet aussi de manipuler des fractions.

>>> from fractions import Fraction
>>> Fraction(3, 2)
Fraction(3, 2)
>>> int(Fraction(3, 2) + Fraction(1, 2))
2

Si vous êtes dans un cas où vous manipulez beaucoup de “parts de”, c’est fort pratique.

Quelques bonnes raisons de plus d’utiliser iPython

dimanche 23 décembre 2012 à 19:01

Le shell Python est vraiment pratique pour expérimenter, apprendre le langage, tester un snippet vitos ou administrer son site à distance. Mais iPython, ola, iPython, il déchire sa génitrice avec une poutrelle en verre pilé.

Voici quelques commandes qui vous donneront moult raisons de plus pour très vite installer ce shell alternatif.

Autocall

Taper des parenthèses et des guillemets, c’est sooooooo 1995. iPython peut vous les rajouter automatiquement dans les appels de fonctions, il faut juste préfixer de “/”, “,” ou “;” votre appel de fonction pour qu’il fasse la conversion:

/f 1,2 => f(1,2)
,f 1 2 => f("1","2")
;f 1 2 => f("1 2")

Accès au shell système

Préfixez votre commande d’un “!” bien couillu, et iPython va vous exécuter ça dans le shell système (par exemple bash) et vous retourner le résulat:

>>> ! cat /etc/fstab | grep 'ext4'
UUID=e33c5b98-1570-44d6-a32f-5e7970e1e588 /               ext4    errors=remount-ro 0       1

Oui, oui, on est dans un shell Python là. Ou comment bien faire chier la coloration syntaxique de votre blog.

Et on peut mettre tout ça dans une variable et le traiter en Python derrière:

>>> files = !ls
>>> [f.upper() for f in files]
['BUREAU', 'DOCUMENTS', 'EXAMPLES.DESKTOP', 'IMAGES', 'MOD\xc3\xa8LES', 'MUSIQUE', 'PUBLIC', 'T\xc3\xa9L\xc3\xa9CHARGEMENTS', 'UBUNTU ONE', 'VID\xc3\xa9OS',]

A noter que tout est exécuté dans un autre process, ainsi !cd n’aura aucun effet. Mais tout est prévu: %cd et %pwd proxient tout ça vers os.chdir et os.getcwd :-)

Debugger

Entrez %timeit expression pour qu’il qu’iPython vous l’exécute 10000000 fois et vous donne sa performance.

>>> %timeit x=2**100
10000000 loops, best of 3: 22.5 ns per loop

Autre commande: %pdb vous lancera automatiquement votre débuggeur favoris si une exception se lève durant votre session.

Se faciliter la vie

%edit ouvre un editeur (par défault VI sous nunux, mais c’est configurable), et si vous sauvegardez, le contenu tapé est récupéré et exécuté par iPython. Génial pour les bouts de code trop relou à taper sur le prompt.

%gui qt|wx|gtk lance l’intégration de la main loop d’un des toolkits graphiques afin pour de pouvoir faire mumuse avec des widgets sans bloquer votre shell.

Vous étiez vous demandé pourquoi le prompt iPython était plein de In et de Out, et pas de >>> ? Parce que tout l’historique est numéroté, et accessible:

In [38]: 1 + 1
Out[38]: 2
 
In [39]: In[38]
Out[39]: u'1 + 1'
 
In [40]: Out[38]
Out[40]: 2

Mais vous pouvez aussi utiliser %doctest_mode qui donne à votre prompt un air de shell Python normal. L’avantage ? On peut copier le contenu d’un autre shell dedans (et donc d’une doctest) avec les chevrons !

In [49]: %doctest_mode
Exception reporting mode: Plain
Doctest mode is: ON
>>> >>> for pom in ('pom', 'pom', 'pom', 'pom'):
...     ...     print pom
...     ... 
pom
pom
pom
pom

Quel hébergement Web pour les projets Python ?

samedi 22 décembre 2012 à 14:14

Un lecteur m’a demandé il y a quelques temps quel hébergement choisir pour son projet Django, en ces termes poétiques:

Chez moi, je teste un peu les frameworks web python django et pyramid. Seulement une question demeure à laquelle je ne trouve pas de réponse probante : OK j’ai fait mon super site en python OK il demoule sur mon pc perso Mais maintenant, quel hébergeur utiliser ?

D’abord, je vous invite à consulter les listes des hébergeurs gratuits et payants sur le wiki officiel de Python.

Parmi les hosts gratuits, j’ai essayé et approuvé PythonAnywhere qui est parfait pour un petit projet. Il gère très bien WSGI donc on peut facilement y plugger Django, Bottle, ou Flask. Et pas besoin de savoir comment configurer un serveur, il y a juste quelques réglages et roulez jeunesse.

De réputation, il y a aussi AlwaysData en hosting gratuit. Certes je ne l’ai pas essayé, mais il est sponsor de pas mal d’événements Python et rien que pour ça, ça me donne envie de le soutenir. Par contre avec 10 Mo de disque, vous avez intérêt à pas viser trop gros.

Dans la liste des hébergeurs gratuits, il n’est pas fait mention de Google App Engine. Pourtant l’offre de base est bien gratuite, même si si ça devient payant assez vite. Il faut aussi savoir que ce n’est pas une installation Python standard, et qu’il n’y a pas de base de données autre que l’API Big Data de Google. Bref, c’est assez particulier.

Néanmoins, une fois que votre projet dépote, un hébergeur gratuit ne suffit plus. Dans un premier temps, vous pouvez vous lancer dans un hébergeur pas cher, mais sur un serveur dédié. Ca demande un peu de sysadmin, mais vous êtes plus libres.

Online.net et sa fameuse offre Dedibox est probablement un des meilleurs rapport qualité / prix puisque vous aurez un serveur dédié pour 9 euros / mois. C’est quand même pas la mer à boire de débourser 10 balles, et on est tranquille. Ce n’est pas un hasard, c’est une filiale d’Iliad qui n’est autre que la maison mère de Free. C’est une super opportunité pour se faire la main, car on s’en branle un peu de tuer un serveur à 10 euros, donc on peut s’amuser avec, apprendre à config redis, optimiser PosGres, faire un proxy VPN ou un spam bot via Tor. Très chouette.

Alternativement, vous pouvez faire confiance au français Gandi. C’est un poil plus cher, mais leur service est excellent, et c’est une boîte qui a une très belle éthique. Vous aurez alors un serveur dédié, mais virtualisé, donc vous pourrez augmenter la puissance a posteriori alors qu’avec la dédiboule, vous êtes bon pour migrer tout le serveur.

Après, il faut carrément taper dans les services près des back bones si on veut monter dans les tours. En ce moment, on aime pas mal Leaseweb. Là on aligne deux zéros mensuels par serveurs sur la facture, mais c’est déjà des jolis bébés.

Pour finir, vous voudrez peut-être passer en mode Cloud, et dans ce cas Rackspace et WebFaction ont une très bonne réputation, particulièrement en terme de support. Leur facture est par contre salée, mais le nuage, c’est toujours cher au kilo.

Si vous êtes du genre à aimer les trucs fashions et bien intégré, Heroku et Gondor ont des solutions spécialisées autour de Python. Tout ce qui est déploiement se fait apparemment les doigts dans le pif si vous êtes du genre à pas aimer le sysadmin, avec intégration du versioning et des migrations. Bref, le truc hype de la vibes du flex. Enfin c’est ce qu’on dit, car on a pas testé. On aime avoir le contrôle sur nos machines.

Enfin, mention spéciale à Cinfu qui propose des serveurs payables en Bitcoin. Toujours utile pour créer un site de phishing. Ils interdisent les nœuds Tor par contre.

Heapq, le module Python incompris

vendredi 21 décembre 2012 à 18:23

Guido Van Rossum, notre-Dieu-à-tous-beni-soit-il, a un jour accepté de faire une session de questions-réponses publique, dans laquelle un petit malin lui a demandé “Comment ordonner un million d’entiers 32 bits dans 2Mo de Ram avec Python“.

Ca aurait été sur ce blog, le mec se serait pris un tampon “drozophiliafucker” dans la gueule et ça aurait été plié. Mais quand on est BDFL et, à l’époque, employé chez Google, on est obligé de donner une réponse un peu sérieuse.

Si vous avez suivi le lien précédent, vous verrez que sa réponse implique un obscure module appelé heapq. Si vous allez sur la doc du-dit module, vous verrez qu’elle implique une obscure explication innomable et vous aller retourner à la vidéo de porn que vous avez laissé bufferiser en haute résolution afin de pouvoir voir les grains de beauté qui longent le pourtour anal de la principale protagoniste. Respirons.

Il y a une explication rationnelle à tout cela

heapq est un algorithme qui organise une liste sous forme d’arbre binaire. Vous voyez c’était simple non ?

Nan je déconne, je vais pas vous laisser avec ça quand même.

Le module heapq permet d’utiliser le type “list” pour y ajouter ou retirer les éléments de telle sorte qu’ils soient toujours dans l’ordre.

Sous le capot, ça marche effectivement avec un arbre binaire, mais on s’en branle. Tout ce qu’il faut comprendre c’est:

>>> from heapq import heappush
>>> l = []
>>> heappush(l, 69)
>>> heappush(l, 42)
>>> heappush(l, 2048)
>>> heappush(l, -273.15)
>>> l # la liste est ordonnée en arbre binaire...
[-273.15, 42, 2048, 69]
>>> for x in xrange(len(l)): # et on depop pour itérer dans l'odre
    print heappop(l)
...     
-273.15
42
69
2048

Donc on a une liste, on lui met des éléments dans la tronche dans n’importe quel ordre, et bam, on peut itérer dans le bon ordre sans avoir à rien faire. Et cette insertion est assez rapide (O(lg n) pour les tatillons). Le parcours l’est également (de l’ordre de O(n log n)).

Et donc c’est très pratique pour trouver les x plus petits éléments (ou les plus grands), implémenter des queues de priorité, etc.

Exemple de queue de priorité, (courageusement volé d’ici):

import heapq
 
class PriorityQueue(list):
 
    def __init__(self, data):
        super(Heap, self).__init__()
        for i, x in enumerate(data):
            self.push(i, x)
 
    def push(self, priority, item):
        """
            On push en rajoute une priorité
        """
        heapq.heappush(self, (priority, item))
 
    def pop(self):
        """
            On pop en retirant la proprité
        """
        return heapq.heappop(self)[1]
 
    def __len__(self):
        return len(self)
 
    def __iter__(self):
        """
            Comme on a une méthode next(), on peut se retourner soi-même.
            Ainsi la boucle for appelera next() automatiquement. 
        """
        return self
 
    def next(self):
        """ 
           On depop la liste du plus petit au plus grand.
        """
        try:
            return self.pop()
        except IndexError:
            raise StopIteration
 
>>> l = PriorityQueue(("azerty"))
>>> l
>>> l.push(100, 'après')
>>> l.push(-1, 'avant')
>>> l.push(5, 'pendant')
>>> for x in l:
...     print x
...     
avant
a
z
e
r
t
pendant
y
après

Et le plus beau, c’est qu’on peut prendre plusieurs itérables ordonnés, et utiliser heapq.merge pour obtenir un générateur (qui ne charge pas tout en mémoire d’un coup) qui va permettre d’iterer de manière ordonnée sur tous les éléments.

>>> import heapq
>>> l1 = sorted([random.randint(0, 1000) for x in xrange(5)])
>>> l2 = sorted([random.randint(0, 1000) for x in xrange(5)])
>>> l3 = sorted([random.randint(0, 1000) for x in xrange(5)])
>>> list(heapq.merge(l1, l2, l3))
[52, 59, 60, 171, 174, 262, 336, 402, 435, 487, 557, 645, 899, 949, 996]

Notez que ce n’est pas du tout la même chose que de concaténer les listes:

>>> l1 + l2 + l3
[59, 174, 336, 487, 996, 52, 171, 557, 645, 949, 60, 262, 402, 435, 899]

Car des élements de l2 peuvent être inférieurs à ceux de l1. heap.merge nous ordonne tout bien correctement. C’est l’équivalent de sorted(l1 + l2 + l3), sauf que ça ne charge pas tout en mémoire:

>>> heapq.merge(l1, l2, l3)
<generator object merge at 0x0314F238>

Alors que sorted(), charge tout en mémoire:

>>> sorted(l1 + l2 + l3)
[52, 59, 60, 171, 174, 262, 336, 402, 435, 487, 557, 645, 899, 949, 996]

Bien entendu, ça marche avec des listes créées avec
heapq, ainsi:

>>> l1 = []

>>> for x in xrange(5): heappush(l1, random.randint(0, 1000))

>>> l2 = []

>>> for x in xrange(5): heappush(l2, random.randint(0, 1000))

>>> l3 = []

>>> for x in xrange(5): heappush(l3, random.randint(0, 1000))

>>> list(heapq.merge(l1, l2, l3))

[31, 40, 133, 360, 504, 508, 513, 679, 645, 792, 838, 413, 765, 886, 924]

(Grosse connasserie de ma part, faites comme si vous aviez rien vu.)

Quand on a de grosses quantités de données à trier, c’est très pratique, car l’effort de tri est répartie à chaque insertion, et à chaque itération pendant le parcours, pas concentré sur de gros appels de sorted().

On peut aussi récupérer des trucs du genre, les n plus petits / grands éléments sans tout coder à la main:

>>> l = [random.randint(0, 1000) for x in xrange(100)]
>>> heapq.nsmallest(4, l)
[0, 2, 4, 7]
>>> heapq.nlargest(3, l)
[999, 996, 983]

Et c’est beaucoup plus efficace que de le faire soi-même.

J’en profite pour rappeler au passage que tous les objets en Python sont ordonnables:

>>> (1, 2) > (2, 1)
False
>>> (1, 2) < (2, 1)
True
>>> "a" > "b"
False
>>> "a" < "b"
True

Et qu’en définissant les méthodes __eq__, __lt__, __gt__, etc., on peut donner un moyen de comparer n’importe quelle classe.

Bref, pour tous les besoins de classement, de priorisations, d’ordonnancement, de notion de plus petits, de plus grands, etc. qui concernent un gros jeu de données, heapq est le module qu’il vous faut.

Et là je percute que ça fait quelque temps déjà que je fais des articles élitistes pour des uses cases de 1% de la population. Donc les prochains tutos concerneront des trucs plus terre à terre. Parce que, je fais le malin là, mais je savais pas à quoi servait heapq il y a 3 semaines.

xoxo les filles

Implémenter une fenêtre glissante en Python avec un deque

jeudi 20 décembre 2012 à 20:02

Quand j’entends deque, je pense plus à un deck de Magic de Gathering qu’à un module Python, mais aujourd’hui j’utilise beaucoup plus le dernier que le précédent.

On a déjà vu comment implémenter l’itération par morceaux sur un itérable de n’importe quelle taille. Grâce au deque, on peut aussi facilement créer une fenêtre glissante.

Qu’est-ce qu’une fenêtre glissante ?

Il s’agit de parcourir un itérable par tranches de “n” élements, en faisant en sorte que l’insertion d’un nouvel élément dans la tranche implique la supression du premier entré. Mouai, dit comme ça c’est pas clair. Un p’tit exemple ?

Fenêtre glissante de 3 éléments sur une liste de 10 éléments, à la mano:

>>> l = range(10)
>>> l
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> l[:3]
[0, 1, 2]
>>> l[1:4]
[1, 2, 3]
>>> l[2:5]
[2, 3, 4]
>>> l[3:6]
[3, 4, 5]
...

A ne pas confondre avec le parcours par morceaux, qu’on déjà traité, et qui donne ça:

[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> l[0:3]
[0, 1, 2]
>>> l[3:6]
[3, 4, 5]
>>> l[6:9]
[6, 7, 8]
...

Et bien sûr, comme d’hab, vous allez dire “Cher Sam, malgré ton génie qui n’a d’égal que ta beauté et l’incohérence de cette remarque, je ne vois pas vraiment à quoi ça sert.”

Et bien essentiellement dans les situations où un élément est dépendant des précédents, par exemple pour répondre à des questions existentielles comme “quel est le premier élément qui est la somme des 5 le précédant dans cette liste ?”. Ça n’arrive certes pas tous les jours, mais fuck, j’avais pas de meilleur exemple en tête, là, tout de suite.

Qu’est-ce qu’un deque ?

Un deque est une structure de données qui permet d’implémenter des comportements de types piles, files, FIFO, LIFO, et autres pipos avec efficacité. Traduit en langage de tous les jours, c’est comme une liste, mais:

On va donc l’utiliser comme acumulateur ou pour faire des files d’attentes.

Démonstration:

>>> from collections import deque
>>> ze_deck = deque(maxlen=3) # on limite le deque à 3 éléments
>>> ze_deck.append(1)
>>> ze_deck
deque([1], maxlen=3)
>>> ze_deck.append(2)
>>> ze_deck
deque([1, 2], maxlen=3)
>>> ze_deck.append(3)
>>> ze_deck
deque([1, 2, 3], maxlen=3)
>>> ze_deck.append(4) # ... et MAGIQUEMENT !
>>> ze_deck
deque([2, 3, 4], maxlen=3)
>>> for x in ze_deck:
...     print x
...     
2
3
4

Bam le dernier élément est squizzé, et tout ça rapidement et efficacement. Vous voyez où je veux subtilement en venir ?

Et finalement: comment on utilise l’un pour faire l’autre ?

>>> from collections import deque
>>> from itertools import islice
>>> 
>>> def window(iterable, size=2):
...         iterable = iter(iterable)
...         d = deque(islice(iterable, size), size)
...         yield d
...         for x in iterable:
...                 d.append(x)
...                 yield d
...         
>>> for x in window('azertyuiop'):
...         print x
...     
deque(['a', 'z'], maxlen=2)
deque(['z', 'e'], maxlen=2)
deque(['e', 'r'], maxlen=2)
deque(['r', 't'], maxlen=2)
deque(['t', 'y'], maxlen=2)
deque(['y', 'u'], maxlen=2)
deque(['u', 'i'], maxlen=2)
deque(['i', 'o'], maxlen=2)
deque(['o', 'p'], maxlen=2)

Vous noterez au passage que nous faisons ici un usage malin des générateurs, ce qui nous permet de retourner un itérable sans tout charger en mémoire et de slider sur notre window avec classe.

Ainsi on peut se la peter en faisant des trucs compliqués en une ligne comme:

>>> {tuple(d): i for i, d in enumerate(window(xrange(10), 3))}
{(5, 6, 7): 5, (0, 1, 2): 0, (7, 8, 9): 7, (6, 7, 8): 6, (1, 2, 3): 1, (3, 4, 5): 3, (2, 3, 4): 2, (4, 5, 6): 4}

Ce qui n’a absolument aucun intérêt, mais si vous comprenez ce que ça fait, alors vous avec bien masterisé Python.

I'm richer than you! infinity loop