TD E2 : Les fonctions en python, variables locales/globales et passage des arguments¶
Les fonctions représentent une brique fondamentale de la programmation. Il est important de bien les maîtriser.
1 Variables globales, variables locales¶
!!! warning Attention Ce point est central et très important à comprendre !!!
Q1. Considérer le script ci-après. Essayer de prévoir ce qui va être affiché puis exécuter pour voir
def g():
b = 2
b = b + 10
print("à l'interieur de g :",a, b)
a, b = 1, 1
g()
print("à l'extérieur de g :",a, b)
Il y a deux type de variables : globales et locales. Les variables globales sont celles définies à l'extérieur de toute fonction, et les locales sont celles définies à l'intérieur des fonctions. Ces dernières ne sont visibles qu'à l'intérieur de la fonction.
Dans l'exemple précédent on constate que les variables globales a
et b
définies à l'extérieur de la fonction ne sont pas modifiées par celle-ci. De plus la variable globale a
est consultable en lecture à l'intérieur de la fonction, mais on voit avec b
que l'on ne peut pas a priori modifier une variable globale dans une fonction, car elle devient locale... (voir toutefois la remarque ci-après)
Q2. Une petite subtilité : Considérer le script ci-dessous et essayer de prévoir l'affichage, puis exécuter pour voir.
def f():
a = a + 1
a = 5
print(f())
On constate qu'une variable ne peut pas être globale et locale à la fois dans une fonction. Heureusement, Python va lever une erreur UnboundLocalError
car sinon un code deviendrait vite illisible. Il faut choisir !
Q3. Est-il possible de modifier la variable globale depuis la fonction ? La réponse est oui. Voyons deux façons différentes. La première est déconseillée bien pratique pour des petits bouts de programme, mais il vaudra toujours mieux utiliser la deuxième.
Q3a. Méthode 1 (déconseillée) utilisation du mot clé global
. Exécuter le programme ci-dessous pour constater que l'on a bien modifié la variable globale a
depuis l'intérieur de la focntion :
# modif d'un variable avec le mot clé 'global' (déconseillé)
def f():
global a # b devient globale
a = a + 1
print(f"à l'interieur de f la variable a vaut après modif a = {a}")
a = 5
print(f"avant l'exécution de f, la variable a vaut a = {a}")
f()
print(f"après l'exécution de f, la variable a vaut a = {a}")
Q3b. Méthode 2 (bonne méthode) On utilise le retour de valeurs avec le mot cle return
. Exécuter le programme ci-dessous pour constater que l'on a bien modifié la variable globale a
à l'aide de la fonction :
# Bonne méthode pour modifier une variable globale via une fonction
def f(n : int) -> int :
return n + 1
a = 5
print(f"avant l'exécution de f, la variable a vaut a = {a}")
a = f(a) # on modifie 'a' via la fonction f
print(f"après l'exécution de f, la variable a vaut a = {a}")
!!! warning On retiendra :
- notion de variable globale et vairable locale
- Dans une fonction on peut accéder à une variable globale uniquement en lecture (pour l'écriture, on peut utiliser
global
mais ce n'est pas trop conseillé) - Dans une fonction toutes les variables crées sont locales, donc n'existent pas pour le programme principal.
!!!
Q4. On considère la suite numérique définie par $u_0 = 2$ et $u_{n+1} = f(u_n)$.
On donne ci-dessous le code de la fonction f
. Compléter le script pour créer une liste suite
qui contiendra les termes $u_0$ à $u_9$.
def f(x) :
return 3*x-2
suite = [2]
# à compléter
2. Passage des arguments¶
Il y a un point important qu'il faut prendre en considération dans python : le passage des arguments aux fonctions se fait par référence, c'est à dire que l'objet passé en argument est partagé avec la fonction (on ne passe que son adresse mémoire) et qu'il risque d'être modifié si on n'en fait pas une copie. Voici un exemple pour illustrer cela:
Q5. Cas où l'argument passé est une liste : Essayer de prévoir ce qu'affiche le script suivant, puis l'exécuter pour voir.
def modif1(lst : list) -> list :
""" cette fonction reçoit une liste et y ajoute un élément.
Attention la liste initiale est modifiée"""
lst.append("valeur ajoutée")
my_list = ['a','b', 'c']
print(my_list)
modif1(my_list)
print(my_list)
La liste my_list
définie de façon globale est modifiée par la fonction car elle travaille sur le même objet. Ceci se produit si l'argument est de type mutable (list ou dict par exemple)
Q6. Cas où l'argument passé est un nombre : Essayer de prévoir ce qu'affiche le script suivant, puis l'exécuter pour voir.
def modif2(nombre : int) -> int :
""" reçoit un nombre et ajoute 1.
Remarque, ce nombre n'est pas modifié en dehors
de la fonction"""
nombre += 1
print("dans la fonction :", nombre)
my_number = 10
print("en dehors de la fonction :",my_number)
modif2(my_number)
print("en dehors de la fonction :",my_number)
Cette fois-ci la variable my_number
n'a pas été modifiée. C'est parce qu'elle est de type immuable (c'est le cas en particulier des nombres et des chaines de caractères). Pour ces cas-là, une copie de la variable est créée automatiquement par la fonction, et par conséquent la variable n'est pas modifiée.
Pour ceux qui veulent en savoir plus : on peut aussi visualiser ce phénomène en utilisant la fonction id(...)
qui permet d'obtenir l'adresse mémoire d'un objet. Exécuter les codes suivants et bien analyser les réponses :
def modif1(lst : list) :
lst.append('val')
print("adresse de la variable lst dans la fonction:",id(lst))
def modif2(nombre : int) -> int :
nombre += 1
print("adresse de la variable nombre dans la fonction:",id(nombre))
my_list = ['a' , 'b' ]
print("adresse de la variable my_list:",id(my_list))
modif1(my_list)
my_number = 10
print("adresse de la variable my_number:",id(my_number))
modif2(my_number)
On constate bien que l'adresse de nombre
est différente de celle de my_number
alors que pour les listes les adresses restent les mêmes.
!!! warning On retiendra On retiendra qu'il faut toujours éviter de modifier un objet mutable (comme une liste) directement dans une fonction, car le code est difficile à lire et à deboguer. On préférera toujours faire une copie de la liste et renvoyer la nouvelle liste modifiée. Voir les exemples qui suivent... !!!
Q7. Application : On souhaite écrire une fonction qui supprime le plus grand élément d'une liste. On propose ci-dessous deux fonctions. D'après ce qui précède dire laquelle est fortement conseillée par rapport à l'autre :
Réponse :
- Fonction conseillée : # à compléter
- Fonction déconseillé : # à compléter
from random import shuffle
# la fonction shuffle mékange une liste
def supp_max_1(l : list)-> None:
""" Reçoit une liste , modifie la liste en enlevant le plus
grand élément """
maxi = max(l)
l.remove(maxi) # on enlève l'élément maxi de l
lst = list(range(10))
shuffle(lst) # mélange la liste
print(lst)
print("max = ",max(lst))
print("suppression...")
supp_max_1(lst)
print(lst)
print("max = ",max(lst))
def supp_max_2(l : list)-> list:
""" Reçoit une liste , renvoie une copie de cette liste qui ne
contient plus le plus grand élément """
cp_l = l.copy() # on crée une copie (superficielle) de la liste
maxi = max(cp_l)
cp_l.remove(maxi)
return cp_l
lst1 = list(range(10))
shuffle(lst1)
print(lst1)
print("max lst1 = ",max(lst1))
print("suppression...")
lst2 = supp_max_2(lst1)
print("max lst1 = ",max(lst1))
print("max lst2 = ",max(lst2))
# Remarque : si l'on veut modifier lst1, rien ne nous empeche d'écrire :
# lst1 = supp_max_bien(lst1)
3. On se teste¶
Q8. Essayer d'anticiper l'affichage puis exécuter pour voir...
def f(x):
x = x + 1
a = 2
return x
def g(x):
global a
x = x - 1
a = 2
return x
a = 10
b = f(a)
print(a,b)
a =10
c = g(a)
print(a,c)
4. Exercice¶
L'objectif de cet exercice est d'écrire une fonction qui permet de trouver la mesure principale d'un angle donné.
Un algorithme possible est le suivant~:
- demander deux entiers
a
etb
avecb>0
qui représentent un mesure de l'angle $\dfrac{a\pi}{b}$. - Si
a>0
alors enlever successivement2*b
au nombre $a$ tant que le résultat n'est pas dans $]-b;b]$ - Sinon, ajouter successivement
2*b
au nombre $a$ tant que le résultat n'est pas dans $]-b;b]$ - afficher le nombre obtenu (disons
c
). La mesure principale cherchée est alors $\dfrac{c\pi}{b}$.
Q9. Écrire une fonction principale(a : int, b : int) -> int :
qui reçoit deux entiers $a$ et $b$ avec $b>0$ représentant la fraction $\alpha=\dfrac{a\pi}{b}$ et qui renvoit le nombre $c$ tel que $\dfrac{c\pi}{b}$ est la mesure principale de l'angle $\alpha$.
def principale(a : int, b : int) -> int :
""" reçoit deux entiers a et b représentant la fraction a*pi/b et renvoie
le nombre c tel que c*pi/b en soit la mesure principale"""
if b <0 :
b *= -1
a *= -1
if a>0 :
sens = -1
else :
sens = 1
while a>b or a<=-b :
a += sens*2*b
return a
# on teste :
print(principale(2,3))
print(principale(4,3))
print(principale(-2,3))
print(principale(0,3))
print(principale(2,-3))
2 -2 -2 0 -2