if n>1:
 operations = hanoi(a, c, b, n-1)
 operations = operations+a+"→"+b+" ; "
 operations = operations+hanoi(c, b, a, n-1)
 else:
 operations = a+"→"+b+" ; "
 return operations
resultat = hanoi("1", "2", "3", 3)
print(resultat)
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 :
L’angle d’incidence vaut θ. D’après la loi de Snell-Descartes, l'angle de réfraction θ2 vaut :
Le rayon réfracté passe par le points de coordonnées (0, "h"). L’équation de la droite est donc :
avec
L’équation du cercle de centre O et de rayon R est :
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 :
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 :
ce qui nous permet de calculer cet angle :
Comme nous passons vers un milieu d’indice plus faible, il y a un risque de réflexion totale. L’angle limite est :
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 :
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
Structure des données
Le problème est décrit par trois paramètres :
Un rayon est caractérisé par quatre paramètres :
Pour le calcul et le tracé, nous avons besoin des paramètres intermédiaires suivants :
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 codice_180 (conversion des degrés vers les radians, facteur valant π/180, réel en virgule flottante) et codice_181 (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 codice_182 à partir de l’angle d’émission codice_173. Nous appelons cette fonction codice_184. Cette fonction fait appelle à une fonction qui calcule l’angle du rayon réfracté à partir de l’angle du rayon incident codice_173, les deux angles étant par rapport à la normale au dioptre au point considéré. Nous appelons cette fonction codice_186.
La recherche de l’intersection codice_175 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 codice_33 avec la fonction codice_189. D’après la configuration du problème géométrique, si l’on s’assure que le rayon frappe bien la lentille (0 ≤ codice_178 ≤ codice_170) 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ù codice_192, une valeur unique codice_193. 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 codice_186, nous vérifions les conditions de réflexion totale et si elles sont remplies, alors nous générons une erreur (commandes codice_195 et codice_196). Cette erreur est propagée à la fonction codice_184 : codice_184 appelle la fonction codice_186 et si cette fonction renvoie une erreur, alors codice_184 renvoie également une erreur.
Pour trouver l’angle d’émission codice_201 provoquant la réflexion totale (en radians, réel en virgule flottante), nous effectuons une recherche par dichotomie :
Concrètement :
La valeur retenue est la valeur finale d’codice_206 (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 codice_186 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 codice_184 calcule les différents points de passage du rayon. Elle appelle pour cela la fonction codice_186. Si un appel de la commande codice_186 génère une erreur, alors nous générons également une erreur.
Nous déterminons l’angle d’émision du rayon codice_201 qui provoque une réflecxion totale. Pour cela, nous créons une fonction codice_228 qui cherche par dichotomie.
Nous traçons un rayon tous les 5° jusqu’à la valeur limite.
"""nom : lancerRayons.py
auteur : User:cdang
date de création : 2022-05-06
dates de modification : 
version de Python : 3
module requis : NumPy, matplotlib
Objectif : trace des trajets optique avec une lentille hémisphérique
Entrées
 Le rayon de la lentille, la distance de la source, l’indice de réfraction du verre,
 trois chaînes de caractères saisies par l’utilisateur·rice et qui sont converties en réels.
Sorties
 La valeur limite de l’angle (réel) et le tracé de plusieurs rayons.
import numpy as np
import matplotlib.pyplot as plt
import numpy.polynomial.polynomial as nppol
radversdeg = 180/np.pi
degversrad = 1/radversdeg
def boucleEntreeNombre(messageSaisie, valeurDefaut):
 """Permet de s’assurer que l’utilisateur·rice a bien entré un nombre.
 Entrée :
 — message à afficher (chaîne de caractères) ; 
 — valeur par défaut (réel à virgule flottante).
 Sortie : nombre (réel à virgule flottante)."""
 messageErreur = "Veuillez entrer une valeur numérique (ou vide pour accepter la valeur par défaut).\n"
 execute = True
 while execute:
 strNombre = input(messageSaisie+f" (valeur par défaut {valeurDefaut}) : ")
 if strNombre == "":
 nombre = valeurDefaut
 execute = False
 else:
 try:
 nombre = float(strNombre)
 except:
 print(messageErreur)
 else:
 execute = False
 return nombre
def initialisation():
 """L’utilisateur·rice entre les variables du problème.
 Entrées : aucune.
 Sorties :
 — R1 (mm) : rayon de la lentille ;
 — d (mm) : distance de la source au dioptre plan ;
 — n (sans dimension) : indice de réfraction du verre."""
 R1 = boucleEntreeNombre("Rayon de la lentille en mm", 20.0)
 d = boucleEntreeNombre("Distance de la source au dioptre plan en mm", 20.0)
 n = boucleEntreeNombre("Indice de réfraction (sans dimension)", 1.5)
 return (R1, d, n)
def refrac(n1, n2, theta1):
 """Calcule l’angle de réfraction theta2 (radians) en fonction
 — de l’angle d’incidence theta1 (radians);
 — de l’indice de réfraction n1 du premier milieu ;
 — de l’indice de réfraction n2 du second milieu."""
 reflexionTotale=False
 rapport=n2/n1
 rapportinv=np.reciprocal(rapport)
 if n1 > n2:
 thetal = np.arcsin(rapport) # angle limite pour la réflexion totale
 if theta1 >= thetal:
 reflexionTotale=True
 if reflexionTotale:
 print("Réflexion totale")
 raise ValueError
 else:
 return np.arcsin(rapportinv*np.sin(theta1))
def lanceRayon(n1, n2, d, R, theta1):
 """Détermine le rayon issu de la source
 située à une distance d (mm) du bareau
 et avec une élévation de theta1 (radians),
 en fonction des indices de réfraction n1 et n2.
 Les éléments retournés sont :
 — la hauteur h (mm) à laquelle le rayon frappe le barreau ;
 — l’angle de réfraction theta2 (radians)) dans le barreau ;
 — l’angle de réfraction theta3 (radians) à la sortie du barreau
 — le point M(x, y) (mm) auquel le rayon sort du barreau."""
 h = d*np.tan(theta1)
 if h >= R:
 print("Le rayon est au-dessus du barreau")
 raise ValueError
 else:
 theta2 = refrac(n1, n2, theta1)
 a = np.tan(theta2)
 x = max(nppol.polyroots([h*h - R*R, 2*a*h, 1+a*a])) # recherche de l’intersection du rayon avec le cercle
 y = a*x + h
 M = np.array([x, y])
 thetaint = np.arccos((x + a*y)/(R*np.sqrt(1 + a*a)))
 theta3 = np.arctan(y/x) - refrac(n2, n1, thetaint)
 return (h, theta2, theta3, M)
def rechercheLimite(n1, n2, d, R):
 """Recherche l’angle limite pour la réflexion totale.
 Entrée :
 — indice de réfraction des milieux 1 et 2, n1 et n2 ;
 — distance au barreau, d(mm).
 Sortie : angle limite theta (radians)"""
 angleHaut = np.arctan(R/d)
 angleBas = 0
 angleTest = angleHaut
 try:
 lanceRayon(n1, n2, d, angleTest, R)
 except:
 condition = True # il y a réflexion total en haut de la lentille
 else:
 condition = False # il n’y a jamais réflexion totale dans la lentille
 while condition: #dichotomie
 angleTest = np.mean([angleHaut, angleBas]) # on ajuste la valeur de test
 try:
 lanceRayon(n1, n2, d, R, angleTest)
 except:
 angleHaut = angleTest # réflexion totale : on abaisse la valeur maximale
 else:
 angleBas = angleTest # pas de réflexion totale : on monte la valeur minimale
 condition = ((angleHaut - angleBas) >= 0.001) # on a cerné la limite à 0,001 rad près
 if not condition:
 angleTest = angleBas
 return angleTest
xmax = round(R1 + d)
thetaLimite = rechercheLimite(1, n, d, R1)
thetaLimiteDeg = thetaLimite*radversdeg
print(f"Angle limite pour la réflexion totale : {thetaLimiteDeg:.1f}°.\n")
anglesDeg = np.arange(0, thetaLimiteDeg, 5)[1:] # trace un rayon tous les 5°
anglesRad = anglesDeg*degversrad
nb = len(anglesDeg)
h = np.zeros(nb) # initialisation des vecteurs de valeurs
theta2 = np.zeros(nb)
theta3 = np.zeros(nb)
M = np.zeros((nb, 2))
for i in range(nb):
 (h[i], theta2[i], theta3[i], M[i, :]) = lanceRayon(1, n, d, R1, anglesRad[i])
anglesCercle = 0.5*np.pi*(np.linspace(1, 0, 20))
x_cercle = R1*np.cos(anglesCercle) # coordonnées des pints du cercle
y_cercle = R1*np.sin(anglesCercle)
fig = plt.plot([-d,xmax], [0, 0], "k-.", linewidth="0.5") # tracé de l’axe optique
for i in range(nb):
 plt.plot([-d, 0, M[i, 0], xmax], [0, h[i], M[i, 1], M[i, 1] + (xmax - M[i, 0])*np.tan(theta3[i])],
 label=f"{anglesDeg[i]:.0f}°")
plt.plot([-d, 0, M_lim[0], xmax], [0, h_lim, M_lim[1], M_lim[1] + (xmax - M_lim[0])*np.tan(theta3_lim)],
 label=f"{0.1*int(np.trunc(10*thetaLimite*radversdeg)):.1f}°")
plt.plot(x_cercle, y_cercle, "k", linewidth="0.5") # tracé du cercle
plt.plot([0,0], [0, R1], "k", linewidth="0.5") # tracé du premier dioptre
plt.gca().set_aspect("equal", adjustable="box")
plt.xlabel("x (mm)")
plt.ylabel("y (mm)")
plt.title("Lentille hémisphérique, lancer de rayons")
plt.legend()
plt.savefig("lentille_hemispherique_lancer_rayon.svg", format="svg")
plt.show()
Mesurer le temps.
Le module codice_229 fournit les fonctions suivantes :
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])
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 d’instructions>
t2 = time.perf_counter()
print("Durée d'exécution :", t2-t1
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 codice_238 qui calcule le diamètre primitif de la roue dentée, codice_239 qui calcule la largeur des dents au niveau du cercle primitif et codice_240 qui calcule le rapport de transmission de deux roues engrenées Z1/Z2. La méthode codice_240 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 codice_242 correspond à l'objet lui-même. Ainsi, dans la méthode codice_240, la variable codice_244 est le nombre de dents de la roue elle-même et codice_245 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 codice_246 affiche tous les attributs et méthodes de l'objet codice_87.
Interface graphique avec Tk.
Une interface graphique utilisateur (GUI, ') 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 '.
Les éléments "()" classiques sont :
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 codice_248 ainsi que le module codice_249, 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.
import tkinter as tk
from tkinter import ttk
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")
fenetre = tk.Tk()
fenetre.title("Rapport de réduction")
cadre = ttk.Frame(fenetre, padding="3 3 12 12")
cadre.grid(column=0, row=0, sticky=(tk.N, tk.W, tk.E, tk.S))
fenetre.columnconfigure(0, weight=1)
fenetre.rowconfigure(0, weight=1)
IUz1 = tk.StringVar()
IUm1 = tk.StringVar()
IUz2 = tk.StringVar()
IUm2 = tk.StringVar()
IUrapport = tk.StringVar()
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)
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 : ")
rapport_dynamique = ttk.Label(cadre, textvariable=IUrapport)
bouton = ttk.Button(cadre, text="Calcul", command=calcule)
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)
for enfant in cadre.winfo_children():
 enfant.grid_configure(padx=5, pady=5)
z1_entry.focus()
fenetre.bind("<Return>", calcule)
fenetre.mainloop()
Explications
Nous commençons par définir la boîte de dialogue que nous appelons codice_250 ; c'est un objet codice_251 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 "()" et deux colonnes "()".
Nous allons placer une étiquette "()" « z1 » : codice_252. Cette étiquette se trouve dans une case du cadre, celle de la première colonne et la première ligne : codice_253. Par rapport à cette case, elle est collée à « l'ouest » (W, "", gauche) de la case : codice_254.
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 "()" à côté de l'étiquette lui correspondant. Nous nommons la zone de saisie codice_255 :
z1_entry = ttk.Entry(cadre, width=7, textvariable=IUz1)
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) :
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 codice_256 et nous modifions la valeur avec la méthode codice_257 :
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 codice_258 du clavier :
fenetre.bind("<Return>", calcule)
À tout ceci, nous ajoutons des « gouttières » (marges, "") afin d'espacer les éléments.
Il faut ensuite « activer » la fenêtre pour qu'elle s'affiche. La méthode est codice_259 (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.
import tkinter as tk
from tkinter import ttk
class pignon:
 """roue dentée""" # explication de la classe
 pi = 3.141592653589793 # pour calculer le pas
 def __init__(self, Z=13, m=0.06):
 """Valeurs des attributs"""
 # instructions lancées lors de la déclaration
 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
fenetre = tk.Tk()
IUz1 = tk.StringVar()
IUm1 = tk.StringVar()
IUz2 = tk.StringVar()
IUm2 = tk.StringVar()
IUrapport = tk.StringVar()
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:
 roue1 = pignon(valeurZ1, valeurM1)
 roue2 = pignon(valeurZ2, valeurM2)
 IUrapport.set(roue1.rapport(roue2))
 except:
 IUrapport.set("Erreur")
def configureFenetre():
 """Configuration de la fenêtre principale"""
 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)
 # 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)
configureFenetre()
fenetre.mainloop()
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 :
La syntaxe pour une annotation est :
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 :
par exemple :
a: int
a = 5
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 :
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 :
def plusCinq(a: float = 0) -> float:
 return a + 5
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 :
Par exemple :
def decorateur(f):
 print("Avant la fonction")
 f()
 print("après la fonction")
@decorateur
def afficheFoo():
 print("Foo.")
afficheFoo
Lorsque l'on appelle <codeafficheFoo</code>, on appelle en fait codice_269.
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 codice_270 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)
print(plusCinq.__name__)
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 codice_271 du module codice_272 : 
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)
print(plusCinq.__name__)
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 :
Manipulation de fichiers.
Importer le contenu d'un fichier.
Python possède la fonction codice_273 qui permet d'ouvrir un fichier. Ouvrir signifie qu'il crée un objet de type codice_274 qui possède notamment les méthodes codice_275 et codice_276. Il peut s'agir d'un objet de type « fichier binaire » "()" ou « fichier texte » "()".
Si par exemple on veut utiliser (et donc lire) le contenu du fichier texte codice_277, on écrit :
fichier = open("monfichier.txt", "rt")
fichier.close()
Le paramètre codice_278 signifie que nous ouvrons le fichier en lecture "()" et qu'il s'agit d'un objet de type fichier texte.
Notons deux choses :
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 codice_280 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 codice_281, nous écrivons donc :
with open("monfichier.txt", "rt") as fichier:
 contenu = fichier.read()
print(contenu)
et si nous ne voulons lire que les codice_171 premiers caractères (codice_171 étant un entier), nous utilisons codice_284. 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 codice_285. 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 codice_286 (notez le pluriel).
Chaque élément de la liste se termine par le caractère de fin de ligne codice_10. Pour l'enlever, nous pouvons utiliser la méthode codice_288 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.
Si nous voulons créer un fichier texte pour y mettre le contenu de la variable codice_289, 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 codice_290.
Exploiter le contenu d'un fichier texte.
Avec un fichier texte, la méthode codice_291 crée une variable de type texte. Nous pouvons séparer cette variable en différentes lignes avec la méthode codice_292. 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 codice_293. 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 "(", 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 codice_281 est une liste de listes. Pour avoir la "n"e valeurs de la "m"e 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 codice_295 : codice_296
Si la première ligne contient les en-têtes des colonnes, on peut l'enlever avec la fonction codice_297 :
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 codice_298, 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.
Il existe un module codice_299 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(contenu2)
Utilisation de Pandas.
Pandas 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.
Vous pouvez créer un fichier « Python pur » codice_300. Pour cela, dans le menu codice_301 de Jupyter, choisir codice_302 au format codice_300 ; le fichier se trouve alors dans le répertoire de téléchargement du navigateur.
Notes et références.
[[../Fonctions mathématiques générales|Fonctions mathématiques générales]] < [[../|↑]] > [[../Graphiques|Graphiques]]
[[Catégorie:Python pour le calcul scientifique (livre)]]