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

Rappel : les programmes commencent par :

## Entrées et sorties

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 :
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 :
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 :
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

## Types de variables
### Généralités

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
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
#### Entiers

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 :

#### Réels

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 :
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

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 :

### Chaînes de caractères
#### Ressources

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

#### Généralités

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 :
Dans une chaîne simple "…" ou '…', on peut introduire un retour à la ligne avec \n.

#### Substitution de variables

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 :
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 :
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 :
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 :
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 :
L'utilisation du caractère pourcent « % » permet d'utiliser la mise en forme sprintf du langage C :

#### Méthodes des chaînes

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

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 :
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 :
Pour la syntaxe, voir ci-dessous la section Définition en compréhension.

## Manipulation de listes

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

### Ressources

* (en) « 5. Data structures », sur Python documentation (consulté le 16 mars 2019)

### Copie d'une liste

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 :
Si l'on veut créer une copie de a, il faut utiliser a[:] ou bien a.copy :

### Méthodes de listes

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
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 :
Pour mettre en évidence la performance de la méthode list.sort par rapport à la fonction générique sorted :
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

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
ce qui se rapproche de la notation d'ensemble $\{x^{2}|x\in [0;9]\}$.
Si l'on veut la liste des nombres strictement inférieurs à 20 dont le carré est supérieur à 10, on peut écrire :
ce qui se rapproche de la notation d'ensemble $\{x|x\in [0;19],x^{2}>10\}$.
Pour mettre en évidence la performance du calcul vectorisé par rapport à la méthode itérative :

## Structure d'un programme

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 :
Ce shebang est inutile avec Jupyter.
L'en-tête peut également contenir la description de l'encodage du fichier texte, typiquement :
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. 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.
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

Boucle itérative
La boucle itérative s'écrit :
Si l'on veut que la variable prenne n valeurs de 0 à n – 1, on utilise l'instruction range :
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 :
On peut utiliser les commandes elif (else if) et else :
Boucle antéconditionnée
La boucle antéconditionnée s'écrit :
Cette boucle peut contenir des instructions continue et break.

## Fonction

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
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 :
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 :
Si le paramètre à initialiser est de type modifiable (mutable), comme par exemple une liste, il faut procéder comme suit :
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 :
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). 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.
À l'inverse, si une fonction doit recevoir plusieurs paramètres, on peut à la place lui transmettre une liste à dépaqueter :
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
est la même chose que
* Un utilisateur malveillant pourrait entrer du code malveillant dans la chaîne de caractères.
* 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

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
On peut compléter avec else: et finally: :
On peut séparer les différents types d'erreur :
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

### Ressources

* (en) « Errors and exceptions », sur Python documentation (consulté le 12 mars 2019)
* (en) « Built-in Exceptions », sur Python documentation (consulté le 12 mars 2019)

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

    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.
solution
On peut simplifier la boucle centrale :
Notez que le module NumPy propose l'instruction gcd :

### Tours de Hanoï

    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.
solution

### Lancer de rayons

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.
Il s’agit d’un problème ayant une symétrie de révolution par rapport à l’axe optique. Nous pouvons nous réduire à un problème plan en nous plaçant dans un plan contenant l’axe optique ; l’axe optique est encore un axe de symétrie orthogonale, nous pouvons donc nous contenter d'étudier un demi-plan.
Pour simplifier, nous plaçons le centre du dioptre sphérique à l’origine O du repère. L’axe optique est l’axe x et l'axe perpendiculaire, vertical sur la figure, c’est l’axe y.
Les coordonnées de la source sont donc (-d ; 0). Le rayon issu de la source et faisant un angle θ avec l’axe x frappe le dioptre plan à l’altitude h. Nous avons :
    h = d ⋅ tan θ.
L’angle d’incidence vaut θ. D’après la loi de Snell-Descartes, l'angle de réfraction θ$_2$ vaut :
    θ$_2$ = arcsin((sin θ) / n).
