4. Les fonctions

4.1. Éléments de base

Une fonction est un bloc nommé regroupant une séquence d'instructions, c'est l'unité de réutilisation de code qui permet la décomposition dite « procédurale ». Les principaux éléments du concept de fonction en python sont les suivants :

  • l'instruction def qui permet de définir un nouvel objet fonction et de l'associer au nom fourni. Comme toute affectation, le nom de la fonction devient une référence d'objet.

  • l'instruction return permet de renvoyer la valeur calculée par la fonction.

  • l'instruction global permet d'étendre (et donc modifier) la portée d'une variable au module englobant ; par défaut la portée d'une variable définie dans une fonction est locale.

  • les arguments d'une fonction sont passés par référence.

La syntaxe générale est la suivante :


def <funcname>(arglist):
    <statementlist>
    [return <value>] 

Remarques :

  • listarg peut être vide, i.e. la fonction peut ne recevoir aucun argument.

  • statementarg constitue le corps de la fonction.

  • l'instruction return est optionnelle. Elle peut apparaître dans le corps de la fonction.

Exemple 12. Un exemple de fonction

Voici un exemple de fonction retournant l'élément maximum d'une liste non vide.


>>> def max(list):
        if len(list) > 0:
            _max = l[0]
            for index in range(1, len(list)):
                if _max < list[index]:
                     _max = list[index]
            return _max

>>> l = [1, 2, 6, 5, 4, 7, -2, 8, -1]
>>> max(l)
8
>>> l = []
>>> max(l)
>>>


4.2. Portée des variables

Définir une fonction c'est définir un nouvel espace de nom, ainsi toutes les déclarations internes à une fonction ne sont pas visibles des autres fonctions. On parle dans ce cas de portée locale [local scope] pour désigner ce comportement. Une fonction est définie au sein d'un espace englobant qu'on appelle module, ce dernier possède une portée globale. Ces deux types sont liées comme suit :

  • Le module englobant défini une portée globale, i.e. un espace où résident les variables créées (par affectation).

  • Chaque appel de fonction (dans le module) défini une nouvelle portée locale, i.e. un espace autonome pour les noms qui n'entre pas en conflit avec l'espace englobant.

  • Les noms affectés sont locaux, sauf déclaration explicite du contraire par l'instruction global

  • Tous les noms restants sont soit globaux (i.e. définis dans le module englobant), soit internes (i.e. les noms pré-définis par python).

Règles des 3 portées L, G et I

  1. La référence à des noms de variables déclenchent la recherche au sein de trois niveaux de portée, locale (L), globale (G) et enfin interne (I).

  2. L'affectation de noms créée ou modifie les noms locaux par défaut

  3. La déclaration par l'instruction global permet de rattacher les noms au module (espace de nom) englobant.

Figure 3.  règles LGI

Schématisation des 3 portées.


Exemple 13. Illustration des portées


# Global scope
foo = 72                  # foo et func sont globaux

def func(bar):            # bar et foobar sont locaux
    # local scope
    foobar = bar + foo    # foo est recherché d'abord dans (L), non trouvé puis dans (I), OK !
    return foobar

print 'res=', func(foo)

# res= 144


L'instruction global

Les principes de cette instruction (déclaration au sens python) peuvent s'énoncer comme suit :

  • elle permet de définir des noms (depuis un espace à portée locale) se rattachant à l'espace de nommage englobant ;

  • les noms globaux doivent être déclarés uniquement s'ils sont affectés dans une fonction ;

  • les noms globaux peuvent être référencés dans une fonction sans être déclarés.

Exemple 14. Illustration de l'utilisation de l'instruction global


# Global scope
x, y = 6, 8

def func():
    # 
    global z, t           # ces variables (typiquement locales) deviennent globales
                          # au module englobant
    z = y - x
    t = 3
    func2()   

def func2():
    t = z + 2             # t ici est local à func2, il masque le t global
    return t              # renvoyer 4 

#
func()
print z, t                # z, t appartiennent bien à l'espace de nom du module

# 2 3


4.3. Passage des paramètres

