Appeler ses propres fonctions dans le cadre du langage Python.
Les éléments de cours sont développés dans les 2 premières sections, les exercices associés dans la dernière section.
Dans le cas de la fonction fibonacci
, on doit transmettre un argument
et un seul lors de l'appel de la fonction : l'entier n
pour lequel
on veut calculer le nombre de Fibonacci.
L'argument transmis peut tout aussi bien être une constante (par exemple 9
),
une variable (par exemple x
) ou toute expression dont la valeur est un
entier positif ou nul (par exemple 5+4
).
La fonction fibonacci
retourne un et un seul paramètre de retour : le
nombre de Fibonacci u
associé à l'entier n
passé en entrée.
>>> from fibo7 import fibonacci
>>> fibonacci(9)
55
>>> fibonacci(5+4)
55
>>> fibonacci(8) + fibonacci(7)
55
>>> y = fibonacci(9)
>>> y
55
>>> x = 9
>>> y = fibonacci(x)
>>> y
55
Une fonction s'utilise (s'« appelle ») sous la forme du nom de la fonction suivi de parenthèses à l'intérieur desquelles on transmet (on « passe ») zéro ou plusieurs arguments conformément à la spécification de la fonction.
Mais quel rapport existe-t-il entre ces arguments transmis
et les paramètres d'entrée-sortie de la fonction (n
et u
dans le cas
de la fonction fibonacci
) ?
C'est le problème du « passage des paramètres » de l'instruction appelante
à la fonction appelée.
Dans la définition d'une fonction, la liste des paramètres d'entrée spécifie les informations à transmettre comme arguments lors de l'appel de la fonction. Les arguments effectivement utilisés doivent être fournis dans le même ordre que celui des paramètres d'entrée correspondants : le premier argument sera affecté au premier paramètre d'entrée, le second argument sera affecté au second paramètre d'entrée, et ainsi de suite.
Les paramètres introduits dans la définition sont appelés les paramètres formels
(par exemple n
dans la définition de la fonction fibonacci
);
les paramètres passés en arguments sont appelés les paramètres effectifs
(par exemple x
dans l'appel fibonacci(x)
).
Un paramètre formel est un paramètre d'entrée d'une fonction utilisé à l'intérieur de la fonction appelée.
Un paramètre effectif est un paramètre d'appel d'une fonction utilisé à l'extérieur de la fonction appelée.
Lors de l'appel d'une fonction, il y a copie des paramètres effectifs
dans les paramètres formels : les valeurs des paramètres effectifs
passés en argument sont affectées aux paramètres formels d'entrée
correspondants (on parle de « passage par valeur » ou de « passage par copie »).
Ainsi, ce ne sont pas les paramètres effectifs qui sont manipulés par la
fonction elle-même mais des copies de ces paramètres.
A la sortie de la fonction, on copie la valeur du paramètre
formel de retour (u
dans la fonction fibonacci
) dans un paramètre
effectif qui remplace temporairement l'appel de la fonction (fibonacci(x)
)
et qui est finalement utilisé par l'instruction qui a appelé la fonction
(l'affectation dans le cas de l'instruction y = fibonacci(x)
).
Cette variable temporaire est ensuite détruite automatiquement.
Le passage des paramètres par valeur (ou par copie) consiste à copier la valeur du paramètre effectif dans le paramètre formel correspondant.
Ainsi, l'instruction y = fibonacci(x)
est « équivalente » à la séquence
d'instructions suivante (appel équivalent) :
On commence par copier la valeur du paramètre effectif x
dans le paramètre formel d'entrée n
(n = x
);
en Python, n
est vraiment créée à ce moment là seulement.
On exécute ensuite « tel quel » le code de la fonction fibonacci
:
les variables internes à la fonction (u
, u1
, u2
, i
)
sont créées et manipulées conformément à la définition de fibonacci
.
L'instruction « return u
» provoque ensuite la création d'une variable
temporaire tmp
dans laquelle on copie la valeur du paramètre formel
de sortie u
(tmp = u
) et toutes les variables introduites
dans le corps de la fonction sont détruites
(del n, u, u1, u2, i
). On affecte la valeur de tmp
à y
(y = tmp
) :
cette affectation réalise effectivement l'affectation souhaitée (y = fibonacci(x)
).
Enfin, la variable temporaire tmp
introduite
au « retour de la fonction » fibonacci
est détruite (del tmp
).
A la fin, seules coexistent les deux variables x
et y
: toutes les autres
variables (n
, u
, u1
, u2
, i
et tmp
) ont été
créées temporairement pour les besoins de l'appel de la fonction, puis détruites.
Dans certains cas, le paramètre effectif occupe une trop grande place en mémoire pour envisager de le recopier. Par exemple, une image satellite de \(6000\times 6000\) pixels codés sur 8 niveaux de gris occupe \(36 Mo\) en mémoire; la copier conduirait à occuper \(72 Mo\) ! Par ailleurs, on peut vouloir effectuer un traitement sur l'image originale et non traiter une de ses copies. Pour remedier à ces problèmes liés au passage des paramètres par valeur, on utilisera le « passage par référence » qui consiste à transférer la référence de la variable (son nom ou son adresse en mémoire) plutôt que sa valeur.
Le passage des paramètres par référence consiste à copier la référence du paramètre effectif dans le paramètre formel correspondant.
En Python, certains types de variables utilisent systématiquement
le passage par référence : c'est le cas des listes (type list
),
des dictionnaires (type dict
) ou encore des classes (types définis par
le programmeur) telles que Turtle
ou Image
.
Les fonctions f()
et g()
nécessitent chacune un argument
de type list
(assert type(x) is list
).
Cet argument sera donc passé par référence lors de l'appel de ces fonctions :
à l'intérieur de chacune d'elles, on manipulera une copie de la référence de la
liste passée en argument (t
dans les exemples de droite).
La fonction f()
cherche à modifier
le premier élément de la liste (x[0] = 5
) : elle affecte au premier
élément de la liste nommée x
(l'élément de rang 0
: x[0]
)
la valeur 5
. Comme le paramètre formel x
est une copie de la
référence originale t
, il référence le même tableau de valeurs [0,1,2]
et la modification portera bien sur l'élément 0
du tableau original.
A la sortie de la fonction f()
, la liste t
originale est bien modifiée.
Quant à elle, la fonction g()
cherche à modifier la référence de la liste
x
(x = y
). En fait, elle modifie la copie de la référence (x
)
mais pas la référence originale (t
). L'affectation suivante (x[0] = 5
)
modifie ainsi le premier élément du tableau de valeurs [9,8,7]
.
Mais, à la sortie de la fonction g()
,
le tableau de valeurs [0,1,2]
référencé par la liste t
originale
n'est pas modifié.
Les mêmes fonctionnements sont obtenus avec un dictionnaire comme le montrent les exemples ci-dessous.
2 Passage de dictionnairesDans la définition d'une fonction, il est possible de définir une valeur par défaut pour chacun des paramètres d'entrée. On peut définir une valeur par défaut pour tous les paramètres, ou une partie d'entre eux seulement; dans ce cas, les paramètres sans valeur par défaut doivent précéder les autres dans la liste. On obtient ainsi une fonction qui peut être appelée avec tous ses paramètres ou une partie seulement des arguments attendus:
>>> def f(x, y=0, z='r') : return x,y,z
...
>>> f(1,2,'t')
(1, 2, 't')
>>> f(1,2)
(1, 2, 'r')
>>> f(1)
(1, 0, 'r')
>>> f()
Traceback ...
TypeError: f() takes at least 1 argument (0 given)
>>>
De nombreuses fonctions des bibliothèques standards possèdent des
arguments par défaut. C'est le cas par exemple de la fonction standard int()
ou encore de la fonction log()
du module math
.
>>> int('45')
45
>>> int('45',10)
45
>>> int('101101',2)
45
>>> int('45',23)
97
>>> int('45',23) == 4*23**1 + 5*23**0
True
>>> from math import *
>>> e
2.7182818284590451
>>> log(e)
1.0
>>> log(e,e)
1.0
>>> log(e,10)
0.43429448190325176
>>> log(pi,7) == log(pi)/log(7)
True
La fonction int()
permet de transformer en un entier décimal
une chaîne de caractères (par exemple '45'
) qui représente un nombre écrit
dans la base spécifiée par le \(2^{\grave eme}\) argument
(par exemple 23
). Par défaut, cette
base est la base décimale (10
).
La fonction log()
calcule le logarithme
d'un nombre (par exemple 2.7182818284590451
) dans la base spécifiée par le
\(2^{\grave eme}\) argument (par exemple 10
).
Par défaut, cette base est celle des logarithmes népériens (e
).
Dans la plupart des langages de programmation, les arguments que l'on transmet à l'appel d'une fonction doivent être passés exactement dans le même ordre que celui des paramètres qui leur correspondent dans la définition de la fonction. En Python, si les paramètres annoncés dans la définition de la fonction ont reçu chacun une valeur par défaut, on peut faire appel à la fonction en fournissant les arguments correspondants dans n'importe quel ordre, à condition de désigner nommément les paramètres correspondants:
>>> def f(x=0, y=1) : return x,y
...
>>> f(3,4)
(3, 4)
>>> f()
(0, 1)
>>> f(3)
(3, 1)
>>> f(y=4, x=3)
(3, 4)
>>> f(y=6)
(0, 6)
>>> f(x=8)
(8, 1)
La fonction f()
a 2 paramètres d'entrée (x
et y
)
qui admettent chacun une valeur par défaut (respectivement 0
et 1
).
On peut l'appeler classiquement avec 0, 1 ou 2 arguments comme dans
les 3 appels de gauche (f()
, f(3)
, f(3,4)
). On peut aussi
l'appeler en nommant explicitement les arguments comme dans les 3
exemples de droite (f(y = 4, x = 3)
, f(y = 6)
, f(x = 8)
);
dans ce cas, l'ordre des paramètres n'a plus d'importance.
>>> def f(x=0, y=1) : return x,y
...
>>> x = 7
>>> f(x=x)
(7, 1)
Dans ce dernier exemple, comment distinguer le paramètre formel x
et le paramètre effectif x
dans l'appel f(x = x)
?
C'est la question de la portée des variables.
Les noms des paramètres effectifs n'ont ainsi rien de commun avec les noms des paramètres formels correspondants : il ne représente pas la même variable en mémoire... même s'ils ont le même nom !
Lorsqu'on définit des variables à l'intérieur du corps d'une fonction,
ces variables ne sont accessibles qu'à la fonction elle-même.
On dit que ces variables sont des « variables locales »
à la fonction : en quelque sorte, elles sont confinées à l'intérieur de la
fonction.
C'est par exemple le cas des variables n
, u
, u1
, u2
et i
de la fonction fibonacci
.
Chaque fois que la fonction fibonacci
est appelée, Python réserve pour elle
en mémoire un nouvel « espace de noms ».
Les valeurs des variables locales
n
, u
, u1
, u2
et i
sont ainsi stockées dans cet espace de
noms qui est inaccessible depuis l'extérieur de la fonction. Ainsi par
exemple, si nous essayons d'afficher le contenu de la variable u
juste après avoir effectué un appel à la fonction fibonacci
,
on obtient un message d'erreur (name 'u' is not defined
):
>>> from fibo7 import fibonacci
>>> fibonacci(9)
55
>>> u
Traceback ...
NameError: name 'u' is not defined
L'espace de noms réservé lors de l'appel de la fonction est automatiquement détruit dès que la fonction a terminé son travail.
Les variables définies à l'extérieur d'une fonction sont des « variables globales ». Leur valeur est « visible » de l'intérieur d'une fonction, mais la fonction ne peut pas a priori la modifier:
>>> def f(x) : y = 3; x = x + y; return x
...
>>> y = 6
>>> f(1)
4
>>> y
6
Dans l'exemple ci-dessus, on constate que la variable y
n'a pas changé de
valeur entre « avant » et « après » l'appel à la fonction f
bien qu'il
semble qu'elle soit modifiée à l'intérieur de f
(y = 3
).
En fait, il ne s'agit pas des mêmes variables : l'une est globale
(extérieure à la fonction) et vaut 6, l'autre est locale
(intérieure à la fonction) et vaut 3.
Si deux variables portent le même nom à l'extérieur et à l'intérieur d'une fonction, la priorité est donnée à la variable locale à l'intérieur de la fonction; à l'extérieur de la fonction, le problème ne se pose pas : la variable locale n'existe pas.
Nous généralisons ici la méthode de l'appel équivalent que nous avons présenté à l'aide de la fonction de Fibonacci pour illustrer le passage par valeur des arguments d'une fonction.
On considère la trame de la fonction f
ci-dessous et
son appel associé f(...)
:
# définition
def f(e1,e2,e3) :
# code de la fonction
return s1,s2
# appel
x,y = f(a,b,c)
L'appel f(a,b,c)
présent dans l'affectation x,y = f(a,b,c)
est équivalent à la séquence suivante :
Copie des paramètres d'appel effectifs dans les paramètres formels correspondants:
e1,e2,e3 = a,b,c
Dans le cas des types de base (bool
, int
, float
, str
),
ce sont les objets eux-mêmes qui sont copiés.
Dans le cas de variables de type list
, dict
ou d'« objets »
plus structurés, ce sont les références de ces objets qui sont copiées
et non les objets eux-mêmes.
Les variables manipulées au sein de la fonction sont bien des copies des paramètres effectifs, que ce soient selon le type de l'objet, les copies des objets eux-mêmes ou les copies des références sur ces objets .
Exécution du code de la fonction:
# code de la fonction
Le code de la fonction est exécuté « tel quel » sans changement.
Copie des paramètres formels de retour dans des variables temporaires:
tmp1,tmp2 = s1,s2
Cette copie est nécessaire car les paramètres s1
et s2
n'existent
que dans le contexte de la fonction f
appelée.
Destruction des variables locales à la fonction:
# destruction des paramètres formels d'entrée-sortie
del e1,e2,e3,s1,s2
# destruction des éventuelles variables ``v1,v2...`` locales à la fonction
del v1,v2...
L’espace de noms réservé lors de l’appel de la fonction est automatiquement détruit dès que la fonction a terminé son travail.
Utilisation des variables temporaires:
x,y = tmp1,tmp2
S'il n'est pas fait référence à ces variables temporaires dans le contexte appelant (ie. on n'utilise pas les retours de la fonction), ces variables ne seront plus accessibles par la suite.
Destruction des variables temporaires:
del tmp1,tmp2
A la fin de cet appel équivalent, les variables locales à la fonction appelée et toutes les variables temporaires créées pour les besoins de l'appel ne sont plus accessibles au contexte appelant.
Appliquons cette méthode de l'appel équivalent aux exemples du palindrome, du codage en base b et de la mise en majuscules d'une liste de caractères.
2 Palindrome# définition
def palindrome(t) :
rang, ok = 0, True
while rang < len(t)/2 and ok :
if t[rang] != t[len(t)-1-rang] :
ok = False
else :
rang = rang + 1
return ok
# appel
a = "kayak"
x = palindrome(a)
print a,x
Méthode de l'appel équivalent | Appel : palindrome(a) |
copie des paramètres d'appel effectifs | t = a
|
exécution du code de la fonction | rang, ok = 0, True
while rang < len(t)/2 and ok :
if t[rang] != t[len(t)-1-rang] :
ok = False
else :
rang = rang + 1
|
copie des paramètres formels de retour dans des variables temporaires | tmp = ok
|
destruction des variables locales à la fonction | # paramètres d'entrée-sortie
del t, ok
# variables locales
del rang
|
utilisation des variables temporaires | x = tmp
|
destruction des variables temporaires | del tmp
|
# définition
def codage(n,b) :
q, r = n/b, n%b
code = [r]
while q != 0 :
r = q%b
code = [r] + code
q = q/b
return code
# appel
a, base = 23, 5
x = codage(a,base)
print a,base,x
Méthode de l'appel équivalent | Appel : codage(a,base) |
copie des paramètres d'appel effectifs | n,b = a,base
|
exécution du code de la fonction | q, r = n/b, n%b
code = [r]
while q != 0 :
r = q%b
code = [r] + code
q = q/b
|
copie des paramètres formels de retour dans des variables temporaires | tmp = code
|
destruction des variables locales à la fonction | # paramètres d'entrée-sortie
del n,b,code
# variables locales
del q,r
|
utilisation des variables temporaires | x = tmp
|
destruction des variables temporaires | del tmp
|
# définition
def majuscules(t) :
i = 0
while i < len(t) :
t[i] = chr(ord('A') + ord(t[i]) - ord('a'))
i = i + 1
return
# appel
x = ['s','a','l','u','t']
majuscules(x)
x = ['s','a','l','u','t']
print 'avant',x
majuscules(x)
print u'après',x
Méthode de l'appel équivalent | Appel : majuscules(x) |
copie des paramètres d'appel effectifs | t = x
C'est la référence sur la liste |
exécution du code de la fonction | i = 0
while i < len(t) :
t[i] = chr(ord('A') + ord(t[i]) - ord('a'))
i = i + 1
|
copie des paramètres formels de retour dans des variables temporaires | # pas de paramètres de sortie à copier
La fonction ne retourne rien. Elle modifie directement
les éléments de la liste référencée par Le passage par référence permet de modifier l'original ! |
destruction des variables locales à la fonction | # paramètres d'entrée-sortie
del t
# variables locales
del i
Seule la référence |
utilisation des variables temporaires | # pas de variables temporaires
|
destruction des variables temporaires | # pas de variables temporaires
|