Python pour le calcul scientifique/Éléments de programmation

Rappel : les programmes commencent par :

#!/usr/bin/python3

import numpy as np
import matplotlib.pyplot as plt

Entrées et sorties

modifier

Pour permettre à l'utilisateur ou à l'utilisatrice d'entrer une valeur, nous utilisons la fonction input() comme évoqué précédemment (chapitre Premiers programmes), avec la syntaxe variable = input(texte). Notez que la valeur renvoyée par input() est une chaîne de caractères. Si vous voulez autre chose, typiquement un nombre, il faut convertir cette chaîne.

Par exemple, nous demandons ici d'entrer une longueur sous la forme d'une valeur numérique :

longueurDefaut = 10.0
texteDemandeLongueur = f"Veuillez entrer la longueur en millimètres (valeur par défaut {longueurDefaut} mm) : "

longueur = input(texteDemandeLongueur)

if longueur=="":
    longueur=longueurDefaut
else:
    longueur=float(longueur)

print(longueur)

Pour afficher un texte, on utilise la fonction print(), également présentée dans le chapitre Premiers programmes, avec la syntaxe print(texte). Le texte à afficher peut être de n'importe quel type (entier, réel en virgule flottante, booléen, chaîne de caractères…) mais si l'on veut « mélanger » les types, il faut tout convertir en chaînes de caractères, avec la fonction str(), et concaténer les chaînes avec +.

Par exemple :

print("La longueur vaut : "+str(longueur)+" mm.")

Nous pouvons aussi utiliser une « chaîne “f” » (f-string) : on met un le f devant le guillemet ouvrant et dans la chaîne, on met un champ sous la forme {nomDeVariable}. L'exemple ci-dessus devient alors :

print(f"La longueur vaut : {longueur} mm.")

Les chaînes « f » sont détaillées dans la section Chaînes de caractères ci-dessous.

Si l'on veut introduire un retour à la ligne dans la chaîne, on utilise les caractères \n (contre-oblique suivie de la lettre N minuscule). Par exemple

print("Ceci est un texte\navec un retour à la ligne.")

Types de variables

modifier

Généralités

modifier

Python définit « tout seul » le type de la variable : « 3 » sera un entier (integer), « 3.0 » sera un réel à virgule flottante (float), « "3" » sera une chaîne de caractères (string).

On peut connaître le type d'une variable avec la fonction type().

On peut tester certaines valeurs, avec le module NumPy :

  • np.isnan(x) indique si les valeurs de x sont des NaN (not a number) ; si x est une matrice, le résultat est une matrice de booléens, l'élément [i, j] est True si x[i, j] est un NaN ;
  • np.isinf(x) indique si les valeurs de x sont ±∞ ; si x est une matrice, le résultat est une matrice booléenne de même dimension.

On peut forcer un type :

  • int(x) : transforme la valeur x en nombre entier ;
  • long(x) : " en entier long (précision illimitée) ;
  • float(x) : " en nombre réel à virgule flottante ;
  • str(x) : " en chaîne de caractères ;
  • complex(Re, Im) : crée le nombre complexe Re + Im·j, j désignant la racine carrée de –1 ;
  • list() : crée une liste ;
  • tuple() : crée un n-uplet.

Par exemple

type(3) # <class 'int'>
type(float(3)) # <class 'float'>
complex(1, 1) == 1 + 1j # True
list("blabla") # ['b', 'l', 'a', 'b', 'l', 'a']

