TD1: Introduction à Python


Antony Lesage
antony.lesage@u-psud.fr

Ce premier cours-TD à pour vocation d'introduire les bases du langage Python3 et d'initier à l'utilisation de la bibliothèque NumPy, dédiée au calcul numérique. Il s'inspire très largement du cours de Ruggero Cortini, dispensé l'an dernier en MSA, mais également des cours en Anglais de Robert Johansson et de Rajath Kumar, disponibles sur la plateforme GitHub:

Python

Python est un langage de programmation moderne, généraliste, orienté objet et de haut niveau.

Caractéristiques techniques :

  • typage dynamique : Pas besoin de définir le type des variables, des arguments ou des types de retour des fonctions, il est inféré à l'exécution.
  • gestion automatique de la mémoire : Pas besoin d'allouer ou de désallouer explicitement pour les variables et les tableaux de données. Pas de problème de fuite de mémoire.
  • interprété : Pas besoin de compilé le code. L'interpréteur Python lit et exécute directement le code python.

Avantages :

  • L'avantage principal est la facilité de programmer, réduisant le temps nécessaire au développement, débogage et maintien du code.
  • Le langage encourage plusieurs bonnes pratiques de progrmmation:
    • L'indentation comme syntaxe, définissant la portée d'un scope.
    • L'intégration de la documentation au code source.
  • Une bibliothèque standard fournie et un large nombre de paquets dédiés au calcul scientifique.

