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:

.. code-block:: python

  # 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 :ref:`portées des variables <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*.