Python distingue plusieurs genres de types :

  • Un itérable est un objet dont on peut extraire les éléments un par un ; ce sont les objets pour lesquels on peut écrire for i in iterable:. Il s'agit essentiellement des listes, n-uplets, chaînes de caractères, ensembles, dictionnaires et fichiers.
  • Un modifiable (mutable) est un objet que l'on peut modifier ; par exemple une liste est modifiable — on peut changer la valeur d'un élément, en ajouter ou en enlever un — mais les n-uplets non, pas plus qu'une chaîne de caractères ou un nombre.
  • Un identifiable (hashable, le hashage étant une signature caractéristique d'un objet) : objet possédant un identifiant unique. Un objet identifiable est toujours non-modifiable (unmutable).

Types numériques

modifier

Entiers

modifier

Nous pouvons définir les entiers au format octal ou hexadécimal : il faut débuter le nombre par respectivement 0o (le chiffre zéro et la lettre o) et 0x (le chiffre zéro et la lettre x). À l'inverse, la fonction hex() renvoie une chaîne correspondant à l'écriture d'un entier au format hexadécimal, et oct() renvoie la chaîne correspondant à l'éciture en octal. Par exemple :

print(0o10, ";", 0x10)
# 8 ; 16
print(hex(20))
# 0x14

Les réels disposent de fonctions spécifiques appelées « méthodes ».

Une méthode est une fonction spécifique à un type d'objets. Étant conçue ad hoc, elle est souvent plus économe en ressource et en temps qu'une fonction générique. Pour appliquer la méthode meth() à la variable x, on écrit : x.meth().

Nous avons déjà présenté la méthode float.as_integer_ration() qui donne la fraction réduite égale à la valeur du nombre. Les réels disposent de plusieurs autres méthodes :

  • float.trunc() : tronque le nombre réel ;
  • float.floor(), float.ceil() : renvoie l'entier le plus proche, respectivement inférieur ou supérieur ;
  • float.hex() : renvoie une chaîne de caractères correspondant à l'écriture du nombre en hexadécimal.

Par exemple :

a = 20.
print(a.hex())
# 0x1.4000000000000p+4

print(10..hex())
# 0x1.4000000000000p+3

Dans le deuxième exemple, nous appliquons la méthode float.hex() directement au nombre 10. ; le point est obligatoire car sinon, c'est un entier, pour lequel la méthode n'est pas définie.

Notez que la méthode float.hex() est différentes de la fonction hex() : la première concerne les réels, la seconde les entiers.

Complexes

modifier

Nous avons déjà mentionné la méthode complex.conjugate() qui donne le conjugué du nombre.

Un nombre complexe dispose de deux attributs :

  • complex.real : sa partie réelle ;
  • complex.imag : sa partie imaginaire.

Par exemple :

a = 5+2j
print(a.conjugate(), ";", a.real, ";", a.imag)
# (5-2j) ; 5.0 ; 2.0

Chaînes de caractères

modifier
Ressources
« 7. Input and Output », sur Python Documentation (consulté le 6 avril 2019)

Généralités

modifier

Il existe en fait trois manières de définir une chaîne de caractères :

  • avec des guillemets simples ou doubles comme vu précédemment : "…" ou bien '…' ;
  • avec trois guillemets doubles : """…""" : cela permet d'avoir une chaîne de caractères s'étendant sur plusieurs lignes, les retours de ligne étant pris en compte ; c'est utilisé en particulier pour la description des fonctions (docstrings, voir ci-après) ;
  • avec des guillemets précédés d'un « r », r"…" ou r'…' : cela permet d'interpréter les barres de fraction inverses « \ » comme un caractère « normal » et non comme un caractère d'échappement (voir ci-après) ; cela est utile lorsque l'on utilise les possibilités LaTeX dans le tracé de graphiques (voir plus loin) ;
  • avec des guillemets précédés d'un « f », f"…" ou f'…' : cela permet d'utiliser des variables formatées (voir ci-après).

Une chaîne de caractères n'est pas modifiable. Si l'on veut remplacer un caractère, l'insérer ou le supprimer, il faut transformer la chaîne en liste, avec la commande list(), puis rassembler la liste en la joignant (join) à une chaîne vide :

chaine = "blabla"
chaineList = list(chaine)
chaineList[2] = "c"
chaine = "".join(chaineList)
print(chaine) # blcbla

Dans une chaîne simple "…" ou '…', on peut introduire un retour à la ligne avec \n.

Substitution de variables

modifier

Lorsque l'on veut utiliser des variables, on fait précéder les guillemets d'un « f » et l'on écrit les noms de vrariables entre accolades. Par exemple :

monde = "world"

chaine = f"Hello {monde}!"

print(chaine) # Hello world!

On peut indiquer la taille de la chaîne générée à partir de la variable sous la forme {nomVariable:taille}, la taille étant un entier. Par exemple :

chiffre1 = 1
nom1 = "un"
chiffre2 = 2
nom2 = "deux"

chaine = f"{nom1:5} : {chiffre1:5d}\n{nom2:5} : {chiffre2:5d}"

print(chaine)
# un    :     1
# deux  :     2

Vous remarquez que l'on ajoute un « d » pour les entiers décimaux, et que les nombres sont alignés à droite. Si le nombre est un nombre réal à virgule flottante (float), on peut indiquer le nombre de décimales sous la forme .nf :

chaine = f"{np.pi:.5f}"
print(chaine)
# 3.15169

Avec la syntaxe m.nf, on indique également que la totalité du nombre doit occuper m caractères.

Pour convertir un nombre en caractère Unicode correspondant, on utilise la lettre c :

nompi = 0x03c0
chaine = f"{nompi:c} = {np.pi:.5f}"
print(chaine)
# π = 3.14159

La classe str dispose également de la méthode .format(). On indique un n-uplet de chaînes (ou de nombres) à la méthode et l'on met des accolades dans la chaîne principale ; les accolades sont remplacées dans l'ordre des chaînes de la méthode. On peut changer l'ordre en indiquant quelle valeur utiliser dans quelle accolade. Par exemple :

chaine1 = "On compte {} puis {}".format(1, 2)
chaine2 = "On compte {0} puis {1}. Mais à rebours, on compte {1} puis {0}.".format("un", "deux")

print(chaine1, "\n", chaine2)
# On compte 1 puis 2
#  On compte un puis deux. Mais à rebours, on compte deux puis un.

L'utilisation du caractère pourcent « % » permet d'utiliser la mise en forme sprintf() du langage C :

chaine = "π = %.5f" % np.pi
print(chaine)
# π = 3.14159

Méthodes des chaînes

modifier

Le type str dispose d'un certain nombre de méthodes. Nous avons déjà vu les méthodes str.join() et str.format(), en voici quelques autres :

  • str.capitalize() : met le premier caractère en capitale (majuscule) et les autres en minuscule ;
  • str.lower() : met tout en minuscules (lowercase) ;
  • str.upper() : met tout en capitales (lowercase) ;
  • str.center(n) : met la chaîne au centre d'une chaîne de longueur n, en complétant avec des espaces ; on peut compléter avec d'autres caractères avec str.center(n, c), par exemple "a".center(7, ".") donne "....a...." ;
  • str.ljust(n, c) et str.rjust(n, c) : comme .center() mais la chaîne est respectivement alignée au fer à gauche (left) et à droite (right) ;
  • str.isdigit() : booléen vrai si tous les caractères sont des nombres ;
  • str.find(sous-chaine), str.rfind(sous-chaine) : indique respectivement le premier emplacement et le dernier emplacement de la sous-chaîne dans la chaîne ;
  • str.partition(séparateur) : retourne un triplet avec la portion de chaîne avant le séparateur, le séparateur puis la portion de chaîne après le séparateur ;
  • str.replace(ancien, nouveau) : remplace la chaîne ancien par la chaîne nouveau dans la chaîne ;
  • str.split(séparateur) : découpe la chaîne au niveau des séparateurs et renvoie une liste.

Autres fonctions

modifier

La fonction chr() transforme un code Unicode en caractère. Par exemple, chr(97) donne "a" et chr(0x03c0) donne "π".

Si on veut créer une liste de caractères qui se suivent, on peut par exemple utiliser :

[chr(x) for x in range(97, 102)]
# ['a', 'b', 'c', 'd', 'e']

Si on veut créer une liste de nombres sous la forme de chaînes de caractères, on peut utiliser la commande str() vue ci-dessus. Par exemple :

[str(x) for x in range(1, 6)]
# ['1', '2', '3', '4', '5']

Pour la syntaxe, voir ci-dessous la section Définition en compréhension.

Manipulation de listes

modifier

Les listes sont une structure de données fondamentale en Python.

Ressources

Copie d'une liste

modifier

Contrairement à d'autres types, lorsque vos écrivez b = a avec des listes, vous ne créez pas une copie de la variable a, vous créez un alias : l'objet b est un autre nom de l'objet a. En particulier, si vous modifiez b, vous modifiez en fait a. Par exemple :

a = [1, 2, 3, 4]
b = a
b[2] = 5
print(a, b) # [1, 2, 5, 4] [1, 2, 5, 4]

Si l'on veut créer une copie de a, il faut utiliser a[:] ou bien a.copy() :

a = [1, 2, 3, 4]
b = a[:]
c = a.copy()
b[2] = 5
c[2] = 6
print(a, b, c) # [1, 2, 3, 4] [1, 2, 5, 4] [1, 2, 6, 4]

Méthodes de listes

modifier

Pour modifier une liste, vous disposez des méthodes suivantes :

  • a.append(x) : ajoute l'élément x à la fin de la liste a ;
  • a.extend(x) : ajoute la liste x à la fin de la liste a ;
  • a.append(i, x) : aoute l'élément x avant l'interstice i de la liste a ;
  • x = a.pop(i) : enlève l'élément i de la liste a et le met dans la variable x ; x = a.pop() enlève le dernier élément de la liste ;
  • a.clear() : vide la liste a ;
  • a.sort() : trie la liste par ordre croissant ;
  • a.sort(reverse = True) : trie par ordre décroissant ;
  • a.reverse() : inverse l'ordre de a.

Pour supprimer l'élément à l'indice i, au lieu d'utiliser a.pop(i), on peut aussi utiliser

del(a[i])

Pour trier une liste, on peut aussi utiliser la fonction sorted(), ce qui permet par exemple de conserver la liste originale, non triée : b = sorted(a). La fonction sorted() fonctionne avec tous les objets « itérables » comme par exemple une chaîne de caractères :

a = "ahjbfk"
print(sorted(a)) # ['a', 'b', 'f', 'h', 'j', 'k']

Pour mettre en évidence la performance de la méthode list.sort() par rapport à la fonction générique sorted() :

import numpy as np
import time

a = np.random.rand(int(1e7))
t1 = time.perf_counter()
b = sorted(a) # Fonction générique
t2 = time.perf_counter()
a.sort() # Méthode spécifique
t3 = time.perf_counter()
print("Sorted :", t2-t1, " s ; .sort :", t3-t2, "s ; rapport :", (t2-t1)/(t3-t2))
# Sorted : 14.2...  s ; .sort : 1.1... s ; rapport : 12.6...

Par rapport à une valeur donnée :

  • a.remove(x) : retire la première occurrence de la valeur x de la liste a ;
  • a.index(x) : indique l'indice où se trouve la première occurrence de la valeur x ;
  • a.count(x) : indique le nombre de fois que l'on trouve la valeur x dans la liste a.

Définition en compréhension

modifier

La définition en compréhension (list comprehension) est une méthode permettant de construire des listes en indiquant simplement des axiomes, des consignes de filtrage. Cette méthode est élégante car proche de la notation mathématique et compacte, mais c'est une méthode itérative donc lente par rapport à une méthode vectorisée fournie par le module NumPy.

Par exemple, pour créer la liste des carrés des nombres entiers entre 0 et 9, il suffit d'écrire

carre = [x**2 for x in range(10)]

ce qui se rapproche de la notation d'ensemble  .

Si l'on veut la liste des nombres strictement inférieurs à 20 dont le carré est supérieur à 10, on peut écrire :

X = [x for x in range(20) if x**2 > 10]

ce qui se rapproche de la notation d'ensemble  .

Pour mettre en évidence la performance du calcul vectorisé par rapport à la méthode itérative :

import time
import numpy as np

n = int(1e7) # taille de la liste
t1 = time.perf_counter()
carre = [x**2 for x in range(n)] # Définition en compréhension
t2 = time.perf_counter()
carre2 = np.arange(n)**2 # Calcul vectorisé
t3 = time.perf_counter()
print("En compréhension : ", t2-t1, "s ; vectorisé :", t3-t2, "s ; rapport :", (t2-t1)/(t3-t2))
# En compréhension :  4.515... s ; vectorisé : 0.156... s ; rapport : 28.982...

Structure d'un programme

modifier

Un programme est simplement une suite d'instructions.

Dans les environnements Unix BSD, un programme Python peut être considéré comme un script c'est-à-dire qu'il suffit de taper son nom dans l'invite de commande (shell) sans avoir à invoquer python. Le programme doit alors commencer par un en-tête normalisé surnommé shebang :

#!/usr/bin/env python3

Ce shebang est inutile avec Jupyter.

L'en-tête peut également contenir la description de l'encodage du fichier texte, typiquement :

# coding: utf-8

Le codage UTF-8 est le codage par défaut pour Python 3, il est donc inutile de l'indiquer.

Les commentaires sont introduits par le croisillon #.

On peut grouper une suite d'instructions dans un bloc. Un bloc d'instructions commence par deux-points « : » et est identé, c'est-à-dire qu'il a une marge constituée de quatre espaces — on peut aussi utiliser une tabulation mais il ne faut pas mélanger les deux méthodes ; les tabulations sont déconseillées, il vaut mieux utiliser quatre espaces[1]. Pour terminer le bloc, il suffit simplement de revenir en début de ligne ; contrairement à d'autres langages, il n'y a pas de commende de fin (end), c'est l'indentation qui définit le bloc.

: # début du bloc
    instruction 1
    instruction 2dernière instruction du bloc
instruction hors bloc

Par exemple, une exécution conditionnelle if ou une boucle for exécute un bloc d'instruction. Si l'on a besoin d'un bloc d'instruction qui « ne fait rien », on utilise l'instruction pass.

Structures de contrôle

modifier

Boucle itérative

La boucle itérative s'écrit :

for <variable> in <itérable>:
    <bloc d'instructions>

Si l'on veut que la variable prenne n valeurs de 0 à n – 1, on utilise l'instruction range() :

for i in range(5):
    print(i)
print("Fin de la boucle")

[▶]

0
1
2
3
4
Fin de la boucle

En fait, la commande range() extrait des valeurs de l'ensemble des nombres entiers ; on peut ainsi utiliser le découpage en tranches, par exemple range(2, 5)pour avoir la « liste » [2, 3, 4]. Notez que range() ne crée pas à proprement parler une liste, cela crée un objet de type « range » (plage, intervalle) ; pour avoir une liste, il faut écrire list(range(n)).

Dans une boucle, la commande continue() saute la fin du bloc d'instruction et passe à la valeur suivante de la boucle. La commande break() interrompt la boucle et passe à la suite.

Exécution conditionnelle

L'exécution conditionnelle s'écrit :

if <booléen>:
    <bloc d'instructions>

On peut utiliser les commandes elif (else if) et else :

if <booléen>:
    <bloc d'instructions>
elif <booléen>:
    <bloc d'instructions>
else:
    <bloc d'instructions>

Boucle antéconditionnée

La boucle antéconditionnée s'écrit :

while <booléen>:
    <bloc d'instructions>

Cette boucle peut contenir des instructions continue() et break().

Fonction

modifier

La déclaration d'une fonction utilise la commande def. La fonction est un bloc d'instructions. Si elle doit renvoyer des valeurs, on utilise la commande return. Par exemple

def nombres(n):
    """Entrer plusieurs nombres""" # description de la fonction
    foo = [] # initialisation
    for i in range(n):
        foo = foo+[float(input("Entrez un nombre"))]
    return foo
a = nombres(3)
print(a)

La fonction commence par une chaîne de caractères qui la décrit. Cette chaîne peut être récupérée automatiquement par certains logiciels pour faire une documentation automatique. Si la description prend plusieurs lignes, elle commence et finit par trois double-guillemets """…""" ; en fait, par convention, même si cela n'est pas obligatoire, les descriptions sont toutes encadrées de trois double-guillemets. Cette description est appelée docstring (documentation string). Pour récupérer les docstrings :

def foo():
    """Cette fonction ne fait rien"""
    
    pass

print(foo.__doc__)
# Cette fonction ne fait rien

L'instruction input() permet à l'utilisateur de saisir une valeur. La valeur est retournée sous la forme d'une chaîne de caractères qui est ensuite convertie en nombre réel avec l'instruction float().

On peut définir une valeur par défaut en l'indiquant dans l'en-tête de la définition de la fonction, de la manière suivante :

def nombres(n=1): # valeur par défaut : 1
    """Entrer plusieurs nombres"""
    foo = [] # initialisation
    for i in range(n):
        foo = foo+[float(input("Entrez un nombre"))]
    return foo

Si le paramètre à initialiser est de type modifiable (mutable), comme par exemple une liste, il faut procéder comme suit :

def fooFonction(fooListe=None): # valeur par défaut : n'existe pas
    """Description"""
    if fooListe = None:
        fooListe = [] # initialisation
    <suite des instructions>

Par défaut, les variables sont locales. On peut rendre une variable globale avec l'instruction global à l'intérieur de la fonction, avant l'utilisation de la variable. Par exemple :

a = 1
b = 1

def toto():
    """Test de variable globale"""
    global a
    a = 2
    b = 2

toto()
print("a =", a, "; b =", b) # a = 2 ; b = 1

Pour être plus précis : si une variable n'est pas assignée dans une fonction, alors Python va chercher une variable du même nom à l'extérieur de la fonction. Mais à partir du moment où la variable est assignée dans la fonction, elle devient locale sauf si l'on a utilisé l'instruction global.

Si l'on s'attend à un nombre indéfini d'arguments, on utilise la notion d'empaquetage/dépaquetage (packing/unpacking)[2]. L'empaquetage consiste à mettre les arguments dans un n-uplet, le dépaquetage consiste à développer un n-uplet en plusieurs variables. Cela se fait en mettant un astérisque (splat) « * » devant le nom de la variable. Par convention, on utilise le nom de variable *args mais cela n'est pas obligatoire.

def concatenation(*args):
    """Concatène des chaînes de caractères"""
    resultat = ""
    for i in args:
        resultat = resultat + i
    return resultat

concatenation("a", "foo", "toto") # 'afoototo'

À l'inverse, si une fonction doit recevoir plusieurs paramètres, on peut à la place lui transmettre une liste à dépaqueter :

def addition(a, b):
    """Ajoute deux nombres"""
    return a+b

arg = (1, 2)
addition(*arg) # 3

On peut aussi empaqueter/dépaqueter un dictionnaire, on utilise pour cela deux astérisques « ** ». Par convention, on utilise le nom **kwargs sans que cela ne soit obligatoire.

L'instruction lambda permet de créer de petites fonctions ne contenant pas de boucle ni de branchement conditionnel. Cependant, si la déclaration est courte et compacte, le code n'est pas toujours facilement lisible ; l'utilisation de cette instruction n'est pas recommandé.

Par exemple l'expression

f = lambda x: 2*x

est la même chose que

def f(x):
    return 2*x
 L'instruction eval() exécute une chaîne de caractères c'est-à-dire traite une chaîne de caractères comme si c'étaient des instructions données à Python. Cette instruction est à éviter pour deux raisons :
  1. Un utilisateur malveillant pourrait entrer du code malveillant dans la chaîne de caractères.
  2. L'exécution est lente puisque Python doit compiler la chaîne à la volée.

Cette instruction peut en général être remplacée par une autre instruction.

Gestion des erreurs

modifier

Dans un bloc d'instructions, on peut utiliser la structure try:… except:. Le bloc après try est exécuté ; si une erreur se déclare dans ce bloc, alors le bloc except s'exécute. Par exemple

try:
    1/0 # Génère une erreur
except:
    print("Division par zéro") # Cette instruction est donc exécutée

On peut compléter avec else: et finally: :

try:
    <code à exécuter>
except:
    <sexécute en cas derreur>
else:
    <sexécute sil ny a pas derreur>
finally:
    <sexécute dans tous les cas>

On peut séparer les différents types d'erreur :

try:
    <code à exécuter>
except ValueError:
    print("Valeur erronée")
except TypeError:
    print("Type erroné")

Les types d'erreur les plus courants sont :

  • NameError : le nom de variable n'existe pas ;
  • TypeError : la valeur n'est pas du bon type ;
  • ValueError : la valeur n'est pas compatible avec ce qui est attendu ;
  • RuntimeError : type d'erreur général.

On peut aussi créer ses propres erreurs : si une situation erronée survient, on peut « lever » une exception avec raise. Par exemple

if a < 0:
    raise ValueError("La valeur doit être positive")
Ressources

Exercices

modifier

Calcul du PGCD et du PPCM par l'algorithme d'Euclide

modifier
Pour plus de détails voir : w:Algorithme d'Euclide.

Écrire un programme Python qui demande deux nombres entiers et affiche leurs PGCD et PPCM. Le programme utilisera l'algorithme d'Euclide.

Notez que le module NumPy propose l'instruction gcd() :

import numpy

print(numpy.gcd(a, b))

Tours de Hanoï

modifier
Pour plus de détails voir : w:Tours de Hanoï.

Écrire un programme Python qui demande le nombre n de plateaux et affiche les manipulations nécessaires pour déplacer la pile d'un emplacement à un autre. Le programme utilisera l'algorithme récursif.

Lancer de rayons

modifier
 
Lentille hémisphérique.

Considérons une lentille hémisphérique de rayon R faite d’un verre d’indice de réfraction n. Nous plaçons une source ponctuelle à une distance d du dioptre plan, sur l’axe optique. Tracer des rayons partant de la source et traversant la lentille.

Mesurer le temps

modifier

Le module time fournit les fonctions suivantes :

  • time.gmtime() : renvoie la date et l'heure du méridien de Greenwich (Greenwich mean time, GMT), sous la forme d'un dictionnaire (année, mois, jour du mois, heure, minute, seconde, jour de la semaine, jour de l'année, heure d'été/hiver),
    • jour de la semaine est un entier entre 0 (lundi) et 6 (dimanche),
    • jour du mois est un entier entre 1 et 366 ;
  • time.localtime() : comme le précédent, mais l'heure est l'heure locale ;
  • time.time() : donne le nombre de seconde qui se sont écoulées depuis le 1er janvier 1970 ;
  • time.gmtime(n) et time.localtime(n) transforment un nombre de secondes (écoulées depuis le 1er janvier 1970) en une date au format (année, mois, jour, etc.), n-uplet de neuf valeurs ; time.mktime() fait le contraire, il transforme un n-uplet de neuf valeurs (années, mois, jour, etc.) en un nombre de secondes (écoulées depuis le 1er janvier 1970) ;
  • time.sleep(n) : provoque une pause dans le déroulement du programme de n secondes ;
  • time.perf_counter() : indique une date en seconde ; s'utilise pour mesurer la durée d'exécution d'une partie du code, en faisant la différence entre deux relevés.

Concernant la date et l'heure sous la forme d'un n-uplet, on peut extraire l'heure de la manière suivante :

import time
a = time.localtime()

print("Il est ", a[3], "h", a[4])
# ou bien
print("Il est ", a.tm_hour, "h", a.tm_min)

Pour mesurer la performance d'une portion de code :

import time

t1 = time.perf_counter()
<suite dinstructions>
t2 = time.perf_counter()

print("Durée d'exécution :", t2-t1

Programmation orientée objet

modifier

Nous n'allons pas ici faire un cours de programmation orientée objet (POO), nous allons aborder le sujet de manière pragmatique.

De manière schématique, un « objet » est une « super-variable ». Cette super-variable peut contenir plusieurs variables, appelées « attributs » ; elle contient en fait un dictionnaire (paires « nom d'attribut : valeur d'attribut »). Elle peut aussi contenir des fonctions spécifiques appelées « méthodes ». De même qu'une variable a un type, un objet fait partie d'une « classe ». La classe est le modèle de l'objet ; en franglais informatique, on dit que l'objet est une instance de la classe.

La POO est donc un formalisme : lorsque l'on définit des variables et des fonctions concernant un même type d'objet (au sens commun du terme), on les empaquette dans une classe. Il faut donc d'abord définir la classe, puis attribuer cette classe à un objet (« instancier » la classe).

Considérons par exemple que nous voulons travailler sur des engrenages ; pour simplifier, nous nous contentons d'engrenages à dentures droites. Une roue dentée, un pignon, est essentiellement définie par son nombre de dents Z et par son module m qui correspond à la largeur de dents[3]. Nous allons définir trois méthodes : la méthode .diametrePrimitif() qui calcule le diamètre primitif de la roue dentée, .pas() qui calcule la largeur des dents au niveau du cercle primitif et .rapport() qui calcule le rapport de transmission de deux roues engrenées Z1/Z2. La méthode .rapport() vérifie par ailleurs que les roues ont le même module, condition indispensable pour former un engrenage.

Nous définissons la classe ainsi :

class pignon:
    """roue dentée""" # explication de la classe
    
    pi = 3.141592653589793 # pour calculer le pas
    
    def __init__(self, Z=13, m=0.06):
        # instructions lancées lors de la déclaration
        """Valeurs des attributs"""
        self.Z = Z # nombre de dents
        self.m = m # module
    
    def diametrePrimitif(self):
        """Calcule le diamètre primitif"""
        return self.m*self.Z
    
    def pas(self):
        """Calcule le pas"""
        return self.pi*self.m
    
    def rapport(roueDentee, self):
        """Calcule le rapport de transmission"""
        if roueDentee.m != self.m: # gestion de l'erreur
            raise ValueError("Les pignons doivent avoir le même module")
        else:
            return roueDentee.Z/self.Z

Nous remarquons que lorsque nous déclarons les méthodes, le paramètre self correspond à l'objet lui-même. Ainsi, dans la méthode .rapport(), la variable self.Z est le nombre de dents de la roue elle-même et roueDentee.Z est le nombre de dents de la roue passée en paramètre.

Pour déclarer les roues, nous écrivons :

roue1 = pignon() # attribution de la classe, « instanciation »
roue1.Z = 13 # définition des caractéristiques du pignon « roue1 »
roue1.m = 2

roue2 = pignon(16, 2) # manière alternative

Nous pouvons alors utiliser les objets de la manière suivante :

print(roue1.Z) # 13
print(roue1.diametrePrimitif()) # 26
R = roue1.rapport(roue2) # 0.8125

La commande dir(a) affiche tous les attributs et méthodes de l'objet a.

Ressources
« Classes », sur Python documentation (consulté le 8 mars 2019)

Interface graphique avec Tk

modifier

Une interface graphique utilisateur (GUI, graphic user interface) est un ensemble de boîtes permettant d'interagir avec l'utilisateur c'est-à-dire permettre la saisie d'informations, l'exécution d'actions et afficher des informations. Elle se compose d'éléments appelés widgets.

Les éléments (widgets) classiques sont :

  • boîte de dialogue (dialog box) : fenêtre contenant d'autres éléments ;
  • étiquette (label) : texte affiché ;
  • liste déroulante (drop-down list) : zone permettant le choix d'une option, la liste se déployant lorsque l'on clique sur la zone ;
  • zone de texte, champ de saisie (text box) : zone permettant de taper du texte ;
  • boîte combinée (combo box) : zone de saisie de texte contenant une liste déroulante qui permet de choisir des éléments prédéfinis ;
  • bouton (button) : objet effectuant une action lorsque l'on clique dessus ;
  • case à cocher (checkbox, tickbox) : objet permettant d'activer ou de désactiver une option lorsque l'on clique dessus ;
  • bouton radio, case d'option (radio button) : objet permettant d'activer une option en désactivant les autres options ; une seule option peut être activée à la fois.

Plusieurs modules permettent de gérer les interfaces graphiques. Nous choisissons ici le module développé sur la bibliothèque Tk qui est une bibliothèque multiplateforme. Pour cela, nous importons le module tkinter ainsi que le module ttk, ce dernier proposant des options plus « modernes » :

import tkinter as tk
from tkinter import ttk

Voici un programme permettant comme précédemment de calculer le rapport de transmission d'un engrenage. Nous détaillons sa construction ci-après.

# référence : https://tkdocs.com/tutorial/firstexample.html

import tkinter as tk
from tkinter import ttk

# ***************
# ***************
# ** Fonctions **
# ***************
# ***************

def calcule(*args): 
    """Calcule le rapport de transmission d'un engrenage"""
    
    try:
        valeurZ1 = float(IUz1.get())
        valeurM1 = float(IUm1.get())
        valeurZ2 = float(IUz2.get())
        valeurM2 = float(IUm2.get())
        if valeurM1 != valeurM2:
            IUrapport.set("Erreur de module")
        else:
            IUrapport.set(valeurZ2/valeurZ1)
    except:
        IUrapport.set("erreur")

# *************************
# *************************
# ** Interface graphique **
# *************************
# *************************

# fenetre principale
fenetre = tk.Tk()
fenetre.title("Rapport de réduction")

# élément (widget) cadre contenant tout le reste
cadre = ttk.Frame(fenetre, padding="3 3 12 12")
cadre.grid(column=0, row=0, sticky=(tk.N, tk.W, tk.E, tk.S))

# le cadre s'étire si l'on étire la fenêtre
fenetre.columnconfigure(0, weight=1)
fenetre.rowconfigure(0, weight=1)

# Paramètres du système (variables)
IUz1 = tk.StringVar()
IUm1 = tk.StringVar()
IUz2 = tk.StringVar()
IUm2 = tk.StringVar()
IUrapport = tk.StringVar()

# Création des zones de saisie
z1_entry = ttk.Entry(cadre, width=7, textvariable=IUz1)
m1_entry = ttk.Entry(cadre, width=7, textvariable=IUm1)
z2_entry = ttk.Entry(cadre, width=7, textvariable=IUz2)
m2_entry = ttk.Entry(cadre, width=7, textvariable=IUm2)

# Création des étiquettes statiques
z1_label = ttk.Label(cadre, text="z1")
m1_label = ttk.Label(cadre, text="m1")
z2_label = ttk.Label(cadre, text="z2")
m2_label = ttk.Label(cadre, text="m2")
rapport_statique = ttk.Label(cadre, text="Rapport de transmission : ")

# Création de l'étiquette dynamique
rapport_dynamique = ttk.Label(cadre, textvariable=IUrapport)

# Création du bouton
bouton = ttk.Button(cadre, text="Calcul", command=calcule)

# Placement des éléments (widgets)
z1_label.grid(column=1, row=1, sticky=tk.W)
z1_entry.grid(column=2, row=1, sticky=(tk.W, tk.E))

m1_label.grid(column=1, row=2, sticky=tk.W)
m1_entry.grid(column=2, row=2, sticky=(tk.W, tk.E))

z2_label.grid(column=1, row=3, sticky=tk.W)
z2_entry.grid(column=2, row=3, sticky=(tk.W, tk.E))

m2_label.grid(column=1, row=4, sticky=tk.W)
m2_entry.grid(column=2, row=4, sticky=(tk.W, tk.E))

rapport_statique.grid(column=1, row=5, sticky=tk.W)

rapport_dynamique.grid(column=2, row=5, sticky=(tk.W, tk.E))

bouton.grid(column=2, row=6, sticky=tk.W)

# ajoute une gouttière entre les éléments
for enfant in cadre.winfo_children():
    enfant.grid_configure(padx=5, pady=5)

# Emplacement initial du curseur
z1_entry.focus()

# effet de la touche [entrée]
fenetre.bind("<Return>", calcule)

# *************************
# *************************
# ** Programme principal **
# *************************
# *************************

# Affichage et activation de la fenêtre
fenetre.mainloop()

Explications

Nous commençons par définir la boîte de dialogue que nous appelons fenetre ; c'est un objet Tk et nous lui donnons un titre «  » :

fenetre = tk.Tk()
fenetre.title("Rapport de réduction")

Puis, nous définissons un cadre attaché à cette fenêtre et qui va nous permettre « d'accrocher » les autres éléments, ce qui permet de garder une apparence satisfaisante lorsque l'on retaille la fenêtre :

cadre = ttk.Frame(fenetre)

Le cadre va comporter six lignes (row) et deux colonnes (column).

Nous allons placer une étiquette (label) « z1 » : text="z1". Cette étiquette se trouve dans une case du cadre, celle de la première colonne et la première ligne : grid(column=1, row=1). Par rapport à cette case, elle est collée à « l'ouest » (W, west, gauche) de la case : sticky=tk.W.

z1_label = ttk.Label(cadre, text="z1") # Création de l'étiquette
z1_label.grid(column=1, row=1, sticky=tk.W) # Placement de l'étiquette

Notez que l'on aurait pu écrire directement :

ttk.Label(cadre, text="z1").grid(column=1, row=1, sticky=tk.W)

mais le fait de séparer la création de l'élément et son placement facilite la maintenance (recherche d'erreur, évolution du code).

Pour tout ce qui est dynamique, c'est-à-dire les zone de saisie des valeurs et l'affichage du résultat, il faut définir des « chaînes variables » () :

IUz1 = tk.StringVar()

Cette variable est une variable globale à la création. Nous pouvons alors placer la zone de saisie (entry) à côté de l'étiquette lui correspondant. Nous nommons la zone de saisie z1_entry :

z1_entry = ttk.Entry(cadre, width=7, textvariable=IUz1)

Nous faisons de même pour les trois autres paramètres de l'engrenage, m1, z2 et m2. Le résultat est également une chaîne variable globale. Par rapport à notre mise en page, elle se situe dans la case colonne 2 ligne 5, centrée sur cette case (collé à l'est et à l'ouest) :

rapport = tk.StringVar()
rapport_dynamique = ttk.Label(cadre, textvariable=rapport)
rapport_dynamique.grid(column=2, row=5, sticky=(tk.W, tk.E))

Il nous faut encore définir une fonction de manière classique, nous l'appelons « calcule ». Les variables étant globales, on les utilise directement. On récupère les valeurs avec la méthode get() et nous modifions la valeur avec la méthode set() :

def calcule():
    valeurZ1 = float(IUz1.get())
    valeurZ2 = float(IUz2.get())
    IUrapport.set(valeurZ2/valeurZ1)
Cette fonction est déclenchée lorsque l'on clique sur le bouton « Calcul » situé dans la case du cadre ligne 6 colonne 2 :
bouton = ttk.Button(cadre, text="Calcul", command=calcule)
bouton.grid(column=2, row=6, sticky=tk.W)

ou bien si l'on appuie sur la touche [entrée] du clavier :

fenetre.bind("<Return>", calcule)

À tout ceci, nous ajoutons des « gouttières » (marges, paddings) afin d'espacer les éléments.

Il faut ensuite « activer » la fenêtre pour qu'elle s'affiche. La méthode est mainloop() (boucle principale) : « boucle » (elle est active en permanence et attend des actions sur ses éléments),

fenetre.mainloop()

Nous avons ci-dessus mis la plupart du code en programme principal. Nous pouvons aussi programmer de manière fonctionnelle, en mettant la plupart du code dans des fonctions ; cependant, pour que la fenêtre et les variables dynamiques soient globales à tout le programme, elles doivent être déclarées dans le programme principal. Nous pouvons aussi mêler la programmation orientée objet.

Annotations

modifier

Une annotation est un commentaire qui sert à expliciter un type de variable. La syntaxe est différente des commentaires « classiques » : cela permet d'avoir un affichage différent avec les éditeurs de texte ayant une coloration syntaxique, et ces informations peuvent être récupérées par des logiciels extérieurs pour effectuer une documentation automatique ou bien des vérifications de type. Cependant :

  • comme les commentaires normaux, ils n'ont aucune influence lors de l'exécution du texte ; en particulier :
  • rien n'oblige à annoter les variables ;
  • il est possible d'avoir une variable ayant un type différent de son annotation ; le fait de pouvoir définir et changer le type de variable à la volée est une fonctionnalité fondamentale de Python.

La syntaxe pour une annotation est :

nom_de_variable + deux-points + espace + type

par exemple :

a: int

Notez qu'ici, la variable n'est pas créée. Pour la créer, il faut lui affecter une valeur. Il est possible de l'affecter après ou bien sur la même ligne avec la syntaxe :

nom_de_variable + deux-points + espace + type + espace + égal + espace + valeur

par exemple :

a: int
a = 5
# est équivalent à
a: int = 5

Même si l'annotation n'a pas d'impact sur l'exécution, le type doit être un type existant sinon cela génère une erreur de syntaxe. Les types classiques sont :

intfloatstrboollisttupledict

Il est également possible de mettre une chaîne de caractères :

a: "ce que je veux" = 3.1516

On peut annoter une fonction. Il est possible d'annoter les variables déclarées au sein de la fonction, mais pas les variables globales (puisqu'elle ne sont pas définie au sein de la fonction). On peut aussi annoter :

  • les variables passées en paramètre, avec la même syntaxe dans les parenthèses ;
  • annoter le type de la variable de sortie (retournée) en la faisant précéder de -> :
