a0, b0, epsilon = 16, -27, 0.001
# Racine carrée
sa = a0 / 2
while abs(sa ** 2 - a0) >= epsilon:
P = sa ** 2 - a0
P_prime = 2 * sa
sa = sa - P / P_prime
# Racine cubique
positif = True
if b0 < 0:
positif, b0 = False, -b0
sb = b0 / 2
while abs(sb ** 3 - b0) >= epsilon:
P = sb ** 3 - b0
P_prime = 3 * sb ** 2
sb = sb - P / P_prime
if not positif:
sb = -sb
s = sa + sb
def procedure():
print("Première procédure")
def procedure():
print("Première procédure")
procedure()
procedure()
procedure()
Première procédure
Première procédure
Première procédure
Exemple avec racine cubique
def racine_cubique_27():
"""Affiche la racine cubique de 27."""
b0 = 27
epsilon = 0.001
positif = True
if b0 < 0:
positif = False
b0 = -b0
sb = b0 / 2
while abs(sb ** 3 - b0) >= epsilon:
P = sb ** 3 - b0
P_prime = 3 * sb ** 2
sb = sb - P / P_prime
if not positif:
sb = -sb
print(sb)
racine_cubique_27()
3.000000081210202
def carre(x):
print(f"{x ** 2}")
carre(2)
carre(3)
4
9
Argument pour le calcul de la racine cubique
def racine_cubique(x):
epsilon = 0.001
positif = True
if x < 0:
positif = False
x = -x
s = x / 2
while abs(s ** 3 - x) >= epsilon:
P = s ** 3 - x
P_prime = 3 * s ** 2
s = s - P / P_prime
if not positif:
s = -s
print(s)
racine_cubique(27)
racine_cubique(-27)
3.000000081210202
-3.000000081210202
def somme(a, b)
resultat = a + b
print(resultat)
somme(2, 3)
somme(3, 4)
5
7
static void passage_par_valeur(int a)
{
a = a + 1;
Console.WriteLine(a); // affiche 1
}
static void passage_par_reference(ref int a)
{
a = a + 1;
Console.WriteLine(a); // affiche 1
}
static void Main()
{
x = 0;
passage_par_valeur(x); // passe une copie de x
Console.WriteLine(x); // affiche 0
passage_par_reference(ref x); // passe une référence vers x
Console.WriteLine(x); // affiche 1
}
def puissance(x, exposant=2):
print(f"{x ** exposant}")
puissance(2)
puissance(2, 3)
4
8
Racine cubique avec
def racine_cubique(x, epsilon=0.0000001):
positif = True
if x < 0:
positif = False
x = -x
s = x / 2
while abs(s ** 3 - x) >= epsilon:
P = s ** 3 - x
P_prime = 3 * s ** 2
s = s - P / P_prime
if not positif:
s = -s
print(s)
racine_cubique(27)
racine_cubique(27, 1)
3.000000000000002
3.0004936436555805
def bonjour(prenom="Amélie", nom="Poulain"):
print(f"Bonjour {prenom} {nom}")
bonjour()
bonjour(nom="Teng")
Bonjour Amélie Poulain
Bonjour Amélie Teng
def bonjour(prenom="Amélie", nom):
print(f"Bonjour {prenom} {nom}")
def bonjour(prenom="Amélie", nom):
^
SyntaxError: non-default argument follows default argument
def f(a):
print(a)
def g(a, b):
f(a)
f(b)
def h():
a = 3
b = a + 2
g(b, a)
h()
5
3
a = 5
def f(a=10):
print(a)
f()
f(a)
10
5
a = 3
def f():
for _ in range(a):
print(a * "*")
f()
***
***
***
a = 3
def f():
a -= 1
for _ in range(a):
print(a * "*")
f()
a -= 1
UnboundLocalError: local variable 'a' referenced before assignment
global
a = 3
def f():
global a
a -= 1
for _ in range(a):
print(a * "*")
f()
**
**
def somme(a, b):
"""Renvoie la somme de a + b."""
return a + b
resultat = somme(1, 2)
3
def nom_de_la_fonction(liste_de_parametres):
corps_de_la_fonction
return
, auquel cas la valeur de la fonction devient la valeur de cette expression return
,None
est retourné.def racine_carree(x, epsilon=0.000001):
"""Renvoie la racine carrée de x à epsilon près."""
s = x / 2
while abs(s ** 2 - x) >= epsilon:
P = s ** 2 - x
P_prime = 2 * s
s = s - P / P_prime
return s
resultat = racine_carree(16)
4.000000000000004
def racine_cubique(x, epsilon=0.000001):
"""Renvoie la racine cubique de x à epsilon près."""
positif = True
if x < 0:
positif = False
x = -x
s = x / 2
while abs(s ** 3 - x) >= epsilon:
P = s ** 3 - x
P_prime = 3 * s ** 2
s = s - P / P_prime
if not positif:
s = -s
return s
resultat = racine_cubique(-27)
-3.000000000000002
r2 = racine_carree(16)
r3 = racine_cubique(-27)
resultat = somme(r2, r3)
1.0000000000000022
def converti_nombre(x):
"""Converti le nombre x textuel sous un format entier."""
if x == "zéro":
return 0
elif x == "un":
return 1
elif x == "deux":
return 2
else:
return "non défini"
resultat = converti_nombre("un")
1
def ajoute_soustrait_et_multiplie(a, b):
"""Renvoie a + b, a - b et a * b."""
somme = a + b
difference = a - b
produit = a * b
return somme, difference, produit
ajout, diff, prod = ajoute_soustrait_et_multiplie(3, 2)
print(f"somme : {ajout}; différence : {diff} ; produit : {prod}")
somme : 5; différence : 1 ; produit : 6
"""La première ligne décrit de manière concise le but de la fonction.
On laisse ensuite une ligne vide avant de rentrer plus dans les détails.
Ensuite, on documente chaque entrée, puis chaque sortie.
"""
somme
def somme(a, b):
"""Retourne la somme des arguments.
a - entier, flottant ou chaîne de caractères.
b - entier, flottant ou chaîne de caractères.
Retourne la somme a + b.
"""
return a + b
def racine_carree(x, epsilon=0.000001):
"""Renvoie la racine carrée de x à epsilon près.
Calcule la racine carrée d'un nombre x positif en employant
l'algorithme de Newtown-Raphson.
x - nombre flottant positif ou nul.
epsilon - nombre flottant strictement positif.
Retourne une valeur proche de la racine carrée de x, à plus
ou moins epsilon près.
"""
s = x / 2
while abs(s ** 2 - x) >= epsilon:
P = s ** 2 - x
P_prime = 2 * s
s = s - P / P_prime
return s
help(round)
round(number, ndigits=None)
Round a number to a given precision in decimal digits.
The return value is an integer if ndigits is omitted or None. Otherwise
the return value has the same type as the number. ndigits may be negative.
help(racine_carree)
racine_carree(x, epsilon=1e-06)
Renvoie la racine carrée de x à epsilon près.
Calcule la racine carrée d'un nombre x positif en employant
l'algorithme de Newtown-Raphson.
x - nombre flottant positif ou nul.
epsilon - nombre flottant strictement positif.
Retourne une valeur proche de la racine carrée de x, à plus
ou moins epsilon près.
print
arg1 = "La fonction print"
arg2 = "peut prendre"
arg3 = "N"
arg4 = "arguments"
print(arg1, arg2, arg3, arg4)
La fonction print peut prendre N arguments.
max
max_pair = max(1, 5)
max_serie = max(4, 8, 0, -1, 4, 5)
print(f"{max_pair}\n{max_serie}")
5
8
def moyenne(*arguments):
"""Renvoie la moyenne des arguments.
arguments - doit comporter au moins une valeur et toutes
les valeurs sont numériques.
Retourne la moyenne de ces arguments.
"""
total = 0
for argument in arguments:
total += argument
return total / len(arguments)
resultat = moyenne(1, 2, 3, 4, 5, 6, 7, 8, 9)
print(resultat)
5.0
def log(message, *valeurs):
"""Affiche dans la sortie standard le message et la liste des valeurs.
message - chaîne de caractères à afficher.
valeurs - liste d'arguments variable de valeurs à afficher.
"""
if not valeurs:
print(message)
else:
valeurs_str = str(valeurs[0])
for valeur in valeurs[1:]:
valeurs_str += ", " + str(valeur)
print(f"{message} : {valeurs_str}")
log("Bonjour")
log("Mes valeurs", 7, 42, 3.14)
Bonjour
Mes valeurs : 7, 42, 3.14
def debit(diff_poids, diff_temps, periode=1, unites_par_kg=1):
return ((diff_poids * unites_par_kg) / diff_temps) * periode
def affiche_parametres(**kwargs):
"""Affiche simplement les paramètres d'entrée."""
for cle, valeur in kwargs.items():
print(f"{cle} : {valeur}")
affiche_parametres(a=1, b=3, c=5)
affiche_parametres(prenom="Louise", nom="Clark")
a : 1
b : 3
c : 5
prenom : Louise
nom : Clark
def echange(premier, second):
return second, premier
un = 1
deux = 2
un, deux = echange(un, deux)
print(f"{un}, {deux}")
2, 1
_
(underscore) pour une variable dont la valeur ne nous intéresse pas.def quelques_elements():
return 1, 2, 3
un, _, trois = quelques_elements()
print(f"un : {un} ; trois : {trois}")
un : 1 ; trois : 3
Vous pouvez utiliser l'unpacking operator lorsqu'une fonction retourne un grand nombre de résultats.
def longue_liste():
return 1, 2, 3, 4, 5, 6, 7, 8, 9
un, *entre, neuf = longue_liste()
print(f"un : {un} ; neuf : {neuf} ; nombre d'autres : {len(entre)}")
un : 1 ; neuf : 9 ; nombre d'autres : 7
def longue_liste():
return 1, 2, 3, 4, 5, 6, 7, 8, 9
un, deux, *autres = longue_liste()
print(f"un : {un} ; deux : {deux} ; nombre d'autres : {len(autres)}")
un : 1 ; deux : 2 ; nombre d'autres : 7
def f(N, a=1, b=0, c=1, b0=0):
"""Calcule la Nième valeur de la suite arithmético-géométrique f.
f est définie telle que f(0) = b0, et f(N) = a . f(N-1)^c + b sinon.
N - nombre entier strictement positif.
a - nombre entier utilisé comme multiplicateur géométrique.
b - nombre entier utilisé comme raison arithmétique.
c - nombre entier comme puissance.
b0 - nombre entier constituant le début de la suite.
Retourne la Nième valeur de la suite.
"""
if N == 0:
return b0
else:
return a * (f(N - 1, a, b, c, b0) ** c) + b
Pour a = 2, b = 1, b0 = 0, c = 1 :
def f(N):
"""Calcule la Nième valeur de la suite arithmético-géométrique f.
f est définie telle que f(0) = 0, et f(N) = 2 * f(N-1) + 1 sinon.
Retourne la Nième valeur de la suite.
"""
if N == 0:
return 0
else:
return 2 * f(N - 1) + 1
resultat = f(3)
print(resultat)
7
def f(N):
if N == 0:
print("f(0) = 0")
return 0
else:
precedent = f(N - 1)
actuel = 2 * precedent + 1
print(f"f({N}) = {actuel}")
return actuel
resultat = f(3)
f(0) = 0
f(1) = 1
f(2) = 3
f(3) = 7
def f(N):
if N == 0:
return 0
else:
precedent = f(N - 1)
actuel = 2 * precedent + 1
return actuel
resultat = f(1000000)
RecursionError: maximum recursion depth exceeded
Fatal Python error: _Py_CheckRecursiveCall: Cannot recover from stack overflow.
def foo():
pass
foo_type = type(foo)
print(f"{foo_type}")
<class 'function'>
def f():
return 1
def g():
return 2
fonction = f # la variable "fonction" est liée à f
a = fonction() # f est appelée
fonction = g # la variable "fonction" est liée à g
b = fonction() # g est appelée
print(f"a = {a} ; b = {b}")
a = 1 ; b = 2
Généralisation de la dichotomie
def dichotomie(x, f, debut=0, fin=1000, epsilon=0.001):
"""Cacule la racine r telle que f(r) - x = 0 par dichotomie.
Généralisation de l'algorithme de dichotomie sur un intervalle [debut ; fin]
avec une fonction d'évaluation f pour le calcul d'une racine r. La racine r
doit être dans l'intervalle de recherche, sinon la condition de fin de
l'algorithme n'est pas garantie.
x - nombre flottant dont on recherche la racine | f(r) - x | < epsilon.
f - fonction d'évaluation prenant et renvoyant un flottant. Cette fonction
doit être dérivable sur l'intervalle [debut ; fin].
debut - début de l'intervalle de recherche de r.
fin - fin de l'intervalle de recherche de r.
epsilon - erreur acceptable qui doit être strictement supérieure à 0.
Renvoie la racine r telle que | f(r) - x | < epsilon.
"""
r = (debut + fin) / 2
while abs(f(r) - x) >= epsilon:
if f(r) < x:
debut = r
else:
fin = r
r = (debut + fin) / 2
return r
def affine(x):
"""Renvoie la valeur en entrée."""
return x
resultat = dichotomie(50, affine)
print(resultat)
49.999237060546875
def racine_carree(x, epsilon=0.001):
"""Renvoie la racine carrée de x."""
def carre(x):
return x ** 2
debut = 0
fin = max(1, x)
return dichotomie(x, carre, debut, fin, epsilon)
resultat = racine_carree(25)
print(resultat)
4.9999237060546875
def suite(a=1, b=0, c=1, b0=0):
"""Renvoie la suite f telle que f(0) = b0, et f(N) = a . f(N-1)^c + b.
a - nombre entier utilisé comme multiplicateur géométrique.
b - nombre entier utilisé comme raison arithmétique.
c - nombre entier comme puissance.
b0 - nombre entier constituant le début de la suite.
Retourne une fonction prenant un paramètre entier N et renvoyant f(N).
"""
def f(N):
return b0 if N == 0 else a * (f(N - 1) ** c) + b
return f
suite_arithmetique = suite(b=2)
print(f"f(0) = {suite_arithmetique(0)}")
print(f"f(1) = {suite_arithmetique(1)}")
print(f"f(2) = {suite_arithmetique(2)}")
print(f"f(3) = {suite_arithmetique(3)}")
f(0) = 0
f(1) = 2
f(2) = 4
f(3) = 6
lambda
est suivi d'une liste de paramètres puis d'une expression.lambda x : x ** 2
def racine_carree(x, epsilon=0.001):
"""Renvoie la racine carrée de x."""
return dichotomie(x,
lambda x : x ** 2,
debut=0,
fin=max(1, x),
epsilon=epsilon)
resultat = racine_carree(25)
print(resultat)
4.9999237060546875
def suite(a=1, b=0, b0=0):
"""Renvoie la suite f telle que f(0) = b0, et f(N) = a . f(N-1) + b.
a - nombre entier utilisé comme multiplicateur géométrique.
b - nombre entier utilisé comme raison arithmétique.
b0 - nombre entier constituant le début de la suite.
Retourne une fonction prenant un paramètre entier N et renvoyant f(N).
"""
if a == 1:
# Calcul du terme général dans le cas d'une suite arithmétique
return lambda N : b0 + N * b
else:
# Calcul du terme général dans le cas général
r = b / (1 - a)
return lambda N : (a ** N) * (b0 - r) + r
ma_suite = suite(a=2, b=1)
print(f"f(1) = {ma_suite(1)}")
print(f"f(2) = {ma_suite(2)}")
print(f"f(3) = {ma_suite(3)}")
f(1) = 1.0
f(2) = 3.0
f(3) = 7.0
str
(1/2)chaine = "bonjour, monde"
majuscules = chaine.upper()
print(majuscules)
capitales = chaine.capitalize()
print(capitales)
index_o = chaine.find("j")
print(index_j)
BONJOUR, MONDE
Bonjour, monde
3
str
(2/2)str
.upper
, capitalize
et find
qui sont rattachées au type str
.Les fonctions sont fondamentales en programmation. Il s'agit de l'unité de travail permettant la réutilisation et la programmation modulaire. Nous allons voir dans ce cours en quoi les fonctions sont importantes et comment les définir et les appeler.
La solution naïve consiste à dire qu'il suffit de changer la valeur de a0 puis de relancer le script. Ce n'est pas vraiment réutilisable : il faut modifier le code source à chaque fois que l'on souhaite tester une nouvelle valeur. Vous n'avez pas besoin de changer le code source de votre calculatrice pour faire un nouveau calcul.
Il s'agit d'une manière un peu complexe d'afficher 1. Pour afficher la somme d'une racine carrée et d'une racine cubique, on est obligé d'implémenter 2 fois Newtown-Raphson et de copier-coller nos algorithmes. On souhaite réutiliser nos algorithmes.
Si on devait recopier chaque algorithme à chaque fois qu'on doit l'appeler avec une valeur particulière, ce sera ingérable. Pour gérer des projets complexes, on est obligé de construire des couches d'abstraction. Chaque couche d'abstraction s'appuie sur les couches inférieures pour fournir des services de plus haut niveau. Une couche d'abstraction abstraie une partie de la complexité. Cela permet au final de manipuler facilement des composants qui eux-mêmes sont complexes. Dans ce diagramme, on a une application avec une interface graphique et des APIs qui permettent de manipuler un modèle de données qui s'appuie lui-même sur une base de données, sur des fonctions mathématiques, etc. Si les couches d'abstraction n'existaient pas, l'utilisateur final devrait lui-même manipuler les fonctions mathématiques. Les jeux vidéos seraient potentiellement moins funs et plus éducatifs !
Ici, rien n'est affiché. On ne fait que définir une procédure.
Ici, on défini une procédure, puis on l'appelle 3 fois. Par conséquent, le message est affiché 3 fois dans la sortie standard.
Evidemment, il peut y avoir plusieurs instructions dans une procédure. Une procédure peut contenir ses propres variables locales. Une procédure possède son propre contexte d'exécution.
La valeur de la variable x passée en argument de la fonction carre est remplacée par le nombre au niveau de l'appel de procédure. Ainsi, pour carre(2), on a x == 2. De même, pour carre(3), on a x == 3.
On a désormais des procédures réutilisables et utiles.
On peut évidemment passer autant d'arguments que nécessaire.
Cet exemple est en C#. On doit utiliser un autre langage de programmation car le Python ne supporte pas le passage par référence.
Un argument sans valeur par défaut ne peut pas succéder à un argument avec une valeur par défaut.
La portée d'une variable est limitée a la procédure dans laquelle elle est définie. Les arguments d'une procédure sont des variables de cette procédure. Ainsi, dans le contexte de h, on appelle g(b, a), avec b == 5 et a == 3. Dans le contexte de g, le premier argument a == 5, et le second argument b == 3. La première fois que l'on appelle f, on affiche donc 5. La deuxièmre fois que l'on appelle f, la variable a dans le contexte de f est a == 3. Donc on affiche 3 la deuxième fois.
Même principe que précédemment. La différence ici est que la variable globale a n'affecte pas le contexte de f.
On peut accéder en lecture seule à une variable globale dans le contexte d'une fonction. On dit que la variable est globale car elle n'est locale à aucune procédure.
On ne peut pas accéder en écriture à une variable globale par défaut.
On doit informer explicitement que l'on souhaite réutiliser la variable a du contexte global.
Le mot clé return permet de retourner un résultat. On peut affecter ce résultat à une autre variable. En pratique, vous avez déjà utilisé de nombreuses fonctions : range, int, float, input, print, etc.
Plutôt que d'afficher le résultat dans la sortie standard, on préfère le renvoyer. Cela offre plus de flexibilité et est plus évolutif.
On vient de se faire une petite bibliothèque composée des fonctions somme, racine_carre, et racine_cubique. On va donc pouvoir répondre à notre problématique initiale de simplification de notre code.
Le code initial pour résoudre ce problème était beaucoup plus conséquent, comme vous pourriez le voir en revenant sur les premières diapositives.
L'emploi, ou non, de plusieurs returns n'affecte pas les performances d'exécution. Il existe des débats concernant l'élégance de l'emploi de plusieurs returns. Il s'agit essentiellement d'une histoire de préférence personnelle. Souvent, une fonction écrit avec plusieurs returns comporte moins de variables et est plus simple à comprendre et à maintenir. En effet, une fonction équivalente avec un unique return devrait employer au moins une variable supplémentaire pour sauvegarder le résultat à renvoyer.
Il est possible de retourner plusieurs résultats en les séparant par des virgules. On les récupère de la même manière au niveau de l'appel de fonction. Nous verrons d'autres exemples plus loin dans ce cours.
On peut dans certains cas se retrouver avec plus de commentaires que de code. Il faut faire attention à bien garder les commentaires synchronisés avec l'implémentation.
Il s'agit de la docstring de la fonction native round.
On peut utiliser la même commande pour obtenir les docstring que l'on a défini.
La fonction print prend un nombre variable (en anglais : variadic) d'arguments.
La fonction max prend également un nombre variable d'arguments. En pratique, de nombreuses fonctions en Python offre cette possibilité.
Contrairement au précédent exemple, celui-ci supporte le cas où la liste variable d'arguments est vide.
On emploie ici une étoile supplémentaire. Cette syntaxe deviendra plus claire lorsque nous parlerons des dictionnaires lors d'un prochain cours.
La récursivité est un concept avec lequel les étudiants ont souvent des difficultés au départ. Il est utile d'introduire cette notion avec une intuition géométrique puis un exemple plus textuel avant de passer à une définition plus formelle. Attention : cette page contient un git animé inactif dans la version pdf. Images de Wikipedia. Liens : - https://upload.wikimedia.org/wikipedia/commons/thumb/2/21/Mandel_zoom_00_mandelbrot_set.jpg/640px-Mandel_zoom_00_mandelbrot_set.jpg - https://upload.wikimedia.org/wikipedia/commons/a/a4/Mandelbrot_sequence_new.gif
Attention : cette page contient un git animé inactif dans la version pdf. Images de Wikipedia. Lien : https://upload.wikimedia.org/wikipedia/commons/f/fd/Von_Koch_curve.gif
On défini une valeur constante pour le premier élément. On défini la valeur d'un élément par rapport aux éléments précédents.
Cette définition est identique à la définition mathématiques. Par contre, cette définition est complexifiée inutilement par la nécessité de passer les valeurs de a, b, c et b0 aux appels récursifs de f. On va simplifier cette définition avant de la tester puis de l'instrumenter.
On voit bien ici que f(3) mène à l'évaluation de f(2), qui mène à l'évaluation de f(1), qui mène à l'évaluation de f(0). f(0) contient la condition de fin de la récursivité. On le voit car f(0) n'appelle pas f. Une fois que f(0) est affiché et évalué, on remonte la pile d'appels et on effectue les calculs. On calcule d'abord f(1) = 2 * 0 + 1 = 1, et on l'affiche. On calcule ensuite f(2) = 2 * 1 + 1 = 3, et on l'affiche. On calcule enfin f(3) = 2 * 3 + 1 = 7, et on l'affiche.
A chaque appel récursif, le contexte d'appel doit être sauvegardé. Cela augmente progressivement la quantité de mémoire nécessaire au calcul. Par ailleur, la callstack (pile d'appels) est limitée par le système d'exploitation. Lorsque l'on appelle f avec 1 000 000, on doit avoir une profondeur de pile d'appels de 1 million. Or, le système d'exploitation utilisé ne permet pas d'avoir une telle prodondeur. Par conséquent, l'exécution s'arrête lorsque l'on atteint les limites du système d'exploitation. Ce type de problème s'appelle : stack overflow (surchage de pile d'appels).
On va voir ce que sont ces fonctions d'ordre supérieur.
Comme vous pouvez le voir, il est possible de définir des sous-fonctions. Ces fonctions ne peuvent être appelées que depuis leur contexte directe. Elles sont invisibles de l'extérieur. Ces fonctions internes ne devraient jamais faire plus de 1 à 3 lignes.
En français, le terme "lambda" peut avoir une connotation péjorative signifiant : commun, quelconque voire inintéressant. Le terme "fonction lambda" se retrouve dans beaucoup de langages de programmation. Ce terme vient du calcul lambda. Les fonction lambda sont intéressantes et pratiques.
Il n'est évidemment pas possible de définir une fonction lambda récursive. En effet, une fonction lambda n'a pas de nom et ne peut donc pas savoir comment s'appeler elle-même. C'est la raison pour laquelle nous utilisons ici le calcul du terme général d'une suite arithmético-géométrique. Il s'agit de la version itérative de l'algorithme récursif vu dans les diapositives précédentes.
Image de Wikipedia. Lien : https://upload.wikimedia.org/wikipedia/commons/3/33/Spaghettata.JPG
On peut espérer que les 2 dernier points vont évoluer dans le bon sens. Les langages fonctionnels sont extrêmement intéressants mais certains cas d'usage ne sont pas supportés, comme la programmation temps réelle embarquée.