Inconvénients :

  • Puisque Python est un langage de programmation interprété et typé dynamiquement (i.e. lors de l'exécution), l'exécution du code python peut être lente comparée à des langages de programmation compilés et typés statiquement (i.e. lors de la compilation), tels que le C ou le Fortran.

Interpréteur IPython

IPython est un shell interactif qui pallie aux limitations de l'interpréteur python standard : python, et est efficace dans le cadre d'une utilisation scientifique de python. Il fournit une invite de commande interactive, plus user-friendly.

Pour exécuter un fichier Python (.py), il vous suffit d'invoquer l'interpréteur ipython en tapant dans l'invite de commande d'un terminal :

ipython programme.py

On peut aussi lancer l'interpréteur simplement en tapant ipython dans un terminal et écrire du code python de façon interactive. Ce qui peut être pratique pour tester rapidement du code.

Voici quelques fonctionnalités intéressantes qu'IPython fournit :

  • Historique des commandes, qui peut être parcouru à l'aide des flèches haut et bas du clavier.
  • Autocomplétion avec la touche TAB.
  • ...

Notebook Jupyter

Le notebook Jupyter est un environement HTML pour Python, similaire à Mathematic ou à Maple. Il fournit un environement organisé en cellules interactives qui peuvent être exécutées, permettant l'organisation et la documentation de calculs de façon structurée.

Le notebook jupyter peut être invoqué en se plaçant dans le répertoire contenant les fichiers notebook (.ipynb) et en tapant dans un terminal la commande :

jupyter notebook

Editeur Spyder

Spyder est un IDE MATLAB-like pour le calcul scientifique avec Python. Il permet d'éditer, d'exécuter et de déboguer du code dans un seul environement.

Spyder fournit entre autres :

  • Un editeur de code efficace avec coloration syntaxique, permettant de l'introspection dynamique de code, intégré au débogueur de Python.
  • Un exploreur de variables, une invite de commande IPython
  • Variable explorer, IPython command prompt.
  • Une documentation intégrée.

Fichier Python

En-tête

Un fichier source Python se termine par l'extension .py et débutera systématiquement par la ligne de code suivante :

# -*- coding: UTF-8 -*-

permettant d'utiliser l'encodage UTF-8 au sein du fichier source.

Commentaire

Le reste du temps, une ligne de code débutant par le caractère dièse (#) sera un commentaire :

# Ceci est un commentaire en Python

Variables et types primitifs

Python fournit des types de base, dits types primitifs :

Notation Type
bool booléen
int entier
float réel
str string

Les cellules si dessous sont éditables et exécutables. Vous pouvez exécuter une cellule à l'aide du bouton ou encore en vous servant des touches :

  • "Shift + Enter" : le code de la cellule est exécuté et le curseur à la cellule suivante.
  • " Ctrl + Enter" : le code de la cellule est exécuté et le curseur reste sur la même cellule.
  • " Alt + Enter" : le code de la cellule est exécuté et le notebook crée une nouvelle cellule immédiatement après.

Vous pouvez également ajouter une nouvelle cellule à l'aider du bouton .

In [1]:
# Booléen
b = True  # La majuscule est importante !
type(b)
Out[1]:
bool
In [2]:
# Entier
n = 1
type(n)
Out[2]:
int
In [3]:
# Réel
x = 1.0  # En réalité, dans la mémoire d'un ordinateur on ne peut stocker que des nombres décimaux !
type(x)
Out[3]:
float
In [4]:
# Chaine de caractère
s = "1.0"  # s = '1.0', fonctionne éngalement
type(s)
Out[4]:
str

Conversion de type

Grâce aux fontions int, float et str, on peut convertir le type d'une variable.

In [5]:
var = '1'
var = int(var)
var = float(var)
type(var)
Out[5]:
float

Opérations arithmétiques

Symbole Opération
+ Addition
- Soustraction
* Multiplication
/ Division
// Division partie entière
** Puissance
% Modulo
In [6]:
1 + 2
Out[6]:
3
In [7]:
1 - 2
Out[7]:
-1
In [8]:
1 / 2
Out[8]:
0.5

$ E(1/2) = 0 $

In [9]:
1 // 2
Out[9]:
0

$2^2$

In [10]:
2 ** 2
Out[10]:
4

$3 \equiv 1 [2]$

In [11]:
3 % 2
Out[11]:
1
Symbole Opération
+= Addition
-= Soustraction
*= Multiplication
/= Division
//= Division partie entière
**= Puissance
%= Modulo
In [12]:
x = 1.0
x += 9.0
print(x)

x -= 1.0
print(x)

x /= 2.0
print(x)

x //= 2.0
print(x)

x **= 4.0
print(x)

x %= 6.0
print(x)
10.0
9.0
4.5
2.0
16.0
4.0

Opérateurs de comparaison

Symbole Opération
== égal
!= différent
< inférieur
> supérieur
<= inférieur ou égal
>= supérieur ou égal
In [13]:
print('1 == 2 is', 1 == 2)
print('1 != 2 is', 1 != 2)
print('')

print('1 <  2 is', 1 <  2)
print('1 <= 2 is', 1 <= 2)
print('')

print('1 >= 2 is', 1 >  2)
print('1 >  2 is', 1 >= 2)
print('')
1 == 2 is False
1 != 2 is True

1 <  2 is True
1 <= 2 is True

1 >= 2 is False
1 >  2 is False

Opérations logiques

Symbole Opération
and ET logique
or OU logique
not Négation
In [14]:
True and False
Out[14]:
False
In [15]:
True or False
Out[15]:
True
In [16]:
not (True and False)
Out[16]:
True
In [17]:
not (True or False)
Out[17]:
False

Chaines de caractère (string)

En Python, les chaines de caractère peuvent aussi bien s'écrire entre simples guillemets (') qu'entre doubles guillemets (").

In [18]:
s1 = "We are the knights"
s2 = 'who say Ni'

print(type(s1))
print(type(s2))
<class 'str'>
<class 'str'>

Taille

Il existe une fonction len qui permet de connaitre la taille d'une chaine de caractère.

In [19]:
print('len(s1) =', len(s1))
print('len(s2) =', len(s2))
len(s1) = 18
len(s2) = 10

Concaténation

On peut concaténer deux chaines grâce à l'opérateur +.

In [20]:
print(s1 + s2)
print(s1 + ' ' + s2)  # ' '.join((s1, s2))
We are the knightswho say Ni
We are the knights who say Ni

Formatage

On peut formater les chaines grâce à l'usage combiné des accolades {} et de la méthode format.

In [21]:
print('{0}{1}{0}'.format('abra', 'cad'))
abracadabra

On peut faire de l'alignement.

In [22]:
print('{:03}'.format(  1))
print('{:03}'.format( 10))
print('{:03}'.format(100))
001
010
100

On peut tronquer l'affichage d'un nombre après la virgule.

In [23]:
print('{:0.4f}'.format(3.14159265359))
3.1416
In [24]:
print(round(3.14159265359, 4))
3.1416

Indexation et Slicing

On peut accéder à un caractère de la chaine grâce à la syntaxe : chaine[index].

In [25]:
sparta = 'This is Sparta!'
print(sparta[0], sparta[1], sparta[-2], sparta[-1])
T h a !

On peut extraire une chaine à l'aide de la syntaxe : chaine[start:stop:step].

In [26]:
print(sparta[0:-1:1])
This is Sparta
In [27]:
print(sparta[:4])  # [0:4] est implicite
This
In [28]:
print(sparta[5:7])
is
In [29]:
print(sparta[8:])  # [8:len(sparta)] est implicite
Sparta!
In [30]:
print(sparta[::2])
Ti sSat!
In [31]:
print(sparta[1:-1:2])
hsi pra

Conteneurs (liste, tuple, dictionnaire)

Liste

Les listes sont similaires aux chaines de caractère, hormis que chaque élément peut être de n'importe quel type. Lorsque l'on ajoute des éléments de types différents, Python construit automatiquement une liste polymorphe.

In [32]:
l = []  # Déclare une liste vide
In [33]:
l = [ 1, 2, 3 ]
print(l)
[1, 2, 3]

Ajoute un élément à la liste.

In [34]:
l.append(2)
print(l)
[1, 2, 3, 2]

Supprime la première occurence de la valeur 2 dans la liste.

In [35]:
l.remove(2)
print(l)
[1, 3, 2]

Insére un élément dans la liste avec la syntaxe : liste.insert(index, item).

In [36]:
l.insert(1, 2)
print(l)
[1, 2, 3, 2]

Supprime l'élément à l'index donné de la liste.

In [37]:
del l[3]
print(l)
[1, 2, 3]

Retourne le dernier élément en le retirant de la liste.

In [38]:
print(l.pop())
print(l)
3
[1, 2]

Le slicing fonctionne aussi sur les listes.

In [39]:
l = [ 1, 2, 3, 4, 5, 6, 7, 8 ]
print(l[2:-2:2])
[3, 5]

On peut créer très simplement une liste polymorphe. (Rappelez-vous de vos cours de C++, pour ceux qui en ont eu, ça demandait un peu plus travail en amont.)

In [40]:
l = [ 1, 2.0, '3', [4, 5] ]

print(l)
print('')
print(type(l[0]))
print(type(l[1]))
print(type(l[2]))
print(type(l[3]))
[1, 2.0, '3', [4, 5]]

<class 'int'>
<class 'float'>
<class 'str'>
<class 'list'>

Tuple (ou n-uplet)

Les tuples sont semblables aux listes, hormis qu'ils ne peuvent plus être modifiés une fois crées. On dit qu'ils sont immuables.

Il exite deux syntaxes, l'une avec des parenthèses...

In [41]:
t1 = (1, 2, 3)
print(t1, type(t1))
(1, 2, 3) <class 'tuple'>

... l'autre sans.

In [42]:
t2 = 4, 5, 6
print(t2, type(t2))
(4, 5, 6) <class 'tuple'>

On peut défaire un tuple, toujours en utilisant les parenthèses (ou pas)

In [43]:
x, y, z = t1
print(x, y, z)
1 2 3
In [44]:
(x, y, z) = t2
print(x, y, z)
4 5 6

On peut crée un nouveau tuple à partir d'autres tuples, avec l'opérateur +.

In [45]:
t1 + t2
Out[45]:
(1, 2, 3, 4, 5, 6)

Le slicing fonctionne toujours !

In [46]:
t1[::2]
Out[46]:
(1, 3)

Vérifiez le caractère immuable des tuples en essayant de modifier l'un des éléments de t1 (par exemple).

In [47]:
# t1[0] = 0

Dictionnaire

Les dictionnaires sont semblables aux listes, hormis que chaque élément est un couple : { clé: valeur }.

In [48]:
dictionaire = { 'x': 2, 'y': 4, 'z': 8 }
print(dictionaire)
{'x': 2, 'z': 8, 'y': 4}

On peut (par exemple) indexer à l'aide de caractères.

In [49]:
print(dictionaire['x'])
print(dictionaire['y'])
print(dictionaire['z'])
2
4
8

Si la clé n'existe pas, une nouvelle entrée est créée dans le dictionnaire.

In [50]:
dictionaire['a'] = 1
dictionaire['b'] = 3
dictionaire['c'] = 5
print(dictionaire)
{'b': 3, 'z': 8, 'c': 5, 'a': 1, 'x': 2, 'y': 4}

Structure de contrôle (if, elif, else)

À titre de comparaison, vous pouvez exécuter ce code C++ en local ou en ligne.

#include <iostream>
using namespace std;

int main() {
    const bool condition1 = false;
    const bool condition2 = false;

    cout << boolalpha;

    if (condition1) {
        cout << "condition1 est " << condition1 << endl;
    }
    else if (condition2) {
        cout << "condition2 est " << condition2 << endl;
    }
    else {
        cout << "aucune condition n'est vraie" << endl;
    }

    return 0;
}

Vous avez ici le code équivalent en Python.

In [51]:
condition1 = condition2 = False

if condition1:
    print('condition1 est', condition1)
elif condition2:
    print('condition2 est', condition2)
else:
    print("aucune condition n'est vraie")
aucune condition n'est vraie
In [52]:
if True:
    if True:  # Changez True en False et observez
        print('Second bloc')
    print('Premier bloc')
print('Bloc principal')
Second bloc
Premier bloc
Bloc principal

Boucles (for, while)

for

En Python, les boucles for sont utilisées conjointement avec des objets itérables, tels que les listes. L'objet qui parcourt l'objet itérable est appelé itérateur.

#include <iostream>
using namespace std;

int main() {
    for (int i = 0; i < 5; ++i) {
        cout << "i = " << i << endl;
    }

    return 0;
}
In [53]:
for i in range(5):
    print('i =', i)
i = 0
i = 1
i = 2
i = 3
i = 4
In [54]:
print(range(5))
range(0, 5)

On peut générer une liste depuis un objet range...

In [55]:
list(range(5))
Out[55]:
[0, 1, 2, 3, 4]

... ou un tuple.

In [56]:
tuple(range(5))
Out[56]:
(0, 1, 2, 3, 4)

On peut itérer directement sur les éléments d'une liste, ...

In [57]:
for it in [ 2, 4, 8, 10, 12 ]:
    print('it = {:2}'.format(it))
it =  2
it =  4
it =  8
it = 10
it = 12

..., mais aussi d'un tuple.

In [58]:
behhgk = ('Brout', 'Englert', 'Higgs', 'Hagen', 'Guralnik', 'Kibble')
for prenom in behhgk:
    print(prenom)
Brout
Englert
Higgs
Hagen
Guralnik
Kibble

La fonction enumerate retourne un couple (tuple) : (index, item).

In [59]:
for i, prenom in enumerate(behhgk):
    print(i, prenom)
0 Brout
1 Englert
2 Higgs
3 Hagen
4 Guralnik
5 Kibble

Liste en compréhension

On peut également générer une liste à l'aide d'une boucle for avec une syntaxe compacte.

In [60]:
[ i for i in range(-3, 3) if i != 0 ]
Out[60]:
[-3, -2, -1, 1, 2]

while

Un exemple simpliste

In [61]:
i = 0

while i < 5:
    print(i)
    i = i + 1
0
1
2
3
4

Un exemple un peu plus convainquant.

In [62]:
liste = [ i for i in range(4) ]

while liste:
    print(liste.pop())
3
2
1
0

Fonction

Définition

Une fonction en Python est définie en utilisant le mot-clé def suivi : du nom de la fonction, de la signature de type à l'intérieur de parenthèses () et d'un double point :.

In [63]:
def hello_world():
    print('Hello World!')
In [64]:
hello_world()
Hello World!

Docstring

Il n'est pas obligatoire, mais fortement recommandé d'écrire une docstring qui décrit le but et le fonctionnement de la fonction. La docstring s'écrit directement en dessous de la définition de la fonction, avant le code de la celle-ci.

In [65]:
def addition(x, y):
    """Retourne la somme des deux arguments `x` et `y`."""
    
    return x + y
In [66]:
addition(2, 3)
Out[66]:
5

Argument par défaut et argument par mot-clé

On peut également écrire des fonctions avec des arguments par défaut, ce qui signifie que si l'argument n'est pas renseigné lors de l'appel de la fonction, une valeur par défaut sera utilisée. La valeur par défaut d'un argument définit en rajoutant un signe = après le nom de ce dernier, dans le signature de type de la fonction.

In [67]:
def my_function(a, b=1, verbose=False):
    if verbose:
        print('Computing: {} ** {}'.format(a, b))
        
    return a ** b
In [68]:
my_function(2, 3)
Out[68]:
8

On peut définir explicitement la valeur d'un argument en précisant son nom lors de l'appel de la fonction.

In [69]:
my_function(2, verbose=True)
Computing: 2 ** 1
Out[69]:
2
In [70]:
my_function(verbose=True, b=3, a=2)
Computing: 2 ** 3
Out[70]:
8

Fonction lambda

In [71]:
carre = lambda x: x * x
carre(2)
Out[71]:
4
In [72]:
my_lambda_function = lambda b: my_function(3, b=b, verbose=True)
my_lambda_function(2)
Computing: 3 ** 2
Out[72]:
9

Module

La plupart des fonctionnalités en Python sont fournies sous forme de module. Un module est un fichier Python contenant des définitions et des déclarations. Par exemple, la bibliothèque stantard de Python est découpé en modules.

On peut importer le module math de la bibliothèque standard et utiliser les fonctions qui y sont définies.

In [73]:
import math

On peut obtenir plus d'information sur le module soit en tapant : help(math), soit en cherchant sur internet.

In [74]:
help(math)
Help on built-in module math:

NAME
    math

DESCRIPTION
    This module is always available.  It provides access to the
    mathematical functions defined by the C standard.

FUNCTIONS
    acos(...)
        acos(x)
        
        Return the arc cosine (measured in radians) of x.
    
    acosh(...)
        acosh(x)
        
        Return the inverse hyperbolic cosine of x.
    
    asin(...)
        asin(x)
        
        Return the arc sine (measured in radians) of x.
    
    asinh(...)
        asinh(x)
        
        Return the inverse hyperbolic sine of x.
    
    atan(...)
        atan(x)
        
        Return the arc tangent (measured in radians) of x.
    
    atan2(...)
        atan2(y, x)
        
        Return the arc tangent (measured in radians) of y/x.
        Unlike atan(y/x), the signs of both x and y are considered.
    
    atanh(...)
        atanh(x)
        
        Return the inverse hyperbolic tangent of x.
    
    ceil(...)
        ceil(x)
        
        Return the ceiling of x as an Integral.
        This is the smallest integer >= x.
    
    copysign(...)
        copysign(x, y)
        
        Return a float with the magnitude (absolute value) of x but the sign 
        of y. On platforms that support signed zeros, copysign(1.0, -0.0) 
        returns -1.0.
    
    cos(...)
        cos(x)
        
        Return the cosine of x (measured in radians).
    
    cosh(...)
        cosh(x)
        
        Return the hyperbolic cosine of x.
    
    degrees(...)
        degrees(x)
        
        Convert angle x from radians to degrees.
    
    erf(...)
        erf(x)
        
        Error function at x.
    
    erfc(...)
        erfc(x)
        
        Complementary error function at x.
    
    exp(...)
        exp(x)
        
        Return e raised to the power of x.
    
    expm1(...)
        expm1(x)
        
        Return exp(x)-1.
        This function avoids the loss of precision involved in the direct evaluation of exp(x)-1 for small x.
    
    fabs(...)
        fabs(x)
        
        Return the absolute value of the float x.
    
    factorial(...)
        factorial(x) -> Integral
        
        Find x!. Raise a ValueError if x is negative or non-integral.
    
    floor(...)
        floor(x)
        
        Return the floor of x as an Integral.
        This is the largest integer <= x.
    
    fmod(...)
        fmod(x, y)
        
        Return fmod(x, y), according to platform C.  x % y may differ.
    
    frexp(...)
        frexp(x)
        
        Return the mantissa and exponent of x, as pair (m, e).
        m is a float and e is an int, such that x = m * 2.**e.
        If x is 0, m and e are both 0.  Else 0.5 <= abs(m) < 1.0.
    
    fsum(...)
        fsum(iterable)
        
        Return an accurate floating point sum of values in the iterable.
        Assumes IEEE-754 floating point arithmetic.
    
    gamma(...)
        gamma(x)
        
        Gamma function at x.
    
    gcd(...)
        gcd(x, y) -> int
        greatest common divisor of x and y
    
    hypot(...)
        hypot(x, y)
        
        Return the Euclidean distance, sqrt(x*x + y*y).
    
    isclose(...)
        isclose(a, b, *, rel_tol=1e-09, abs_tol=0.0) -> bool
        
        Determine whether two floating point numbers are close in value.
        
           rel_tol
               maximum difference for being considered "close", relative to the
               magnitude of the input values
            abs_tol
               maximum difference for being considered "close", regardless of the
               magnitude of the input values
        
        Return True if a is close in value to b, and False otherwise.
        
        For the values to be considered close, the difference between them
        must be smaller than at least one of the tolerances.
        
        -inf, inf and NaN behave similarly to the IEEE 754 Standard.  That
        is, NaN is not close to anything, even itself.  inf and -inf are
        only close to themselves.
    
    isfinite(...)
        isfinite(x) -> bool
        
        Return True if x is neither an infinity nor a NaN, and False otherwise.
    
    isinf(...)
        isinf(x) -> bool
        
        Return True if x is a positive or negative infinity, and False otherwise.
    
    isnan(...)
        isnan(x) -> bool
        
        Return True if x is a NaN (not a number), and False otherwise.
    
    ldexp(...)
        ldexp(x, i)
        
        Return x * (2**i).
    
    lgamma(...)
        lgamma(x)
        
        Natural logarithm of absolute value of Gamma function at x.
    
    log(...)
        log(x[, base])
        
        Return the logarithm of x to the given base.
        If the base not specified, returns the natural logarithm (base e) of x.
    
    log10(...)
        log10(x)
        
        Return the base 10 logarithm of x.
    
    log1p(...)
        log1p(x)
        
        Return the natural logarithm of 1+x (base e).
        The result is computed in a way which is accurate for x near zero.
    
    log2(...)
        log2(x)
        
        Return the base 2 logarithm of x.
    
    modf(...)
        modf(x)
        
        Return the fractional and integer parts of x.  Both results carry the sign
        of x and are floats.
    
    pow(...)
        pow(x, y)
        
        Return x**y (x to the power of y).
    
    radians(...)
        radians(x)
        
        Convert angle x from degrees to radians.
    
    sin(...)
        sin(x)
        
        Return the sine of x (measured in radians).
    
    sinh(...)
        sinh(x)
        
        Return the hyperbolic sine of x.
    
    sqrt(...)
        sqrt(x)
        
        Return the square root of x.
    
    tan(...)
        tan(x)
        
        Return the tangent of x (measured in radians).
    
    tanh(...)
        tanh(x)
        
        Return the hyperbolic tangent of x.
    
    trunc(...)
        trunc(x:Real) -> Integral
        
        Truncates x to the nearest Integral toward 0. Uses the __trunc__ magic method.

DATA
    e = 2.718281828459045
    inf = inf
    nan = nan
    pi = 3.141592653589793

FILE
    (built-in)


Décommenter la ligne suivante, placez le curseur après le point . et appuyer sur la touche TAB.

In [75]:
# math.

Si on ne souhaite pas importer le module en entier, on peut se restreindre à importer que quelques fonctions.

In [76]:
from math import exp, cos, sin
In [77]:
exp(1), cos(0), sin(0)
Out[77]:
(2.718281828459045, 1.0, 0.0)

Remarquez lorsque l'on importe un module, les fonctions du module sont accessibles en tapant : module.fonction. Or, si on importe directement une fonction avec la syntaxe : from module import fonction On peut s'affranchir du module., au risque de conflits de nom avec les fonctions déjà présentes dans votre programme. On peut importer tout un module en s'affranchissant de l'encapsulation des fonctions dans le namespace du module grâce à au symbole * qui signifie "tout".

In [78]:
try:
    print(tan(1))
except:
    print("La fonction tan n'a pas encore été importée !")
La fonction tan n'a pas encore été importée !
In [79]:
from math import *
In [80]:
tan(1)
Out[80]:
1.5574077246549023

Besoin de plus d'information sur une fonction ? Vous pouvez vous servir de la fonction help.

In [81]:
help(atan2)
Help on built-in function atan2 in module math:

atan2(...)
    atan2(y, x)
    
    Return the arc tangent (measured in radians) of y/x.
    Unlike atan(y/x), the signs of both x and y are considered.

PyLab

Le module PyLab est une surcouche du module matplotlib qui permet de tracer des courbes.

In [82]:
%matplotlib inline
In [83]:
import pylab
In [84]:
x = [ 1, 3, 5, 7, 9 ]
y = [ 0, 2, 8, 2, 0 ]
pylab.plot(x, y)
pylab.show()

NumPy

Le module NumPy est utilisé dans pratiquement tous les calculs scientifiques faisant intervenir Python. C'est un module qui fournit des structures de données vectorielles et matricielles performantes. Le module est implémenté en C et en Fortran, il donne accès à de bonnes performances pour le calcul vectoriel (formulation en termes de vecteurs et de matrices). Ce qui permet de pallier à certaines lenteurs de Python, notamment sur les boucles.

In [85]:
import numpy as np

Dans le module NumPy, la terminologie utilisé pour les vecteurs et les matrices est array.

Il y a plusieurs façons d'initialiser un np.array, on peut le faire, par exemple, à partir :

  • d'une liste ou d'un tuple de Python
  • de fonctions dédiées à la génération de np.arrays telles que les fonctions : np.arange, np.linspace, ...
  • de la lecture de fichiers de données
In [86]:
a = np.array([ 1, 2, 3 ])
print(a, a.shape)

m = np.array([[ 1, 2 ], [ 3, 4 ]])
print(m, m.shape)
[1 2 3] (3,)
[[1 2]
 [3 4]] (2, 2)

Opération élément à élément

In [87]:
a = np.square(np.arange(1, 4))
print(a)
print(np.sqrt(a))
print('')

m = np.arange(1, 5).reshape(2, 2)
print(m)
print(m.transpose())  # m.T == m.transpose()
print('')

print(np.tan(m))
print('')

print(m[0,:])  # Affiche la première ligne
print(m[1,:])  # Affiche la seconde ligne
print('')

print(m[:,0])  # Affiche la première colonne
print(m[:,1])  # Affiche la seconde colonne
[1 4 9]
[ 1.  2.  3.]

[[1 2]
 [3 4]]
[[1 3]
 [2 4]]

[[ 1.55740772 -2.18503986]
 [-0.14254654  1.15782128]]

[1 2]
[3 4]

[1 3]
[2 4]

Broadcasting

Le terme broadcasting décrit comment NumPy traite des np.arrays avec des formes différentes lors d'opérations arithmétiques. Le broadcasting est soumis à certaines contraintes, le plus petit np.array est broadcasté sur tout l'autre np.array, si ils ont des formes compatibles. Le broadcasting fournit un moyen de vectoriser les opérations sur les np.arrays de telle façon que la boucle est réalisée en C plutôt qu'en Python.

Lorsque NumPy opère sur deux np.arrays, il compare leur forme à dimension à dimension, en commençant par les dernières. Deux dimensions sont compatibles si :

  1. elles sont égales, ou
  2. l'une d'entre elles vaut 1

La multiplication par un scalair.

In [88]:
print(2 * a)
print('')
print(3 * m)
[ 2  8 18]

[[ 3  6]
 [ 9 12]]

La multiplication de deux np.arrays de même forme.

In [89]:
print(a * a)
[ 1 16 81]

Addition de deux np.arrays dont l'un a une dimension valant 1.

In [90]:
a = np.arange(1, 4)
b = np.arange(1, 3)[:,np.newaxis]  # .reshape(-1, 1)
print(a, a.shape)
print(b, b.shape)
[1 2 3] (3,)
[[1]
 [2]] (2, 1)
In [91]:
a + b
Out[91]:
array([[2, 3, 4],
       [3, 4, 5]])

Exercices

Exercice 1 :

Implémenter en Python une fonction qui retourne la n-ième valeur de la suite de Fibonacci, ayant la signature suivante :

def fibo(n):

La suite est définie par récurrence comme suit :

$$ \begin{cases} \mathscr{F}_{n+1} &= \mathscr{F}_{n} + \mathscr{F}_{n-1} \\[2ex] \mathscr{F}_{0} &= 0 \\[2ex] \mathscr{F}_{1} &= 1 \\ \end{cases} $$

Exercice 2 :

  1. Implémenter en Python une fonction, à l'aide d'une boucle for, qui retourne la valeur de $n!$ ayant la signature suivante :

    def fact_for(n):
    
  2. Implémenter en Python une fonction, récursive, qui retourne la valeur de $n!$ ayant la signature suivante :

    def fact_rec(n):
    
  3. Implémenter en Python une fonction, à l'aide de NumPy, qui retourne la valeur de $n!$ ayant la signature suivante :

    def fact_npy(n):
    

    Vous pourrez vous servir notamment des fonctions : np.arange et np.prod.

  4. Comparer leur temps d'exécution en tapant dans une nouvelle cellule :
    %timeit fact_for(12)
    
    %timeit fact_rec(12)
    
    %timeit fact_npy(12)
    

Exercice 3 :

Implémenter en Python une fonction, à l'aide de NumPy, qui calcul le carré du rayon de giration d'une conformation x, à 1D, ayant la signature suivante :

def rayon_giration_2(x):

Le rayon de giration d'une conformation de taille n se calcule comme suit :

$$ r_g^2 = \frac{1}{2n^2} \sum_{i=1}^{n}\sum_{j=1}^{n} \left ( x_ i - x_j \right )^2 $$

Vous pourrez générer une conformation aléatoire à l'aide du sous-module np.random. La commande help(np.random) peut se montrer utile ou regarder la doc en ligne. Stack Overflow est également une excellente ressource !

Autant que faire se peut, évitez d'écrire des boucles en Python en rusant avec les fonctions de NumPy et le broadcasting.

Si vous en avez le loisir, réécrivez cette fonction à l'aide de boucles for et comparer leur temps d'éxecution !