def plusCinq(a: float = 0) -> float:
    return a + 5
Ressources

Décorateur

modifier

Un décorateur est une fonction qui s'applique à une fonction, à la manière de la composition mathématique g ∘ ƒ = g(ƒ). Mais cette composition affecte la fonction elle-même ; l'utilisateur appelle la fonction ƒ mais c'est la fonction g ∘ ƒ qui s'exécute. Cette fonction g est appelée le décorateur.

L'intérêt est de pouvoir modifier une fonction sans modifier le code de la fonction elle-même.

Pour appliquer une décoration, il faut :

  1. Déclarer le décorateur : une fonction qui s'applique à une autre fonction.
  2. Affecter le décorateur à la fonction visée : en mettant @décoration juste avant la définition de la fonction.

Par exemple :

def decorateur(f):
    print("Avant la fonction")
    f()
    print("après la fonction")

@decorateur
def afficheFoo():
    print("Foo.")

afficheFoo

# Avant la fonction
# Foo.
# Après la fonction

Lorsque l'on appelle <codeafficheFoo, on appelle en fait decorateur(afficheFoo).

Si la fonction à modifier admet des paramètres, il faut définir une fonction enveloppante dans le décorateur. Par exemple, nous définissons ci-dessous un décorateur deuxFois() qui fait s'exécuter deux fois de suite la fonction :

