Jouons avec les fonctions

Introduction

Reprenons ce qu’on a vu jusqu’ici.

D’une part, on peut créer des variables en assignant des valeurs à celles-ci:

# Création d'une variable `x` avec la valeur 4
x = 4

D’autre part, on peut définir et appeler des fonctions:

# Définition de la fonction:
def dire_bonjour(nom):
    print("Bonjour " + nom)

# Appel
dire_bonjour("Max")

# Affiche: "Bonjour Max"

Fonctions en tant que variables

Il se trouve qu’en Python, on peut assigner des fonctions à des variables. C’est différent d’assigner le résultat de l’appel à une fonction à une variable, et ça permet de retarder l’appel:

# Définition d'une fonction `dire_bonjour_en_français`
def dire_bonjour_en_français(nom):
    print("Bonjour " + nom)

# Définition d'une fonction `dire_bonjour_en_anglais`
def dire_bonjour_en_anglais(nom):
    print("Hello " + nom)

# Assigne une fonction à la variable - aucune fonction
# n'est appelée à ce stade.
ma_fonction_qui_dit_bonjour = dire_bonjour_en_français

# Appel de la fonction (retardé)
ma_fonction_qui_dit_bonjour("Max")

# Affiche: Bonjour Max

De façon cruciale, notez que l’on n’a pas mis de parenthèses à droite lorsqu’on a créé la variable ma_fonction_qui_dit_bonjour.

On peut donc dire que lorsqu’on définit une fonction avec def ma_fonction() et un corps: il y a en réalité deux étapes:

  1. Python stocke le corps de la fonction quelque part

  2. Il assigne le corps de celle-ci à une variable dont le nom est ma_fonction.

En Python, il est assez fréquent d’utiliser de code tel que celui-ci, souvent avec un dictionnaire:

fonctions_connues = {
   "français": dire_bonjour_en_français,
   "anglais": dire_bonjour_en_anglais,
}

# Ici on stocke la langue parlée par l'utilisateur
# et son prénom
langue_parlée = ...
prénom = ....

if langue_parlée in fonctions_connues:
    fonction = fonctions_connues[langue_parlée]
    fonction(prénom)

Fonctions en tant qu’argement d’autres fonctions

On a vu en début de chapitre qu’on peut assigner des fonctions à des variables.

Du coup, rien n’empêche de passer des fonctions en argument d’autres fonctions.

Par exemple:

def appelle_deux_fois(f):
    f()
    f()


def crier():
    print("Aline !")

appelle_deux_fois(crier)

# Affiche:
# Aline !
# Aline !

Fonctions imbriquées

On peut aussi définir une fonction dans une autre fonction:

def affiche_message(message):
    def affiche():
        print(message)
    affiche()

affiche_message("Bonjour")
# affiche: Bonjour

Deux notes importantes:

Premièrement, la fonction affiche() qui est imbriquées dans affiche_message() n’est pas accessible à l’éxtérieur de la fonction qui la contient. En d’autres termes, ce code ne fonctionne pas:

def affiche_message(message):
    def affiche():
        print(message)

affiche()
# NameError: 'affiche' is not defined

C’est un mécanisme similaire aux portées des variables vu précédemment.

Deuxièment, la fonction affiche() à l’intérieur de affiche_message() a accès à l’argument message de la fonction affiche_message. On appelle ça une « closure ».

Fonctions retournant des fonctions

En réalité, on combine souvent les closures avec des fonctions qui retournent d’autres fonctions:

def fabrique_fonction_qui_additionne(n):
    def fonction_résultat(x):
        return x + n
    return fonction_résultat


additionne_2 = fabrique_fonction_qui_additionne(2)
y = additionne_2(5)
print(y)
# Affiche: 7

Un autre paradigme

Le fait qu’on puisse traiter les fonctions comme n’importe quelle autre valeur (c’est-à-dire les assigner à des variables, les passer en argument et les retourner), est caractéristique des langages dits « fonctionnels ». Python est donc à la fois un langages impératif, objet et fonctionnel. On dit que c’est un langage multi-paradigme.