Le rayon réfracté passe par le points de coordonnées (0, h). L’équation de la droite est donc :
    y = a ⋅ x + h
avec
    a = tan θ$_2$.
L’équation du cercle de centre O et de rayon R est :
    x$^2$ + y$^2$ = R$^2$.
Les coordonnées (x$_M$, y$_M$) de l’intersection M du rayon avec le dioptre sphérique vérifient les deux équations. Par substitution, nous obtenons une équation du second degré en x que nous savons résoudre :
    x$_M$$^2$ + (a ⋅ x$_M$ + h)$^2$ = R$^2$
    ⇔ (1 + a$^2$) ⋅ x$_M$$^2$ + 2 ⋅ a ⋅ h ⋅ x$_M$ + h$^2$ – R$^2$ = 0.
D’après les propriétés du cercle, le rayon est perpendiculaire à la tangente. Le rayon [OM] est donc normal au dioptre en M. Nous pouvons déterminer l’angle d’incidence θ$_i$ par le produit scalaire :
    ${\begin{pmatrix}1\\a\end{pmatrix}}\cdot {\begin{pmatrix}x_{\mathrm {M} }\\y_{\mathrm {M} }\end{pmatrix}}={\sqrt {1^{2}+a^{2}}}\cdot \mathrm {R} \cdot \cos(\theta _{\mathrm {i} })$
ce qui nous permet de calculer cet angle :
    $\theta _{\mathrm {i} }=\operatorname {arcos} \left({\frac {x_{\mathrm {M} }+a\cdot y_{\mathrm {M} }}{\mathrm {R} \cdot {\sqrt {1^{2}+a^{2}}}}}\right)$
Comme nous passons vers un milieu d’indice plus faible, il y a un risque de réflexion totale. L’angle limite est :
    θ$_{max}$ = arcsin(1/n).
Si l’on a θ$_i$ > θ$_{max}$, le rayon repart vers l’intérieur. Nous ne traçons pas le rayon car cela nous emmènerait trop loin dans l’analyse. En revanche, si θ$_i$ ≤ θ$_{max}$, alors nous pouvons appliquer la loi de Snell-Descartes pour avoir l’angle de réfraction θ$_e$ :
    θ$_e$ = arcsin(n ⋅ sin θ$_i$).
Pour tracer le rayon sortant, il nous faut l’angle θ$_3$ par rapport à l’horizontale. L’angle du rayon [OM] par rapport à l’horizontal vaut arctan(y$_M$ / x$_M$), nous avons donc
    θ$_3$ = arctan(y$_M$ / x$_M$) + θ$_e$.