def deuxFois(f):
    def conteneurFonction(*args, **kwargs):
        f(*args, **kwargs)
        f(*args, **kwargs)
    return  conteneurFonction

@deuxFois
def plusCinq(a: int = 0):
    print(a + 5)

plusCinq(2)
# 7
# 7

print(plusCinq.__name__)
# conteneurFonction

Nous voyons que l'application du décorateur a modifié le nom de la fonction — pas le nom de la variable qui contient la fonction mais bien son nom « intime ». Pour éviter cela, on utilise la méthode wraps() du module functools :

import functools

def deuxFois(f):
    @functools.wraps(f)
    def conteneurFonction(*args, **kwargs):
        f(*args, **kwargs)
        f(*args, **kwargs)
    return  conteneurFonction

@deuxFois
def plusCinq(a: int = 0):
    print(a + 5)

plusCinq(2)
# 7
# 7

print(plusCinq.__name__)
# plusCinq

On peut par exemple utiliser un décorateur pour la mémoïsation. La mémoïsation est une méthode consistant à mémoriser les valeurs d'une fonction au fur et à mesure de son utilisation ; ainsi, si l'on veut évaluer la fonction avec les mêmes entrées, on se contente d'aller chercher la valeur enregistrée ce qui est plus rapide. On sacrifie donc la place mémoire au profit de la rapidité. On peut trouver des décorateurs de mémoïsation aux adresses suivantes :

