Ch 4 - Maths et info en PTSI2

publicité
PTSI2 – 2016/2017 – Info
Lycée La Martinière-Monplaisir – Lyon
Ch 4. Fonctions.
1
Introduction, premiers exemples
Nous avons déjà rencontré, en Python, diverses fonctions pré-programmées, comme cos, sqrt dans la
bibliothèque math, mais aussi len, int, print... Nous pouvons aussi créer nos propres fonctions.
Les fonctions sont très importantes pour plusieurs raisons :
• Il peut arriver qu’une même séquence d’instructions doive être utilisée plusieurs fois dans notre
programme. On souhaite ne pas avoir à la récrire systématiquement.
• Devant un problème complexe, le plus efficace est souvent de le décomposer en plusieurs sousproblèmes plus simples que l’on traite séparément à l’aide d’un algorithme chacun. Autrement
dit, on crée plusieurs fonctions auxiliaires et une fonction principale qui se sert de ces autres
fonctions pour répondre au problème.
Exemple : Au chapitre précédent, nous avons écrit un algorithme pour calculer la factorielle d’un
100!
100
nombre donné. Si on veut calculer 100
32 sans utiliser de fonction, sachant que 32 = 32!(100 − 32)! ,
on est obligé d’écrire :
n = 100
k = 32
# calcul de 100!
p1 = 1
for i in range(2 , n + 1):
p1 = p1 * i
# calcul de 32!
p2 = 1
for i in range(2 , k + 1):
p2 = p2 * i
# calcul de (100-32)!
p3 = 1
for i in range(2 , n - k + 1):
p3 = p3 * i
print(p1 / (p2 * p3))
C’est long !
1
Il est plus efficace de créer une fonction qui calcule n! pour un argument n donné. On écrira donc, dans
un fichier factorielle.py :
def facto(n):
p = 1
for i in range(2 , n + 1):
p = p * i
# Attention à bien indenter !
return(p)
# Remarque : la fonction marche parfaitement pour n = 0 et n = 1
Ici, la dernière ligne de la fonction est return(resultat), car on veut que la fonction renvoie un
résultat (on dit aussi "retourner" un résultat).
Pour le calcul de
100
32
, on écrit dans notre fichier :
print( facto(100) / ( facto(32) * facto(100 - 32) ) )
Exercice : La fonction facto étant définie comme ci-dessus, définir une fonction binomial qui prend
deux arguments, n et k, et qui retourne nk .
Amélioration :
2
2
Caractéristiques générales d’une fonction
Voici la syntaxe générale d’une fonction en Python :
def nom_de_la_fonction(arguments):
bloc d’instructions
Bien noter l’indentation du bloc d’instructions.
2.a
Arguments : un seul, plusieurs, aucun...
— Première ligne pour un argument :
def fonction(arg):
— Pour plusieurs arguments, on les sépare par des virgules. Par exemple :
def fonction(arg1, arg2, arg3, arg4, arg5):
— On peut très bien n’avoir aucun argument ; dans ce cas on écrit :
def fonction():
Cela peut paraître étrange au premier abord, mais il y a de nombreuses situations où une fonction sans
argument est adaptée. Par exemple, s’il l’on veut simuler un lancer de deux dés, à l’aide de la fonction
randint de la bibliothèque random : randint(a,b) renvoie un entier aléatoire entre a et b compris.
import random as r
def des():
return( r.randint(1,6) , r.randint(1,6) )
# Plusieurs résultats renvoyés
Pour simuler 10 lancers, on complète le programme :
for i in range(10):
print(des())
# pour un joli affichage : print(’Lancer no ’ + str(i+1) + ’ : ’ + str(des()) )
2.b
Utilisation d’une fonction comportant un return
La plupart des fonctions que nous écrirons se termineront par un return : elles permettent de "renvoyer" quelque chose.
Remarque : return interrompt automatiquement le flux d’exécution : on "sort" de la fonction. Par
exemple, dans le programme suivant, la ligne n = n - 2 est inutile car elle ne sera jamais exécutée :
def f(n):
n = n + 1
return(n)
n = n - 2
3
Reprenons l’exemple de la factorielle :
def facto(n):
p = 1
for i in range(2 , n + 1):
p = p * i
return(p)
Si on exécute seulement ces lignes, l’interpréteur ne répond rien. Le compilateur se contente de vérifier
que la syntaxe est bonne. Il faut faire un appel à la fonction pour la tester ou l’utiliser.
Tests :
Par exemple si on veut voir la valeur de 10! dans l’interpréteur (le "shell"), on a deux méthodes :
• écrire, après l’exécution, dans le shell :
Python shell
>>> facto(10)
3628800
• écrire, avant l’exécution, dans le fichier (le "script") (après la définition de la fonction) :
print(facto(10))
Cette deuxième méthode est à privilégier. Il suffira de mettre cette ligne en commentaire si on
ne veut plus voir le test. On peut copier-coller le résultat dans le fichier, en commentaire :
# Résultat : 3628800
Utilisations :
Si on écrit seulement l’instruction facto(10) ou print(facto(10)) quelque part dans notre fichier,
le calcul est effectué, le résultat éventuellement affiché, mais ce résultat n’est stocké nulle part et il est
immédiatement "perdu". Il faut soit intégrer directement facto(10) dans un autre calcul, soit stocker
la valeur de 10! en faisant une affectation :
print( facto(10) % 2 == 0 ) # affichage du booléen disant si 10! est pair ou non
N = facto(10!) # affectation d’une variable N pour utiliser la valeur 10! plus tard
print( N % 2 == 0 ) # utilisation de la variable
Nous avons vu avec la fonction des que l’on peut mettre dans le return plusieurs objets séparés par
des virgules 1 . Voici un autre exemple : la fonction f qui à x associe (−x + 1, x2 ) :
def f(x):
return( -x + 1 , x**2 )
1. On verra bientôt que cela revient à renvoyer ce qu’on appelle un tuple.
4
Pour récupérer les deux résultats, une méthode possible et très efficace est la suivante :
Python shell
>>> u, v = f(15)
>>> u
-14
>>> v
225
Autre solution : faire une seule affectation t = f(15) ; les deux quantités sont alors t[0] et t[1].
Une fonction peut aussi ne rien renvoyer du tout... Précisons cela :
2.c
Effets de bord
Les fonctions que nous avons créées jusqu’à présent étaient sans effet de bord, c’est-à-dire qu’elles
renvoient un résultat utilisable (en l’affectant par exemple à une variable).
Voici une autre version de la fonction facto, en remplaçant le return par print 2 :
def factobis(n):
p = 1
for i in range(2 , n + 1):
p = p * i
print(p) # à la place du return
L’inconvénient est qu’on ne peut pas utiliser le résultat ! Testons :
Python shell
Python shell
>>> facto(5)
>>> factobis(5)
120
120 # Pas de différence pour l’instant...
>>> a = facto(5)
>>> b = factobis(5)
120 # Bizarre : pas le même comportement que facto...
>>> a
>>> b # ça ne répond rien !!
120
>>> type(a)
>>> type(b)
<class ’int’>
<class ’NoneType’>
>>> 2 * a
>>> 2 * b
240
Traceback (most recent call last):
File "<pyshell#7>", line 1, in <module>
2 * b
TypeError: unsupported operand type(s) for *: ’int’ and ’NoneType’
2. S’il n’y a pas de return, la fonction renvoie quand même quelque chose : une valeur spéciale appelée None
5
De manière générale, on dit qu’une fonction a un effet de bord si, par exemple, elle affiche quelque
chose, ou si elle modifie un objet défini à l’extérieur de la fonction... plus généralement s’il y a une
interaction observable avec l’extérieur. Les effets de bord sont plutôt à éviter mais il y a cependant des
situations naturelles où on y a recourt, par exemple lorsqu’on voudra représenter des fonctions :
import matplotlib.pyplot as plt
def trace(f,a,b):
"""Trace le graphe de f entre a et b"""
les_x = [a + (b - a) * i/100 for i in range(101)]
les_y = [f(t) for t in x]
plt.plot(les_x, les_y)
plt.show()
Faisons l’essai avec une fonction. On la définit, puis on fait un appel à la fonction trace :
def f(x):
return(x ** 2)
trace(f, -2, 2)
Il s’affiche, dans une nouvelle fenêtre :
6
2.d
Documenter sa fonction
Les fonctions prédéfinies en python disposent d’une aide à laquelle on accède via help(nom_de_la_fonction) ;
il est très fortement recommandé de documenter aussi vos propres fonctions, en écrivant du texte entre
triples guillemets juste après la première ligne :
def facto(n):
"""Renvoie la factorielle de n"""
p = 1
for i in range(2 , n + 1):
p = p * i
return(p)
Ainsi,
• on peut taper help(facto) pour que le message d’aide s’affiche dans l’interpréteur :
Python shell
>>> help(facto)
Help on function facto in module __main__:
facto(n)
Retourne la factorielle de n
• ou bien dans IDLE, lorsqu’on a tapé facto( il s’affiche un encadré avec (n) (pour dire combien
d’arguments sont nécessaires) et le message d’aide.
C’est une habitude indispensable à prendre, car lorsqu’on écrit des programmes complexes avec de
nombreuses fonctions, il est important de retrouver rapidement le rôle de chacune des fonctions.
7
3
Variables locales, variables globales
3.a
Variables locales
Reprenons notre éternel exemple de la factorielle : nous savons que n est l’argument ; p est ce qu’on
appelle une variable locale.
def facto(n):
"""Renvoie la factorielle de n"""
p = 1
for i in range(2 , n + 1):
p = p * i
return(p)
On exécute ce fichier, puis on tape dans l’interpréteur :
Python shell
>>> facto(10)
3628800
>>> p
Traceback (most recent call last):
File "<pyshell#1>", line 1, in <module>
p
ZeroDivisionError: name ’p’ is not defined
Commentaire :
Explication : Quand une fonction est appelée et que débute son évaluation, elle crée un nouvel espace
de noms. Les variables qui sont créées dans cette fonction occupent alors un espace mémoire séparé
(appelé espace local ). C’est cet espace mémoire séparé qui est effacé à la fin de l’exécution. En conséquence, les variables locales ne sont visibles qu’à l’intérieur de la fonction.
La notion de variable locale est à rapprocher de celle de variable muette en maths.
On exécute, puis on tape dans l’interpréteur :
Python shell
>>> n
Traceback (most recent call last):
File "<pyshell#1>", line 1, in <module>
n
ZeroDivisionError: name ’n’ is not defined
Commentaire :
8
3.b
Variables globales
Les variables définies à l’extérieur de la fonction sont des variables globales. Leur contenu est visible
de l’intérieur d’une fonction et on peut les utiliser :
pi = 3.14
def aire_disque(r):
a = pi * (r ** 2)
return(a)
Ici, la variable pi ne subit aucune affectation dans la fonction, elle est donc considérée comme une
variable globale.
Si on fait comme ci-dessous, alors pi est considérée comme locale et n’a pas de sens à l’extérieur :
def aire_disque(r):
pi = 3.14
a = pi * (r ** 2)
return(a)
Remarque : On peut forcer une variable à être globale même si on l’affecte à l’intérieur de la fonction,
à l’aide du mot-clé global. Mais c’est généralement une très mauvaise idée ! À ne jamais faire !
Voici maintenant un exemple où une fonction utilise une variable globale et la réaffecte.
p = 34
def facto(n):
"""Renvoie la factorielle de n"""
p = 1
for i in range(2 , n + 1):
p = p * i
return(p)
On exécute, puis on tape dans l’interpréteur :
Python shell
>>> facto(10)
3628800
>>> p
34
Commentaire :
9
En résumé :
H Utiliser des variables locales est très pratique : cela signifie que vous n’avez pas à vous préoccuper
des noms des variables utilisées dans vos fonctions (que ce soit les arguments comme n ou les
variables "auxiliaires" comme p), puisque ça n’interfère pas avec celles définies à l’extérieur.
H Les variables globales devraient être réservées à des constantes du programme, qu’on n’a pas
besoin de modifier. Il est préférable de donner un nom long, en tout cas explicite.
Exercice : On exécute le script suivant :
x = 42
def f():
return(x)
def g():
x = 3
return(x)
def h():
global x
x = 17
return(x)
Anticiper les résultats dans l’interpréteur :
Python shell
>>> f()
>>> x
>>> g()
>>> x
>>> h()
>>> x
10
4
Notions clés
Il faut savoir appréhender les notions suivantes :
H Utilisation de fonctions prédéfinies en les important éventuellement de bibliothèques via
import
H Déclaration d’une nouvelle fonction avec def et, entre parenthèses, un, plusieurs ou aucun
argument(s).
H Définition du corps d’une fonction en blocs d’instructions indentées
H Renvoi d’une ou plusieurs valeurs par return
H Récupération du/des résultats par une affectation
H Documentation d’une fonction et utilisation de help
H Notion de variables locales / globales
11
Téléchargement