5. Fonctions et espaces de noms▲
Les fonctions sont les éléments structurants de base de tout langage procédural.
Elles offrent différents avantages :
- évitent la répétition : on peut « factoriser » une portion de code qui se répète lors de l'exécution en séquence d'un script ;
- mettent en relief les données et les résultats : entrées et sorties de la fonction ;
- permettent la réutilisation : mécanisme de l'import ;
- décomposent une tâche complexe en tâches plus simples : conception de l'application.
Ces avantages sont illustrés sur la figure 5.1 qui utilise entre autres la notion d'import, mécanisme très simple qui permet de réutiliser des fichiers de fonctions, souvent appelés modules ou bibliothèques
5-1. Définition et syntaxe▲
Une fonction est un ensemble d'instructions regroupées sous un nom et s'exécutant à la demande
On doit définir une fonction à chaque fois qu'un bloc d'instructions se trouve à plusieurs reprises dans le code ; il s'agit d'une « factorisation de code ».
La définition d'une fonction se compose :
- du mot clé def suivi de l'identificateur de la fonction, de parenthèses entourant les paramètres de la fonction séparés par des virgules, et du caractère « deux-points » qui termine toujours une instruction composée ;
- d'une chaîne de documentation (ou docstring) indentée comme le corps de la fonction ;
- du bloc d'instructions indenté par rapport à la ligne de définition, et qui constitue le corps de la fonction.
Le bloc d'instructions est obligatoire. S'il est vide, on emploie l'instruction pass.
La documentation, bien que facultative, est fortement conseillée.
def
afficheAddMul
(
a, b) :
"""Calcule et affiche :
- la somme de a et b,
- le produit de a et b.
"""
somme =
a +
b
produit =
a *
b
print
(
"La somme de"
, a, " et"
, b, " est"
, somme, "et le produit"
, produit)
5-2. Passage des arguments▲
5-2-1. Mécanisme général▲
Passage par affectation : chaque paramètre de la définition de la fonction correspond, dans l'ordre, à un argument de l'appel. La correspondance se fait par affectation des arguments aux paramètres.
5-2-2. Un ou plusieurs paramètres, pas de retour▲
Exemple sans l'instruction return, ce qu'on appelle souvent une procédure(15). Dans ce cas la fonction renvoie implicitement la valeur None :
def
table
(
base, debut, fin) :
"""Affiche la table de multiplication des <base> de <debut> à <fin>."""
n =
debut
while
n <=
fin :
print
(
n, 'x'
, base, '='
, n *
base)
n +=
1
# exemple d'appel :
table
(
7
, 2
, 8
)
# 2 x 7 = 14 3 x 7 = 21 4 x 7 = 28 5 x 7 = 35 6 x 7 = 42 7 x 7 = 49 8 x 7 = 56
# autre exemple du même appel, mais en nommant les paramètres ;
table
(
base=
7
, debut=
2
, fin=
8
)
5-2-3. Un ou plusieurs paramètres, un ou plusieurs retours▲
Exemple avec utilisation d'un return unique :
from
math import
pi
def
cube
(
x) :
"""Retourne le cube de l'argument."""
return
x**
3
def
volumeSphere
(
r) :
"""Retourne le volume d'une sphère de rayon <r> ;"""
return
4.0
*
pi *
cube
(
r) /
3.0
# Saisie du rayon et affichage du volume
rayon =
float(
input(
'Rayon : '
))
print
(
"Volume de la sphère ="
, volumeSphere
(
rayon))
Exemple avec utilisation d'un return multiple :
5-2-4. Passage d'une fonction en paramètre▲
Puisqu'en Python une variable peut référencer une fonction, on peut transmettre une fonction comme paramètre :
>>>
def
f
(
x):
... return
2
*
x+
1
...
>>>
def
g
(
x):
... return
x//
2
...
>>>
def
h
(
fonc, x):
... return
fonc
(
x)
...
>>>
h
(
f, 3
)
7
>>>
h
(
g, 4
)
2
5-2-5. Paramètres avec valeur par défaut▲
Il est possible de spécifier, lors de la déclaration, des valeurs par défaut à utiliser pour les arguments. Cela permet, lors de l'appel, de ne pas avoir à spécifier les paramètres correspondants.
Il est également possible, en combinant les valeurs par défaut et le nommage des paramètres, de n'indiquer à l'appel que les paramètres dont on désire modifier la valeur de l'argument. Il est par contre nécessaire de regrouper tous les paramètres optionnels à la fin de la liste des paramètres.
>>>
def
accueil
(
nom, prenom, depart=
"MP"
, semestre=
"S2"
):
... print
(
prenom, nom, "Département"
, depart, "semestre"
, semestre)
...
>>>
accueil
(
"Student"
, "Joe"
)
Joe Student Département MP semestre S2
>>>
accueil
(
"Student"
, "Eve"
, "Info"
)
Eve Student Département Info semestre S2
>>>
accueil
(
"Student"
, "Steph"
, semestre=
"S3"
)
Steph Student Département MP semestre S3
On utilise de préférence des valeurs par défaut non modifiables (int, float, str, bool, tuple) car la modification d'un paramètre par un premier appel est visible les fois suivantes.
Si on a besoin d'une valeur par défaut qui soit modifiable (list, dict), on utilise la valeur prédéfinie None et on fait un test dans la fonction avant modification :
def
maFonction
(
liste=
None
) :
if
liste is
None
:
liste =
[1
, 3
]
5-2-6. Nombre d'arguments arbitraire : passage d'un tuple de valeurs▲
Le passage d'un nombre arbitraire d'arguments est permis en utilisant la notation d'un argument final *nom. Les paramètres surnuméraires sont alors transmis sous la forme d'un tuple affecté à cet argument (que l'on appelle généralement args).
def
somme
(*
args) :
"""Renvoie la somme du tuple <args>."""
resultat =
0
for
nombre in
args :
resultat +=
nombre
return
resultat
# Exemples d'appel :
print
(
somme
(
23
)) # 23
print
(
somme
(
23
, 42
, 13
)) # 78
Si la fonction possède plusieurs arguments, le tuple est en dernière position.
Réciproquement, il est aussi possible de passer un tuple (en fait une séquence) à l'appel qui sera décompressé en une liste de paramètres d'une fonction « classique ».
def
somme
(
a, b, c) :
return
a+
b+
c
# Exemple d'appel :
elements =
(
2
, 4
, 6
)
print
(
somme
(*
elements)) # 12
5-2-7. Nombre d'arguments arbitraire : passage d'un dictionnaire▲
De la même façon, il est possible d'autoriser le passage d'un nombre arbitraire d'arguments nommés en plus de ceux prévus lors de la définition en utilisant la notation d'un argument final **nom. Les paramètres surnuméraires nommés sont alors transmis sous la forme d'un dictionnaire affecté à cet argument (que l'on appelle généralement kwargs pour keyword args).
Réciproquement il est aussi possible de passer un dictionnaire à l'appel d'une fonction, qui sera décompressé et associé aux paramètres nommés de la fonction.
def
unDict
(**
kwargs) :
return
kwargs
# Exemples d'appels
## par des paramètres nommés :
print
(
unDict
(
a=
23
, b=
42
)) # {'a' : 23, 'b' : 42}
## en fournissant un dictionnaire :
mots =
{'d'
: 85
, 'e'
: 14
, 'f'
:9
}
print
(
unDict
(**
mots)) # {'e' : 14, 'd' : 85, 'f' : 9}
Si la fonction possède plusieurs arguments, le dictionnaire est en toute dernière position (après un éventuel tuple).
5-3. Espaces de noms▲
Un espace de noms est une notion permettant de lever une ambiguïté sur des termes qui pourraient être homonymes sans cela. Il est matérialisé par un préfixe identifiant de manière unique la signification d'un terme. Au sein d'un même espace de noms, il n'y a pas d'homonymes :
>>>
import
math
>>>
pi =
2.718
>>>
print
(
pi) # une valeur de l'espace de nom local
2.718
>>>
print
(
math.pi) # une valeur de l'espace de noms math
3.141592653589793
5-3-1. Portée des objets▲
On distingue :
- la portée globale : celle du module ou du fichier script en cours. Un dictionnaire gère les objets globaux : l'instruction globals() fournit un dictionnaire contenant les couples nom:valeur ;
- la portée locale : les objets internes aux fonctions sont locaux. Les objets globaux ne sont pas modifiables dans les portées locales. L'instruction locals() fournit un dictionnaire contenant les couples nom:valeur.
5-3-2. Résolution des noms : règle LGI▲
La recherche des noms est d'abord locale (L), puis globale (G), enfin interne (I) (Fig. 5.3) :
5-3-2-a. Exemples de portée▲
Par défaut, tout identificateur utilisé dans le corps d'une fonction est local à celle-ci. Si une fonction a besoin de modifier certains identificateurs globaux, la première instruction de cette fonction doit être :
global
<
identificateurs>
Par exemple :
# x et fonc sont affectés dans le module => globaux
def
fonc
(
y) : # y et z sont affectés dans fonc => locaux
global
x # permet de modifier x ligne suivante
x =
x +
2
z =
x +
y
return
z
x =
99
print
(
fonc
(
1
)) # 102
print
(
x) # 101
# x et fonc sont affectés dans le module => globaux
def
fonc
(
y) : # y et z sont affectés dans fonc => locaux
# dans fonc : portée locale
z =
x +
y
return
z
x =
99
print
(
fonc
(
1
)) # 100
print
(
x) # 99
# x et fonc sont affectés dans le module => globaux
def
fonc
(
y) : # y, x et z sont affectés dans fonc => locaux
x =
3
# ce nouvel x est local et masque le x global
z =
x +
y
return
z
x =
99
print
(
fonc
(
1
)) # 4
print
(
x) # 99