Les règles de passage des paramètres sont les suivantes :

  • Le passage des paramètres se fait par référence (ce n'est qu'un cas particulier d'affectation après tout). Les paramètres des fonctions sont des références aux objets référencés par l'appelant, et par conséquent :

    • les objets peuvent être partagés entre l'appelant et la fonction appelée.

    • Si les références pointent des objets modifiables [mutables], une modification dans l'appelé affecte l'appelant.

  • L'affectation à des noms de paramètres n'affecte pas l'appelant ; les noms des paramètres sont locaux à la fonction.

Voici un exemple pour illustrer ce qui vient d'être dit :

Exemple 15. passage de paramètres


def func(x, y):
    x    = 27             # la valeur locale de x est changée
    y[0] = 'foofoo'       # impact sur la référence partagée


x = 1
y = [2, 3, 5, 7]

func(x, y)                # x est non mutable, mais y l'est

print x, y

# 1 ['foofoo', 3, 5, 7]

Explications :

  • Puisque x est un nom local à la fonction func, l'affectation à la valeur 27 n'a aucune incidence sur l'appelant. Par contre y dans l'appelant étant une liste (donc mutable), le résultat de l'affectation à y[0] est visible dans l'appelant à l'issue de l'exécution de la fonction.


Autres exemples :

Exemple 16. passage de paramètres (suite)


def swap(x, y):
    x, y = y, x              # seules les valeurs locales sont changées


x, y = 0, 1              
swap(x, y)
print x, y
# 0, 1

x, y = [2, 3, 5, 7], [-2, -1, 0]
swap(x, y)
print x, y
# [2, 3, 5, 7] [-2, -1, 0]


def swap(x, y):
    x[1], y[1] = y[1], x[1]  # modification des références partagées

x, y = [2, 3, 5, 7], [-2, -1, 0]
swap(x, y)
print x, y
# [2, -1, 5, 7] [-2, 3, 0]


def swap(x, y):
    return y, x          


x, y = 0, 1              
x, y = swap(x, y)            # une façon d'envisager l'échange de contenu
print x, y
# 1, 0


Synthèse

En pratique, le passage de paramètres en python ressemble beaucoup à celui de C :

  • Les paramètres non modifiables [mutables] correspondent au passage par référence du C. De fait le passage se fait bien par référence, mais puisqu'il est impossible de changer des objets non modifiable en place, cela revient à faire une copie.

  • Les paramètres modifiables [mutables], tels les listes, les dictionnaires ... correspondent au passage par adresse du C.

Autres modes de correspondances paramètres formels / paramètres effectifs

Jusqu'à présent nous n'avons vu qu'une seule règle implicite, celle de la correspondance classique par position. python dispose d'autres modes. En voici, la liste :

  • par position, de gauche à droite, en passant autant de paramètres effectifs qu'il y a de paramètres formels dans la définition de la fonction.

  • par mots-clés en spécifiant le nom du paramètre (formel) qui reçoit la valeur ou l'expression.

  • par nombre d'arguments variables

  • valeur par défaut, qui permet d'attribuer des valeurs à des paramètres formels, lesquels ne sont pas (forcément) mis en correspondance avec des paramètres effectifs.

Tableau 8. Mode de correspondance entre paramètres formels et paramètres effectifs

SyntaxeDepuisInterprétation
func(value, anothval)appelantcorrespondance par position
func(argA = value, argC = avalue)appelantcorrespondance par nom
def func(arg)fonctiondéfinition et correspondance par nom ou position
def func(arg=val)fonctiondéfinition et valeur par défaut, si le paramètre n'est pas passé à l'appel
def func(*argv)fonctiontuple, correspondance par position pour les arguments restants
def func(**argv)fonctiondictionnaire, correspond aux arguments par mots-clés restant


Exemple 17. Mode par mots-clés et valeurs par défaut


def func(x, y, z=-1, t=-2): 
    print x, y, z, t

u, v, x, y = 0, 1, 2, 3

func(x, y)
# 2 3 -1 -2

func(x, y, z, t)
# 2 3 2 3

func(y=10, x=3)
# 3 10 -1 -2

func(1, 2, 3, 4)
# 1 2 3 4

func(x=1, t=5, y=3)
# 1 3 -1 5

func(u, v, x, y)
# 0 1 2 3


Exemple 18. Nombre variable de paramètres (1)

Ce premier cas permet de récupérer tous les arguments restant par position dans un tuple. Dans cette forme les « arguments normaux » doivent apparaître en premier.


def func( arg, *argv):
    print "Voici les arguments, le premier :", arg
    print "                     le reste :", argv 


func('aaa', 2, 3, 4, 5, 'foo', ('bar', -1, -2, 'foo'))

# Voici les arguments
#    le premier                 : aaa
#    le tuple qui capte lereste : (2, 3, 4, 5, 'foo', ('bar', -1, -2, 'foo'))



Exemple 19. Nombre variable de paramètres (2)

Un autre exemple, moins artificiel que le précédent où il est question de l'intersection d'un nombre quelconque d'ensemble.


def inter(*argv):
    res = []
    if len(argv) > 1:
        _set = argv[1]
        for _elem in argv[0]:          # passer en revue le 1er ensemble
            for _set in argv[1:]:
                if _elem not in _set:
                    break
            else:
                res.append(_elem)                                
    else:
        print "l'intersection suppose au moins deux ensembles !"
    return res 

s1 = [1, 3, 0, 2]
s2 = ['a', 1, 'bbb', 3, '0']
s3 = [ 3, 'aaaa', 'bbb', 1, 'hello', 10]
s4 = [-1, 1, 0, 3]

print "intersection: ", inter(s1, s2, s3)
# intersection:  [1, 3]

print "intersection: ", inter(s1, s2, s3, s4)
# intersection:  [3]


Exemple 20. Nombre variable de paramètres (3)

--- T O D O : ... avec **kvargs.


4.4. Lambda fonction

Cette forme (quasi-équivalente à celle du langage LISP) permet de créer des fonctions anonymes ; en python ces caractéristiques sont les suivantes :

  • La lambda est une expression et non une instruction, elle peut donc apparaître là où def ne peut prendre place. Une telle construction retourne une nouvelle fonction affectable optionnellement à un nom.

  • Le corps d'une lambda est une simple expression et non un bloc d'instructions. L'utilisation du if, par exemple, n'est donc pas possible.

Exemple 21. Lambda fonction


f = lambda x, y: x * y     # pas de return !

print f(2, -5)
# -10

# -------------------------------------------

funclist = [lambda x: x * x, lambda x: x ** 3]
xl = [-10, 20, 30]

for x in xl:
    print x,
    for f in funclist:
        print f(x),
    print
        
# -10 100 -1000
# 20 400 8000
# 30 900 27000


4.5. Ordre supérieur : apply, map

Certains programmes peuvent avoir besoin d'appliquer des fonctions de manière générique sans connaître leurs noms ou leurs paramètres. python offre deux constructions (fonctions) prenant en paramètre une fonction, il s'agit des fonctions apply et map. On parle d'ordre supérieur, car un des paramètres est lui même une fonction.

apply

Cette fonction appelle la fonction qui lui est passée en paramètre, sur la liste des arguments qui lui sont passés. UN exemple simple permet de le comprendre :

Exemple 22. fonction apply


>>> f = lambda x, y: x * y
>>> l = [(2, 5), (3, 4), (-4, 2)]

>>> for x in l:
...    print apply(f, x) 
...
10
12
-8


map

Cette fonction applique la fonction qui lui en passée en paramètre à chacun des éléments d'un objet (séquence) et retourne une liste des résultats de ces appels.

Exemple 23. fonction map


>>> f = lambda x, y: x * y
>>> x = [1, 2, 3]
>>> y = [6, 5, 4]

>>> print map(f, x, y)
[6, 10, 12]

>>> map(pow, [1, 2, 3, 4, 5], [2, 2, 2, 2, 2])
[1, 4, 9, 16, 25]


4.6. Eléments de programmation fonctionnelle

Dans ce contexte nous voulons principalement dire par programmation fonctionnelle outils appliquant des fonctions à des séquences. Typiquement, on a deux fammilles : la sélection basée sur une fonction de test (filter), et l'application de fonction sur des pairs d'item/éléments générant un résultat (reduce).

Exemple 24. filtre

Soit le filtre suivant qui ne retient que les termes vérifiant le prédicat porté par la lambda :


>>> range(-10, 10)
[-10, -9, -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

>>> filter((lambda x: (2 * x + 1) % 3 == 0 or ( x % 2 == 0) ), range(-10, 10)) 
[-10, -8, -6, -5, -4, -2, 0, 1, 2, 4, 6, 7, 8]

L'équivalent (informel) en terme de boucle explicite (for) est :


>>> r = []
>>> for n in range(-10, 10):
...   if (2 * n + 1) % 3 == 0 or ( n % 2 == 0):
...     r.append(n)
... 
>>> r
[-10, -8, -6, -5, -4, -2, 0, 1, 2, 4, 6, 7, 8]


Exemple 25. réduction


>>> reduce((lambda x, y: x + y), [10, 12, 14, 9, 7])
52

>>> reduce((lambda x, y: 10 * x - y), range(1,100))
76543209876543209876543209876543209876543209876543209876543209876543209876543209876543209876543221L

L'équivalent (informel) en terme de boucle explicite (for) de la deuxième réduction est :


>>> l = range(1,100) 
>>> r = l[0]
>>> for n in l[1:]:
...   r = 10 * r - n
... 
>>> r
76543209876543209876543209876543209876543209876543209876543209876543209876543209876543209876543221L


4.7. Définition de liste en compréhension (ou définition intensionnelle)

Définir une liste en compréhension revient à donner une expression (un prédicat) caractérisant la/les propriétés que vérifie(nt) chacun des éléments de la liste. Rappelons que définir en extension revient à énumérer tous les éléments de la liste.

Exemple 26. Liste des entiers cubiques compris entre 0 et 12

Formellement : { x / x^3 et 0 ≤ x ≤ 12 }, soit en python :


>>> [ x ** 3 for x in range(0,13) ]
[0, 1, 8, 27, 64, 125, 216, 343, 512, 729, 1000, 1331, 1728]


Exemple 27. Liste des puissances de 2 inférieures à 1024

Formellement : { x / 2^x et 0 ≤ x ≤ 10 }, soit en python :


>>> l = [2 ** x for x in range(0, 11)]
>>> l
[1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]


Exemple 28. Exemple avec imbrication de boucles


>>> l = [ [x * y for x in [1, 2, 3, 4]] for y in [100, 200, 300, 500]]
>>> l
[[100, 200, 300, 400], [200, 400, 600, 800], [300, 600, 900, 1200], [500, 1000, 1500, 2000]]


4.8. Générateurs & itérateurs

--- T O D O : ...