Ressources
(en) « PEP 318 -- Decorators for Functions and Methods », sur Python.org (consulté le 5 avril 2019)

Manipulation de fichiers

modifier

Importer le contenu d'un fichier

modifier

Python possède la fonction open() qui permet d'ouvrir un fichier. Ouvrir signifie qu'il crée un objet de type file qui possède notamment les méthodes read() et write(). Il peut s'agir d'un objet de type « fichier binaire » (binary file) ou « fichier texte » (text file).

Si par exemple on veut utiliser (et donc lire) le contenu du fichier texte monfichier.txt, on écrit :

fichier = open("monfichier.txt", "rt")

fichier.close()

Le paramètre "rt" signifie que nous ouvrons le fichier en lecture (read) et qu'il s'agit d'un objet de type fichier texte.

Notons deux choses :

  • en faisant cela, nous ne faisons qu'associer le fichier à un objet Python, nous n'avons pas encore importé les données ;
  • si nous ouvrons le fichier, il faut le fermer par la suite ; c'est pourquoi nous utilisons la méthode .close().

Pour éviter d'avoir à fermer le fichier, nous pouvons l'ouvrir au sein d'un contexte :

with open("monfichier.txt", "rt") as fichier:
  

Notons aussi que la chaîne de caractères indiquant le nom du fichier peut contenir le chemin d'accès au répertoire (dossier), mais sous Microsoft Windows, il faut utiliser des barres de fractions / pour séparer les sous-répertoires au lieu de la barre inversée habituelle, par exemple :

