Python, comme tous les langages de la Terre disposent des outils de bases pour faire des mathématiques. Il propose quelques fantaisies, comme la gestion des nombres complexes, rationnelles, ou décimales sans erreurs d'arrondies. Mais le problème n'est pas là, que ce soit pour l'expressivité ou les bibliothèques disponibles, Python n'a pas à rougir.
Python a fait des choix techniques, comme le code interprété et le typage faible, qui en fait un gros consommateur de mémoire, pour une vélocité perfectible. Pour du prototypage, ou du web, ce n'est pas un souci, les freins sont respectivement le temps de codage, et les IO (disques et réseaux). Pour faire des maths, c'est différent, c'est le processeur qui directement sollicité, ainsi que la RAM.
Python est un langage qui a une approche libérale du typage. Techniquement, il se range dans la catégorie typage fort . Il fait peu de conversion implicite de type , mais il ne force pas une variable à conserver le même type de valeur, les collections (list, set, map...) peuvent être hétérogènes. Les arguments d'une fonction n'ont pas de contraintes de types. Plutôt que vérifier une hérédité douteuse, Python utilise le duck typing : "si ça marche comme un canard, et que ça fait couin, c'est que c'est un canard", sans vérifier plus que ça.
Lors de l'exécution du code, l'interpréteur détermine au dernier moment ce qu'il faut effectivement faire comme action, en fonction du type des arguments. Le cout de cette vérification de dernière minute peut paraitre négligeable, mais lors de grosses itérations, le cumul de ces petits tests n'est absolument plus négligeable. Les "list comprehension" de Python permettent de limiter cet effet, en proposant une syntaxe très dense, un peu élitiste.
t = [2 ** n for n in range(8)]
Quand on commence à imbriquer les listes, la syntaxe devient assez vite illisible.
Pour la surconsommation en mémoire, c'est plus compliqué. En python, la collection couramment utilisée pour stocker un ensemble de valeurs est la list . Techniquement, une list contient une référence vers chacune des valeurs qu'elle contient. Chacune de ces valeurs est un objet se trouvant dans la pile. Python modélise tout en objet, il ne s'embarrasse pas de primitives, comme Java, avec ses int et ses Integer .
a = 1 a.__add__(2) # == 3
Si un élément apparait plusieurs fois dans la liste, juste sa référence sera dupliquée, pas sa valeur. Une liste pleine de doublons occupe ainsi moins de mémoire qu'une liste d'éléments uniques. Python propose un moyen plus efficace pour stocker des séries de nombres, les arrays . Une array se comporte à peu près comme une list , mais elle est typé, et ne peut contenir que ce type d'éléments. Elle utilise la mémoire de manière prévisible, 4 octets par int32, par exemple. On peut avoir un gain en mémoire de 1 à 10, en utilisant une array plutôt qu'une list . Concrètement, les arrays sont peu usités en Python, les lists conservent la vedette.
Ces deux optimisations ( list comprehension et array ) sont appréciables, mais insuffisantes pour effectuer des calculs en très grande quantité, comme l'algèbre linéaire aime tant. De toute façon, ce domaine informatique est normalisé depuis 1979 avec BLAS et optimisé avec amour et acharnement depuis plus de 30 ans, avec l'aide intéressé des fondeurs (Intel et AMD).
Numpy propose d'unifier tout ça en se basant sur des normes existantes, en utilisant comme implémentation des bibliothèques en C ou en Fortran, et en profitant de la syntaxe de python (comme la surcharge des opérateurs) pour rendre tout ça utilisable, agréable et expressif.
Numpy s'appuie sur sa propre notion d' array , qui ressemble à celle de Python, mais avec une taille fixe, et des types plus variés, ou même complexes (comparables aux struct du C). Pour ne pas avoir à utiliser les boucles de python, Numpy propose une multitude de traitements possibles sur un vecteur à une ou plusieurs dimensions. Le terme technique est tout simplement "vectorisation".
La syntaxe utilisée est dense, efficace, et provoque autant de fierté chez le développeur qui la maitrise qu'une belle expression régulière. De très belles astuces, utilisant la syntaxe de python, permettent de faire des choses étonnantes. Il est par exemple possible de sélectionner un sous-ensemble d'un vecteur, avec un range (la syntaxe debut:fin
), mais aussi un masque : un vecteur de booléen de même dimension.
import numpy as np # une array de 12 chiffres aléatoires entre 0 et 1 a = np.random.rand((12)) # le masque des valeurs inférieurs à 0.5 b = a < 0.5 # les valeurs inférieurs à 0.5 print a[b] # qui peut aussi s'écrire print a[a < 0.5]
Qui est l'équivalent de
from random import random a = [random() for i in range(12)] print [i for i in a if i < 0.5]
Le modèle mémoire utilisé par ces arrays est bien plus proche de ce qu'utilise le C ou même Fortran, que la version en pur python, ce qui rends les allers-retours entre C/Fortran et Python plus simple et plus efficace.
Numpy peut être compilé avec différentes implémentations de BLAS, et même utiliser les célèbres KML d'Intel capable d'utiliser au maximum les capacités de leurs processeurs. Les KML sont propriétaires, mais il y a une saine (et acharné) compétition pour des implémentations libres de BLAS. Implémentations qui se chargent du multicore, d'ailleurs, soulageant ainsi Python de sa frustrante limitation.
Il est possible de modéliser quantité de choses avec des vecteurs à une ou plusieurs dimensions. Beaucoup de bibliothèques s'appuient sur Numpy et ses arrays pour proposer des APIs de plus haut niveau, spécifiques aux différents besoins métiers.
Même PIL, vétéran du traitement d'images en Python, dans sa variante contemporaine Pillow , permet de faire des allers-retours avec Numpy.
Numpy n'est pas super ami avec pip, ce qui lui colle une réputation de pénible dans le monde des développeurs webs. C'est partiellement faux. Si on lui fournit les bibliothèques nécessaires (libblas-dev et liblapack-dev sous Debian et Ubuntu), et que l'on ne veut pas d'options spécifiques, pip (même dans un virtualenv), sait installer Numpy, en prenant bien son temps. Les gens pressés peuvent utiliser le paquet Debian, de très bonne facture.
Le prochain billet sera dédié à matplotlib, le troisième de la bande.