Les fonctions et les modules

Par Poulet, dernière mise à jour le 29/05/2010 à 17:37:39
Retour au sommaire

Introduction

- Source

Jusqu'à maintenant, nous avons utilisé beaucoup de fonctions et de méthodes prédéfinies, c'est à dire définies avant notre programme. Nous réutilisions en fait du code déjà existant, laissé à notre disposition, pour écrire le nôtre. Cependant, nous allons vite rencontrer le besoin d'écrire nos propres fonctions. Mieux, nous les rangerons dans ce que l'on appelle des modules, qui sont des répertoires de code prêt à être utilisé.

Définir nos propres fonctions

- Source

Ecrire plus de code pour galérer moins

Pour de courts programmes, nous avons pu nous contenter d'écrire notre code en un seul bloc, qui s'exécutait de la première à la dernière ligne (en passant éventuellement par quelques boucles). Nous allons évoluer un petit peu : en définissant nos propres fonctions, dans lesquelles nous rangerons des morceaux de code, nous cassons cette linéarité.

En effet, une fonction ne se préoccupe pas du code qui l'exécute. La fonction print, par exemple, affiche ce que vous lui passez en argument, mais elle ne sait rien du reste de votre code. D'ailleurs, les développeurs de Python qui ont conçu cette fonction ne savent absolument pas ce que vous faites avec. En d'autres termes, une fonction est une boîte noire, qui prend un ou plusieurs arguments, fait quelque chose avec, et qui peut également renvoyer quelque chose.

Ainsi, si nous écrivons une nouvelle fonction, c'est pour qu'elle corresponde à une tâche particulière : multiplier un nombre par trois, inverser une chaîne, lancer une fusée... Cela à plusieurs avantages.

  • Rendre le code réutilisable : en effet, si nous avons une fonction triple qui triple la valeur de son argument, nous pouvons appeler plusieurs fois triple avec différents arguments sans répéter le code nécessaire à la multiplication. Et nous verrons que grâce, aux modules, une fonction peut être partagée par plusieurs projets, sans qu'il soit nécessaire de la réécrire.

  • Organiser notre code : il n'est pas rare qu'un projet dépasse les dix mille lignes de code. Nombreux sont les programmes qui atteignent même le million. Vous imaginez bien qu'un tel projet, sur lequel travaillent plusieurs développeurs, nécessite un minimum d'organisation. Bien souvent, chaque développeur ne travaille que sur une partie du projet, en utilisant les fonctions mises à sa disposition par ses camarades.

  • Simplifier notre pensée : pour développer un gros projet, par exemple un navigateur web, personne ne se lance tête baissée dans l'écriture du programme principal. Au contraire, il est beaucoup plus sage d'écrire des fonctions qui font une seule chose, mais qui la font bien : une fonction qui, à partir d'un URL, télécharge la page correspondante, une autre qui lit une page et télécharge les images liées, et enfin une troisième pour afficher le tout. Chacune de ces fonctions est facile à écrire, car elle est facile à décrire. Et le programme principal n'est plus qu'une habile combinaison des trois.

  • En résumé, il est important de se souvenir qu'un bon programmeur ne se répète jamais. Vous entendez ? Il ne se répète jamais !

Définir une fonction en Python

C'est très simple : on utilise pour cela le mot clef def, suivi du nom de la fonction, de la liste de ses arguments nommés entre parenthèses, de deux points, et de son code, indenté (comme vous savez déjà le faire pour des boucles). Rien de tel qu'un exemple : sauvegardez donc le code suivant dans un nouveau fichier

def ma_fonction(argument1, argument2):
    print("J'affiche", argument1, "et", argument2)

ma_fonction(1, 2)
ma_fonction(3, 12)

puis exécutez ce code. Comme vous pouvez le voir, à l'appel de la fonction, les deux arguments sont remplacés par les valeurs correspondantes dans le corps de la fonction, et se comportent comme des variables. Leur nom n'a pas d'importance, vous pouvez en fait utiliser les mêmes caractères que ceux qui sont autorisés pour les noms de variables (vous pouvez les remplacer par x et y, ou pot_de_fleur et chimpanzé ou ce que vous voulez).