chemin = "C:/Temp/monfichier.txt"

with open(chemin, "rt") as fichier:
  

Pour mettre les données du fichier dans la variable contenu, nous écrivons donc :

with open("monfichier.txt", "rt") as fichier:
  contenu = fichier.read()

print(contenu)

et si nous ne voulons lire que les n premiers caractères (n étant un entier), nous utilisons contenu = fichier.read(n). Cette lecture est séquentielle, c'est-à-dire que si nous appliquons la méthode plusieurs fois, nous reprenons la lecture là où nous l'avons laissée.

Si nous voulons lire une ligne, nous utilisons la méthode .readline(). La lecture ligne par ligne est également séquentielle. Nous pouvons aussi créer une liste dont chaque élément est une ligne du fichier ; nous utilisons alors la méthode .readlines() (notez le pluriel).

Chaque élément de la liste se termine par le caractère de fin de ligne \n. Pour l'enlever, nous pouvons utiliser la méthode .rstrip() pour chaque élément de la liste, par exemple. L'exemple complet est alors :

with open("monfichier.txt", "rt") as fichier:
  contenu = fichier.readlines()
  contenu = [item.rstrip() for item in contenu]
print(contenu)

Exporter du contenu vers un fichier

modifier

Si nous voulons créer un fichier texte pour y mettre le contenu de la variable texte, alors nous utilisons :