Structure des données
Le problème est décrit par trois paramètres :
* Le rayon R1 de la lentille, en milliètres (réel en virgule flottante).
* L’indice du verre, n sans dimension (réel en virgule flottante). L’indice de l’air vaut 1.
* La distance de la source au dioptre d’entrée plan, d en millimètres (réel en virgule flottante).
Un rayon est caractérisé par quatre paramètres :
* L’angle d’émission theta1 en radians (réel en virgule flottante).
* L’angle de réfraction dans la lentille theta2 en radians (réel en virgule flottante).
* Les cordonnées M en millimètre (vecteur de dimension 2 ([x, y]) de réels en virgule flottante) du point d’intersection du rayon avec le dioptre sphérique.
* L’angle de réfraction dans l’air après la lentille theta3 en radians (réel en virgule flottante).
Pour le calcul et le tracé, nous avons besoin des paramètres intermédiaires suivants :
* l’altitude y = h en millimètres (réel en virgule flottante) à laquelle le rayon frappe le dioptre plan d’entrée ;
* l’angle d’incidence du rayon avec le dioptre sphérique thetaint en radians (réel en virgule flottante).
Les angles sont stockés en radians car c’est l’unité naturelle pour le calcul mais nous affichons les valeurs en degrés. Comme le calcul de conversion est récurrent, nous conservons les facteurs degversrad (conversion des degrés vers les radians, facteur valant π/180, réel en virgule flottante) et radversdeg (conversion des radians vers les degrés, facteur valant 180/π, réel en virgule flottante).
Fonctions
Nous avons besoin d’une fonction qui calcule les trois paramètres du rayon (theta2, M, theta3) à partir de l’angle d’émission theta1. Nous appelons cette fonction lanceRayon. Cette fonction fait appelle à une fonction qui calcule l’angle du rayon réfracté à partir de l’angle du rayon incident theta1, les deux angles étant par rapport à la normale au dioptre au point considéré. Nous appelons cette fonction refrac.
La recherche de l’intersection M du rayon avec le dioptre sphérique nécessite de résoudre une équation du second degré. Nous utilisons pour cela la recherche des racines du polynôme en x avec la fonction numpy.polynomial.polynomial.polyroots. D’après la configuration du problème géométrique, si l’on s’assure que le rayon frappe bien la lentille (0 ≤ h ≤ R1) alors nous sommes sûrs que le problème a deux solutions réelles (une positive et une négative) ou, dans le cas dégénéré où h == R1, une valeur unique x == 0. Comme nous recherchons la valeur positive, nous sélectionons la plus grande des deux racines.
Pour la gestion de la réflexion interne : dans la fonction refrac, nous vérifions les conditions de réflexion totale et si elles sont remplies, alors nous générons une erreur (commandes try… except et raise ValueError). Cette erreur est propagée à la fonction lanceRayon : lanceRayon appelle la fonction refrac et si cette fonction renvoie une erreur, alors lanceRayon renvoie également une erreur.
Pour trouver l’angle d’émission thetaLimite provoquant la réflexion totale (en radians, réel en virgule flottante), nous effectuons une recherche par dichotomie :
* nous partons de l’angle maximum possible, lorsque le rayon frappe le sommet de la lentille, et nous appelons la fonction lanceRayon ; si cela ne génère pas d’erreur, alors nous pouvons aller jusqu’à cette valeur, la recherche est terminée ; si cela génère une erreur, alors nous divisons la valeur par deux ;
* à une étape de la recherche donnée, si lanceRayon ne génère pas d’erreur avec l’angle testé, alors nous savons que l’angle limite est supérieur à cette valeur ; cette valeur minore donc la valeur recherchée ; si au contraire lanceRayon génère une erreur, alors c’est que l’angle est trop important, cette valeur majore donc la valeur recherchée ; nous pouvons ainsi resserer l’intervalle de recherche ;
* nous nous arrêtons lorsque les valeurs haute et basse sont suffisamment proche.
Concrètement :
* Nous définissons une variable angleHaut angle en radians, réel en virgule flottante) qui est l’angle d’émission le plus bas connu provoquant la réflexion totale.
* Nous définissons une variable angleBas angle en radians, réel en virgule flottante) qui est l’angle d’émission le plus haut connu ne provoquant pas de réflexion totale. Sa valeur initiale est 0. L’angle limite recherché est donc entre angleBas et angleHaut.
* Nous définissons l’angle angleTest comme étant la moyenne entre angleBas et angleHaut. Si lanceRayon(angleTest) génère une erreur, alors angleTest est la nouvelle valeur d’angleHaut (puisque c’est une valeur provoquant la réflexion totale et qu’elle est plus basse que la valeur actuelle d’angleHaut). À l’inverse, si lanceRayon(angleTest) ne génère pas d’erreur, alors angleTest est la nouvelle valeur d’angleBas (puisque c’est une valeur ne provoquant pas la réflexion totale et qu’elle est plus haute que la valeur actuelle d’angleBas).
* Nous arrêtons la procédure lorsque l’écart entre angleBas et angleHaut est inférieur à 10$^{–3}$ rad (valeur arbitraire).
La valeur retenue est la valeur finale d’angleBas (puisque l’on veut être sûr qu’il n’y ait pas de réflexion totale). La valeur affichée est la valeur en degrés arrondie au dixième.
Nous demandons à l’utilisateur ou à l’utilisatrice les valeurs des paramètres du problème : rayon de la lentille, distance de la source, indice de réfraction du verre. Nous vérifions que les valeurs entrées sont bien des nombres ; si c’est une chaîne vide, alors nous utilisons une valeur par défaut.
Nous créons une fonction refrac qui permet de calculer l’angle réfracté à partir de l’angle d’incidence et des indices de réfraction. S’il y a rélexion totale, alors nous générons une erreur.
La fonction lanceRayon calcule les différents points de passage du rayon. Elle appelle pour cela la fonction refrac. Si un appel de la commande refrac génère une erreur, alors nous générons également une erreur.
Nous déterminons l’angle d’émision du rayon thetaLimite qui provoque une réflecxion totale. Pour cela, nous créons une fonction rechercheLimite qui cherche par dichotomie.
Nous traçons un rayon tous les 5° jusqu’à la valeur limite.

