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

Note

Il est même possible de faire des choses un peu dangereuses car vite incompréhensibles, telles que :

min, max = max, min

Le code ci-dessus affecte min() à max() et max() à min(). Donc maintenant :

print(max(1,9))
# Affiche 1

print(min(1,9))
# Affiche 9

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.