Notre fonction prend ici deux arguments. Elle aurait pu en prendre plus (il suffit de rajouter d'autres noms, séparés par des virgules), moins ou même pas du tout (il suffit de ne rien écrire entre les parenthèses).

Ici, ma_fonction ne retourne rien. Rappelez-vous qu'on dit qu'une fonction retourne une valeur quand, dans le texte du programme, elle peut être remplacée par une valeur après son exécution. Reprenons l'exemple de la fonction input :

print("Entrez votre nom :")
nom = input()
print("Vous vous appelez", nom, ".")

Au moment de l'exécution, l'utilisateur va être sollicité pour rentrer un nom. Supposons qu'il écrive "Marcel", la fonction input va alors renvoyer ce résultat, et tout se passe alors comme si le programme devenait

print("Entrez votre nom :")
nom = "Marcel"
print("Vous vous appelez", nom, ".")

puis print("Entrez votre nom :") print("Vous vous appelez", "Marcel", ".")

En Python, une fonction renvoie une valeur à l'aide de l'instruction return, qui la termine aussitôt (on ressort de la fonction avec la valeur retournée). On peut bien sûr avoir plusieurs instructions return. En mathématiques, on définit par exemple la valeur absolue d'un nombre x par

  • x si x est positif

  • - x si x est négatif

En Python, cette fonction existe déjà, et porte le nom abs. Voici sa définition et une démonstration :

def abs(x):
    if x >= 0:
        return x
    else:
        return -x

print(abs(5))  # Ceci est écrit à l'extérieur de la fonction : c'est notre programme principal
print(abs(-5))

Naturellement, vous n'avez pas besoin de systématiquement redéfinir cette fonction, vous pouvez l'utiliser directement.

Tout ce que nous avons vu auparavant reste utilisable : une fonction peut posséder des variables qui lui sont propres, on peut écrire des boucles dans le code qui la définit, et ainsi de suite. À titre d'exemple, écrivons une fonction qui calcule la moyenne des éléments d'une liste. Pour rappel, la moyenne est donnée par la somme totale des éléments de la liste, que divise la longueur de la liste.

La longueur de la liste, vous savez la calculer : il suffit d'utiliser la fonction len. Pour calculer la somme des éléments, nous allons utiliser une boucle et une variable qui joue le rôle d'accumulateur (à chaque tour de boucle on additionne le nouveau nombre et l'ancienne valeur de cette variable, et on mémorise cette somme). Rien de difficile :

def moyenne(liste):
    n = len(liste)
    accumulateur = 0
    for i in liste:
        accumulateur = accumulateur + i

    return accumulateur/n

Exercices :

  • En réalité, il existe une fonction sum qui calcule la somme des éléments d'une liste. Réécrivez la fonction moyenne en utilisant cette fonction, de façon à ce que le code de moyenne soit aussi court que possible.

  • Codez une fonction qui retourne le triple de la valeur de son argument, puis une qui renvoie la moyenne de ses deux arguments.

  • Codez une fonction qui parcourt une liste (passée en argument) et en retourne le maximum. Il vous faudra écrire une boucle dans le corps de la fonction. En réalité, la fonction max prédéfinie remplit déjà ce rôle. Plus subtil : une fonction qui renvoie le deuxième plus grand nombre d'une liste.

  • Prenez des exercices dans les chapitres qui précèdent celui-ci, et recodez-les sous forme de fonctions qui prennent des arguments et renvoient des résultats.

Une question de portée

- Source

Comme nous venons de le voir, il est possible de définir des variables à l'intérieur des fonctions. Ceci pose un problème : que se passe-t-il si le nom d'une variable est déjà utilisé dans le reste du programme ? La réponse est : rien. Fort heureusement, Python fait la distinction entre ce que l'on appelle les variables locales (internes à une fonction) et les variables globales.

Ainsi, quand on lie (du verbe lier !) une valeur à un nom de variable à l'intérieur d'une fonction, le comportement de Python par défaut est de comprendre que l'on utilise une nouvelle variable, juste à l'intérieur de notre fonction. Regardons l'exemple suivant :

n = 3

def f(x):
    n = x   # On affecte une valeur à n !
    print("À l'intérieur de la fonction, n =", n)

f(12)
print("À l'extérieur de la fonction, n =", n)

Si vous exécutez ce code, vous verrez bien que les valeurs de n affichées sont 12 puis 3. La valeur globale de n n'a pas été altérée par la fonction. En revanche, si nous n'affections pas de nouvelle valeur à n (en retirant la ligne commentée ci-dessus), Python comprendrait automatiquement que le n utilisé dans le reste de la fonction fait référence à la variable globale. On afficherait alors 3 dans les deux cas (essayez !).

En général, l'utilisation de variables globales est fortement déconseillée. De telles variables empêchent en effet la réutilisation d'un code, car elles rendent les fonctions dépendantes les unes des autres, et surtout dépendantes d'un ordre global d'exécution ; en clair, il est plus compliqué de les utiliser dans un contexte différent de celui pour lequel elles ont été conçues initialement.

Mais ce cours serait bien incomplet s'il s'interdisait de parler de tout ce qui peut être mal utilisé par un programmeur vil et peu consciencieux. Ainsi, pour l'intérêt scientifique uniquement, citons le mot clef global qui permet de préciser le caractère global d'une variable, ce qui permet par exemple de la modifier à loisir à l'intérieur d'une fonction.

Voici un exemple d'utilisation du mot clef global (les enfants, ne faites pas ça chez vous !) :

n = 3

def f(x):
    global n
    n = x
    print("À l'intérieur de la fonction, n =", n)

f(12)
print("À l'extérieur de la fonction, n =", n)

Notez bien que, à l'exécution, vous obtiendrez deux fois 12. Le pendant du mot clef global est local, mais il est naturellement moins utilisé puisque, par défaut, les variables locales sont… locales.

Les modules

- Source

Comme tout langage civilisé, Python dispose d'un système de modules qui permet au programmeur de séparer son code dans différents fichiers, ou d'utiliser le code d'autres développeurs sous la forme d'un répertoire de fonctions et de classes prêtes à l'emploi. Un module, en Python, définit des fonctions et des objets pouvant être utilisés depuis un programme.

C'est donc un moyen de réutiliser du code que l'on a pas forcément écrit soi-même, ce qui est bien puisque cela simplifie la vie (et évite de devoir réinventer la roue). De plus, les modules peuvent être écrits dans d'autres langages que Python - ce qui permet d'appeler du code écrit en C pour être performant, du code en Java pour réaliser des applets web ou utiliser les outils de la JVM, ou du code écrit en PHP pour faire le clown (mais c'est plus que rare).

Définir un module

Lors de l'écriture d'un gros programme, comme nous l'avons dit précédemment, il est inutile et casse-cou de penser rédiger l'intégralité du code dans un seul bloc, stocké dans un seul fichier. Il est beaucoup plus raisonnable de séparer les différentes fonctionnalités du code dans différentes fonctions, regroupées dans différents fichiers.

Et non seulement c'est conseillé, mais ça n'a en plus rien de difficile. C'est même exactement ce que nous avons fait jusqu'à maintenant. Créer un module, pour Python, ça n'est rien d'autre que d'enregistrer un fichier .py contenant des définitions d'objets et de fonctions, qui seront importées dans le programme courant.

Importer un module

Nous devons donc apprendre à importer un module déjà écrit. Pour cela, deux possibilités : la première est d'utiliser l'instruction import X, qui importe le module X dans le programme courant. On peut alors utiliser son contenu comme s'il s'agissait des attributs de X (avec un point donc, rappelez-vous du chapitre sur la programmation orientée objet). Prenez par exemple le code suivant :

def fonction(x):
    return x + 2

def fonction2(x):
    return x + 3

et enregistrez-le dans un fichier nommé "unmodule.py". Dans le même répertoire, créez un fichier nommé "unfichier.py", contenant

import unmodule

print(unmodule.fonction(3))

Exécutez ce fichier : normalement, il devrait afficher 5. Le second fichier importe le premier pour réutiliser la première fonction. Remarquez que l'on importe unmodule, et pas unmodule.py.

La deuxième possibilité est de choisir d'importe seulement quelques objets d'un module, mais de les importer définitivement, sans devoir réutiliser le nom du module par la suite (on écrit fonction et plus module.fonction par exemple). Réécrivez "unfichier.py" de la sorte :

from unmodule import fonction2

print(fonction2(3))

Cette fois-ci, nous précisons que nous voulons importer fonction2 mais pas fonction. Ainsi, si nous essayons d'appeler fonction, Python nous signale que le nom n'est pas défini - il n'a pas été recopié dans la liste des noms du programme courant, car nous ne l'avons tout simplement pas importé. Nous aurions pu écrire from unmodule import fonction, fonction2 pour importer les deux noms dans le programme courant.

En réalité, il est possible d'importer tout le contenu d'un module dans le programme courant en utilisant le joker *, comme dans from unmodule import *. Un tel joker veut dire "tout ce qui est défini". C'est radical, mais presque aussi déconseillé que les variables globales : l'instruction from ... import ... remplace les noms qui pouvaient être définis avant l'importation par les nouveaux noms, de manière définitive, et sans avertissement.

Et c'est grave, parce que si vous ne connaissez pas (ce qui arrive souvent) le code du module importé sur le bout des doigts, vous ne savez pas quels noms sont utilisés. Il y a donc des chances pour que vos propres noms soient remplacés à votre insu !

Un module peut également être un programme

Un fichier défini en tant que module peut contenir du code à exécuter. Pour cela, il suffit tout simplement de ne pas l'écrire dans une fonction. Essayez par exemple d'importer (avec import) un module qui contient le code

print("Je suis importé !")

et vous verrez ce message affiché aussi bien à l'importation qu'à l'exécution en tant que programme autonome.

Parfois, on veut simplement définir du code qui ne sera exécuté que si le fichier est exécuté de façon autonome, et pas quand il est importé. Pour cela, Python définit une variable spéciale, nommée __name__ (les deux __ sont là pour signaler son caractère spécial) qui contient, lors d'une exécution autonome, la valeur "__main__". Essayez d'exécuter puis d'importer le module suivant :

def ma_fonction():
    print("Je suis dans la fonction !")

print("Je suis toujours affiché !")

if __name__ == "__main__":
    print("Je ne suis pas affiché lors d'une importation !")