Les fonctions (notions avancées)¶
décompactage des paramètres¶
Lors de l’appel d’une fonction, il est possible d’utiliser le contenu d’un tableau
pour désigner les paramètres. On utilise pour cela l’opérateur *
parfois
appelé unpack operator.
Par exemple, la fonction divmod()
attend deux paramètres, le numérateur et
le dénominateur et retourne le résultat de la division entière et le reste. Il
est possible d’appeler cette fonction en décompactant un tableau de deux éléments.
t = [20, 4]
resultat, reste = divmod(*t)
print("Le résultat de la division est", resultat, "avec comme reste", reste)
# Affiche Le résultat de la division est 5 avec comme reste 0
Dans l’exemple ci-dessus, l’appel à divmod(*t)
est un exemple de décompactage.
Le premier élément du tableau est passé en premier paramètre et le second élément
en deuxième paramètre.
Prudence
Pour que le décompactage fonctionne, il faut que l’ordre des éléments du tableau, leur type et leur nombre correspondent à ce que la fonction attend comme paramètres.
Astuce
Le décompactage est possible pour toutes les structures de données itérables. Donc cela fonctionne également avec les tuples ou les ensembles.
t = (20, 4)
resultat, reste = divmod(*t)
print("Le résultat de la division est", resultat, "avec comme reste", reste)
# Affiche Le résultat de la division est 5 avec comme reste 0
Nous avons vu que Python accepte également le passage de paramètres par leur nom. Il donc également possible de décompacter un dictionnaire. La clé donne le nom du paramètre et la valeur, la valeur du paramètre.
Si nous reprenons l’exemple que nous avions pris dans un chapitre précédent :
def dire_bonjour_a(prenom, nom):
print("Bonjour", prenom, nom)
Nous pouvons décompacter une dictionnaire lors de l’appel avec l’opérateur
**
:
d = {"nom": "Gayerie", "prenom": "David"}
dire_bonjour_a(**d)
# Affiche Bonjour David Gayerie
Astuce
Vous pouvez utiliser l’opérateur de décompactage *
sur un dictionnaire mais
cela signifie que vous passez à la fonction les clés du dictionnaire
comme paramètres.
Astuce
Vous pouvez combiner le décompactage à partir d’une séquence et d’un dictionnaire pour passer à la fois des paramètres et des paramètres nommés lors de l’appel d’une fonction.
s = ["David"]
d = {"nom": "Gayerie"}
dire_bonjour_a(*s, **d)
# Affiche Bonjour David Gayerie
compactage des paramètres¶
Symétriquement, il est possible de déclarer une fonction qui accepte un nombre
quelconque de paramètres. Pour cela on compacte les paramètres sous la forme
d’un tuple grâce à l’opérateur *
. Par convention, on appelle généralement
ce tuple args
:
def moyenne(x, *args):
"""Calcule la moyenne d'un nombre quelconque de valeurs passées en paramètres."""
nb = 1 + len(args)
somme = x
for y in args:
somme += y
return somme / nb
La fonction ci-dessus oblige à passer au moins un paramètre appelé x
.
Elle utilise le compactage pour représenter le reste des paramètres
sous la forme d’un tuple que la fonction peut ensuite parcourir pour calculer
la somme et faire la moyenne. Donc, les appels suivants à la fonction sont valides :
resultat = moyenne(5)
print(resultat)
# Affiche 5.0
resultat = moyenne(5, 2)
print(resultat)
# Affiche 3.5
resultat = moyenne(5, 2, 10, 20, 12, 22)
print(resultat)
# Affiche 11.833333333333334
Une fonction peut également accepter un nombre quelconque de paramètres nommés en
compactant les paramètres sous la forme d’un dictionnaire grâce à l’opérateur
**
. Par convention, on appelle généralement ce dictionnaire kw
ou kwargs
:
def afficher_params(**kwargs):
"""affiche tous les paramètres nommés passés en paramètres"""
for k, v in kwargs.items():
print("Paramètre", k, "qui a comme valeur", v)
Il est possible d’appeler cette fonction avec n’importe quels paramètres nommés :
afficher_params(prenom="David", taille=174)
Enfin, il est possible d’associer les deux formes de compactage est ainsi créer une fonction qui accepte n’importe quels paramètres :
def fonction_libre(*args, **kwargs):
pass
Appel récursif de fonction¶
Une fonction peut bien évidemment appeler une autre fonction dans le corps de
son traitement. Mais une fonction peut aussi s’appeler elle-même. On parle
alors d’appel récursif. Un appel récursif implique toujours une condition
d’arrêt sinon la fonction va s’appeler sans fin… ou plus exactement il y aura
une fin appelée débordement de pile d’appel (call stack overflow) qui fait
échouer le programme avec une erreur de type RecursionError
:
def appel_recursif():
"""Si vous appelez cette fonction, elle fera échouer votre programme !"""
appel_recursif()
Une condition d’arrêt empêche la fonction de s’appeler elle-même à l’infini. Un exemple classique d’appel récursif et le calcul de la factorielle d’un nombre. Sachant que :
n! = n x (n-1)!
0! = 1
On peut écrire la fonction factorielle(n)
de manière récursive :
def factorielle(n):
if n > 0:
return n * factorielle(n-1)
else:
return 1
Déclaration de fonction dans une fonction¶
Il est possible de déclarer une fonction dans une fonction.
def outter(message):
def inner():
print(message)
inner()
outter("hello")
# Afficher hello
Dans l’exemple ci-dessus, la fonction outter(message)
prend un paramètre, elle définit
la fonction inner()
qui affiche la valeur du message passé à outter(message)
.
Puis la fonction outter(message)
appelle la fonction inner()
. On voit
que ces deux fonctions possèdent un lien entre-elles puisque inner()
peut
utiliser le paramètre passé à outter(message)
. On désigne ce phénomène
sous le nom de fermeture (closure) pour indiquer que, lors de sa déclaration,
un fonction décrit un espace fermé de valeurs auxquelles elle a accès. En
Python, une fonction déclarée dans une autre fonction inclut dans sa fermeture
les paramètres et les variables de la fonction englobante.
Note
Dans des cas avancés, la fonction interne peut avoir besoin de modifier une
variable déclarée dans la fonction englobante. Il faut pour cela utiliser le
mot-clé nonlocal
pour indiquer que la variable n’est pas
déclarée dans la fonction interne mais dans la fonction englobante :
1 2 3 4 5 6 7 | def outter(prenom):
message = None
def inner():
nonlocal message
message = "Bonjour %s" % prenom
inner()
print(message)
|
À la ligne 4, on précise que la variable message
n’est pas locale à la fonction
inner()
, ce qui signifie que cette variable fait référence à celle déclarée
dans la méthode outter(message)
.
Note
Cet usage avancé de la déclaration de fonction sera très utile lorsque nous créerons des décorateurs de fonction.
La fonction comme type Python¶
Une fonction est un type Python comme un autre. Elle n’a pas un statut plus particulier dans le langage que les entiers, les chaînes de caractères ou les séquences. Elle est simplement appelable (callable). Mais nous verrons avec la programmation orientée objet en Python que n’importe quel type peut être appelable.
Donc si une fonction n’est pas différente des autres types, cela signifie qu’il est possible d’affecter une fonction à une variable, de passer une fonction en paramètre d’une autre fonction ou encore de retourner une fonction comme résultat à l’appel d’une fonction.
# on affecte la fonction min à une variable
ma_fonction_min = min
x = ma_fonction_min(2,3)
print(x)
# Affiche 2
Les fonctions anonymes (lambdas)¶
Une lambda est une fonction anonyme (c’est-à-dire une fonction qui est déclarée sans être associée à un nom). Le terme lambda est emprunté à la méthode formelle du lambda-calcul. Les fonctions lambda (ou plus simplement les lambdas) sont utilisées dans la programmation fonctionnelle.
En Python pour déclarer une fonction anonyme, on utilise le mot-clé
lambda
:
neg = lambda x: -x
print(neg(1))
# Affiche -1
somme = lambda x, y: x + y
print(somme(2, 3))
# Affiche 5
Les lambdas sont utilisées pour déclarer des traitements très simples comme ci-dessus et qui peuvent s’écrire sur une seule ligne. Contrairement aux fonctions nommés, on considère que la fonction lambda retourne toujours le dernier résultat produit. Ainsi :
lambda x, y: x + y
retourne implicitement le résultat de l’addition des paramètres x
et y
.
Par soucis de simplification d’écriture, la liste des paramètres d’une lambda
ne s’écrivent pas entre parenthèses.
Les fonctions lambdas sont très utiles lorsqu’une fonction attend en paramètre une autre fonction. On peut ainsi passer en paramètre une fonction anonyme.