with open("monfichier.txt", "wt") as fichier:
  contenu = fichier.write(texte)

Le module principal important pour la manipulation de fichiers est est os.

Exploiter le contenu d'un fichier texte

modifier

Avec un fichier texte, la méthode .read() crée une variable de type texte. Nous pouvons séparer cette variable en différentes lignes avec la méthode .splitlines(). Cela crée une liste de chaînes de caractères, chaque chaîne étant une ligne.

Si maintenant une ligne contient plusieurs données séparées par un séparateur commun, par exemple un espace, nous pouvons séparer les données par la méthode .split(séparateur). Cela crée une liste de chaînes de caractères, chaque chaîne étant une donnée.

Si par exemple le fichier est du type CSV (comma separated values, valeurs séparées par une virgule), l'exploitation du fichier est :

with open("monfichier.txt", "rt") as fichier:
  contenu = fichier.read()

contenu = contenu.splitlines()
contenu = [item.split(",") for item in contenu]

La variable contenu est une liste de listes. Pour avoir la ne valeurs de la me ligne, on utilise :

contenu = [m-1][n-1]

Dans certains fichiers CSV, les séparateurs de valeurs ne sont pas des virgules, on peut donc utiliser un autre caractère pour le séparateur. Si le séparateur est une tabulation, on utilise \t : contenu = [item.split("\t") for item in contenu]