## Mesurer le temps

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 :
Pour mesurer la performance d'une portion de code :

## Programmation orientée objet

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. 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 Z$_1$/Z$_2$. 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 :
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 :
Nous pouvons alors utiliser les objets de la manière suivante :
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

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 » :
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.
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 « » :
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 :
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.
Notez que l'on aurait pu écrire directement :
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 » :
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 :
Nous faisons de même pour les trois autres paramètres de l'engrenage, m$_1$, z$_2$ et m$_2$. 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) :
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 :
ou bien si l'on appuie sur la touche [entrée] du clavier :
À 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),
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.
Calcul du rapport de transmission en programmation fonctionnelle et orientée objet

## Annotations

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 :
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 :
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 :
    int — float — str — bool — list — tuple — dict
Il est également possible de mettre une chaîne de caractères :
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 -> :

### Ressources

* (en) « PEP 526 -- Syntax for Variable Annotations », sur Python.org (consulté le 5 avril 2019)
* (en) « PEP 3107 -- Function Annotations », sur Python.org (consulté le 5 avril 2019)

## Décorateur

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 :
* Déclarer le décorateur : une fonction qui s'applique à une autre fonction.
* Affecter le décorateur à la fonction visée : en mettant @décoration juste avant la définition de la fonction.
Par exemple :
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 :
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 :
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 :
* https://wiki.python.org/moin/PythonDecoratorLibrary#Memoize
* https://gist.github.com/robcowie/1357800

### Ressources

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

## Manipulation de fichiers
### Importer le contenu d'un fichier

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 :
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 :
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 :
Pour mettre les données du fichier dans la variable contenu, nous écrivons donc :
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 :

### Exporter du contenu vers un fichier

Si nous voulons créer un fichier texte pour y mettre le contenu de la variable texte, alors nous utilisons :
Le module principal important pour la manipulation de fichiers est est os.

### Exploiter le contenu d'un fichier texte

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 :
La variable contenu est une liste de listes. Pour avoir la ne valeurs de la me ligne, on utilise :
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 :
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 :
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 :

### Cas d'un fichier CSV

Si le fichier CSV ne contient que des valeurs numériques, on peut utiliser :
Il existe un module csv dédié aux fichiers CSV. La manipulation du fichier se fait comme suit :

### Utilisation de Pandas

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

## Exporter un programme Python

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

* « PEP 8 -- Style Guide for Python Code », sur Python documentation (consulté le 14 mars 2019)

## Notes et références

* ↑ « Tabs or Spaces? », sur Python documentation (consulté le 14 mars 2019)
* ↑ « Introduction à *args et **kwargs », sur Developpez.com (consulté le 9 mars 2019).
* ↑ 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.
* ↑ https://pandas.pydata.org/
Fonctions mathématiques générales < ↑ > Graphiques