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 est un langage de programmation moderne, généraliste, orienté objet et de haut niveau.
Caractéristiques techniques :
Avantages :
Inconvénients :
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.
**Astuce**
Vous pouvez passer l'argument `-i` à l'interpréteur ipython pour le garder ouvert après l'exécution du fichier source : `ipython -i programme.py`
Voici quelques fonctionnalités intéressantes qu'IPython fournit :
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
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 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.
**Remarque**
Cette ligne de code n'est pas nécessaire dans un notebook !
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
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 :
Vous pouvez également ajouter une nouvelle cellule à l'aider du bouton .
# Booléen
b = True # La majuscule est importante !
type(b)
# Entier
n = 1
type(n)
# 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)
# Chaine de caractère
s = "1.0" # s = '1.0', fonctionne éngalement
type(s)
**Remarque**
Vous remarquerez que l'interpréteur Python a inféré le type des variables `b`, `n`, `x` et `s`.
Python fournit une fonction qui permet de se renseigner sur le type d'une variable. Cette fonction s'appelle sombrement : `type`.
Grâce aux fontions int
, float
et str
, on peut convertir le type d'une variable.
var = '1'
var = int(var)
var = float(var)
type(var)
Symbole | Opération |
---|---|
+ | Addition |
- | Soustraction |
* | Multiplication |
/ | Division |
// | Division partie entière |
** | Puissance |
% | Modulo |
1 + 2
1 - 2
1 / 2
$ E(1/2) = 0 $
1 // 2
$2^2$
2 ** 2
$3 \equiv 1 [2]$
3 % 2
Symbole | Opération |
---|---|
+= | Addition |
-= | Soustraction |
*= | Multiplication |
/= | Division |
//= | Division partie entière |
**= | Puissance |
%= | Modulo |
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)
**Remarque**
La fonction `print` permet d'afficher la valeur, d'une variable ou une chaine de caractère (formatée).
Symbole | Opération |
---|---|
== | égal |
!= | différent |
< | inférieur |
> | supérieur |
<= | inférieur ou égal |
>= | supérieur ou égal |
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('')
Symbole | Opération |
---|---|
and | ET logique |
or | OU logique |
not | Négation |
True and False
True or False
not (True and False)
not (True or False)
En Python, les chaines de caractère peuvent aussi bien s'écrire entre simples guillemets (') qu'entre doubles guillemets (").
s1 = "We are the knights"
s2 = 'who say Ni'
print(type(s1))
print(type(s2))
Il existe une fonction len
qui permet de connaitre la taille d'une chaine de caractère.
print('len(s1) =', len(s1))
print('len(s2) =', len(s2))
On peut concaténer deux chaines grâce à l'opérateur +
.
print(s1 + s2)
print(s1 + ' ' + s2) # ' '.join((s1, s2))
On peut formater les chaines grâce à l'usage combiné des accolades {}
et de la méthode format
.
print('{0}{1}{0}'.format('abra', 'cad'))
On peut faire de l'alignement.
print('{:03}'.format( 1))
print('{:03}'.format( 10))
print('{:03}'.format(100))
On peut tronquer l'affichage d'un nombre après la virgule.
print('{:0.4f}'.format(3.14159265359))
print(round(3.14159265359, 4))
On peut accéder à un caractère de la chaine grâce à la syntaxe : chaine[index]
.
sparta = 'This is Sparta!'
print(sparta[0], sparta[1], sparta[-2], sparta[-1])
On peut extraire une chaine à l'aide de la syntaxe : chaine[start:stop:step]
.
print(sparta[0:-1:1])
print(sparta[:4]) # [0:4] est implicite
print(sparta[5:7])
print(sparta[8:]) # [8:len(sparta)] est implicite
print(sparta[::2])
print(sparta[1:-1:2])
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.
l = [] # Déclare une liste vide
l = [ 1, 2, 3 ]
print(l)
Ajoute un élément à la liste.
l.append(2)
print(l)
Supprime la première occurence de la valeur 2 dans la liste.
l.remove(2)
print(l)
Insére un élément dans la liste avec la syntaxe : liste.insert(index, item)
.
l.insert(1, 2)
print(l)
Supprime l'élément à l'index donné de la liste.
del l[3]
print(l)
Retourne le dernier élément en le retirant de la liste.
print(l.pop())
print(l)
Le slicing fonctionne aussi sur les listes.
l = [ 1, 2, 3, 4, 5, 6, 7, 8 ]
print(l[2:-2:2])
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.)
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]))
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...
t1 = (1, 2, 3)
print(t1, type(t1))
... l'autre sans.
t2 = 4, 5, 6
print(t2, type(t2))
On peut défaire un tuple, toujours en utilisant les parenthèses (ou pas)
x, y, z = t1
print(x, y, z)
(x, y, z) = t2
print(x, y, z)
On peut crée un nouveau tuple à partir d'autres tuples, avec l'opérateur +
.
t1 + t2
Le slicing fonctionne toujours !
t1[::2]
Vérifiez le caractère immuable des tuples en essayant de modifier l'un des éléments de t1
(par exemple).
# t1[0] = 0
Les dictionnaires sont semblables aux listes, hormis que chaque élément est un couple : { clé: valeur }
.
dictionaire = { 'x': 2, 'y': 4, 'z': 8 }
print(dictionaire)
On peut (par exemple) indexer à l'aide de caractères.
print(dictionaire['x'])
print(dictionaire['y'])
print(dictionaire['z'])
Si la clé n'existe pas, une nouvelle entrée est créée dans le dictionnaire.
dictionaire['a'] = 1
dictionaire['b'] = 3
dictionaire['c'] = 5
print(dictionaire)
À 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.
condition1 = condition2 = False
if condition1:
print('condition1 est', condition1)
elif condition2:
print('condition2 est', condition2)
else:
print("aucune condition n'est vraie")
**Remarque**
Notez que les accolades (**`{}`**) du C/C++ sont remplacées par le double point (**`:`**) suivi d'un bloc d'indentation en Python.
L'indentation est primordiale, elle délimite la portée (le *scope* en Anglais) des blocs !
if True:
if True: # Changez True en False et observez
print('Second bloc')
print('Premier bloc')
print('Bloc principal')
#include <iostream>
using namespace std;
int main() {
for (int i = 0; i < 5; ++i) {
cout << "i = " << i << endl;
}
return 0;
}
for i in range(5):
print('i =', i)
print(range(5))
**Remarque**
`range(5)` crée un objet itérable qui va générer les nombres de 0 à 4 lorsqu'il est parcouru par l'itérateur.
Il s'utilise avec la syntaxe : `range(start, stop, step)`.
On peut générer une liste depuis un objet range
...
list(range(5))
... ou un tuple.
tuple(range(5))
On peut itérer directement sur les éléments d'une liste, ...
for it in [ 2, 4, 8, 10, 12 ]:
print('it = {:2}'.format(it))
..., mais aussi d'un tuple.
behhgk = ('Brout', 'Englert', 'Higgs', 'Hagen', 'Guralnik', 'Kibble')
for prenom in behhgk:
print(prenom)
La fonction enumerate
retourne un couple (tuple) : (index
, item
).
for i, prenom in enumerate(behhgk):
print(i, prenom)
On peut également générer une liste à l'aide d'une boucle for
avec une syntaxe compacte.
[ i for i in range(-3, 3) if i != 0 ]
while
¶Un exemple simpliste
i = 0
while i < 5:
print(i)
i = i + 1
Un exemple un peu plus convainquant.
liste = [ i for i in range(4) ]
while liste:
print(liste.pop())
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 :
.
def hello_world():
print('Hello World!')
hello_world()
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.
def addition(x, y):
"""Retourne la somme des deux arguments `x` et `y`."""
return x + y
addition(2, 3)
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.
def my_function(a, b=1, verbose=False):
if verbose:
print('Computing: {} ** {}'.format(a, b))
return a ** b
my_function(2, 3)
On peut définir explicitement la valeur d'un argument en précisant son nom lors de l'appel de la fonction.
my_function(2, verbose=True)
my_function(verbose=True, b=3, a=2)
lambda
¶carre = lambda x: x * x
carre(2)
my_lambda_function = lambda b: my_function(3, b=b, verbose=True)
my_lambda_function(2)
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.
import math
On peut obtenir plus d'information sur le module soit en tapant : help(math)
, soit en cherchant sur internet.
help(math)
Décommenter la ligne suivante, placez le curseur après le point .
et appuyer sur la touche TAB.
# math.
Si on ne souhaite pas importer le module en entier, on peut se restreindre à importer que quelques fonctions.
from math import exp, cos, sin
exp(1), cos(0), sin(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".
try:
print(tan(1))
except:
print("La fonction tan n'a pas encore été importée !")
from math import *
tan(1)
Besoin de plus d'information sur une fonction ? Vous pouvez vous servir de la fonction help
.
help(atan2)
Le module PyLab est une surcouche du module matplotlib qui permet de tracer des courbes.
%matplotlib inline
**Remarque**
Cette ligne permet d'afficher les graphes de matplotlib/PyLab directement dans le notebook.
import pylab
x = [ 1, 3, 5, 7, 9 ]
y = [ 0, 2, 8, 2, 0 ]
pylab.plot(x, y)
pylab.show()
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.
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 :
np.array
s telles que les fonctions : np.arange
, np.linspace
, ...a = np.array([ 1, 2, 3 ])
print(a, a.shape)
m = np.array([[ 1, 2 ], [ 3, 4 ]])
print(m, m.shape)
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
Le terme broadcasting décrit comment NumPy traite des np.array
s 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.array
s 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.array
s, il compare leur forme à dimension à dimension, en commençant par les dernières. Deux dimensions sont compatibles si :
1
La multiplication par un scalair.
print(2 * a)
print('')
print(3 * m)
La multiplication de deux np.array
s de même forme.
print(a * a)
Addition de deux np.array
s dont l'un a une dimension valant 1
.
a = np.arange(1, 4)
b = np.arange(1, 3)[:,np.newaxis] # .reshape(-1, 1)
print(a, a.shape)
print(b, b.shape)
a + b
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} $$
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):
Implémenter en Python une fonction, récursive, qui retourne la valeur de $n!$ ayant la signature suivante :
def fact_rec(n):
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
.
%timeit fact_for(12)
%timeit fact_rec(12)
%timeit fact_npy(12)
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 :
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 !