Si la première ligne contient les en-têtes des colonnes, on peut l'enlever avec la fonction del() :

with open("monfichier.txt", "rt") as fichier:
  contenu = fichier.read()

contenu = contenu.splitlines()
del(contenu[0])
contenu = [item.split(",") for item in contenu]

Certains logiciels créent des fichiers en utilisant le séparateur décimal régional, qui en France est la virgule. Pour remplacer les virgules par des points, on peut utiliser la méthode .replace(), de préférence avant de séparer les valeurs :

contenu = contenu.splitlines()
contenu = [item.replace(",", ".") for item in contenu] # remplace les virgules par des points
contenu = [item.split(";") for item in contenu] # si le séparateur est un point-virgule

en effet, lorsque l'on a séparé les valeurs, on a une liste de liste, il faut alors balayer les sous-listes ce qui prend plus de temps :

contenu = contenu.splitlines()
contenu = [item.split(";") for item in contenu] # si le séparateur est un point-virgule
contenu = [[subitem.replace(",", ".") for subitem in item] for item in contenu] # remplace les virgules par des points

Cas d'un fichier CSV

modifier

Si le fichier CSV ne contient que des valeurs numériques, on peut utiliser :

valeurs = np.loadtxt(chemin+nomfic, delimiter=",") # si le séparateur est une virgule

Il existe un module csv dédié aux fichiers CSV. La manipulation du fichier se fait comme suit :

import csv

with open(chemin+nomfic, "rt") as fichier:
    lecteur = csv.reader(fichier, delimiter=",")
    contenu = [ligne for ligne in lecteur]

print(contenu)

Utilisation de Pandas

modifier

Pandas[4] est un module gérant les tableaux de données, appelés data frames. Voici quelques commandes utiles :

import numpy as np
import pandas as pd

M = np.random.rand(10, 10) # crée une matrice NumPy aléatoire de dimension 10 × 10

tableau = pd.DataFrame(M) # transforme la matrice en tableau DataFrame

tableau.to_csv("tableau.csv") # enregistre le tableau dans un fichier CSV

donnees = pd.read_csv("tableau.csv").to_numpy() # lit le fichier et transforme le tableau DataFrame en matrice NumPy

Exporter un programme Python

modifier

Vous pouvez créer un fichier « Python pur » .py. Pour cela, dans le menu fichier/file de Jupyter, choisir télécharger/download au format .py ; le fichier se trouve alors dans le répertoire de téléchargement du navigateur.

Ressources

modifier

Notes et références

modifier
  1. « Tabs or Spaces? », sur Python documentation (consulté le 14 mars 2019)
  2. « Introduction à *args et **kwargs », sur Developpez.com (consulté le 9 mars 2019).
  3. ainsi que par son épaisseur e et le matériau dont elle est faite mais nous allons négliger ces paramètres pour la simplicité de l'étude.
  4. https://pandas.pydata.org/

Fonctions mathématiques générales < > Graphiques