Pas à pas vers la modularité (1/2)

    Publicités

Users Who Are Viewing This Thread (Total: 0, Members: 0, Guests: 0)

Superman

V
Ancien staff
Dec 2, 2009
2,489
0
596
Les fonctions : à vous de jouer

Nous avons utilisé pas mal de fonctions depuis le début de ce tutoriel. On citera pour mémoire print , type et input , sans compter quelques autres. Mais vous devez bien vous rendre compte qu'il existe un nombre incalculable de fonctions déjà construites en Python. Toutefois, vous vous apercevrez aussi que très souvent on crée nos propres fonctions. C'est le premier pas que vous ferez, dans ce chapitre, vers la modularité. Ce terme un peu barbare signifie que l'on va s'habituer à regrouper des parties de notre code que nous serons amenés à réutiliser dans des fonctions. Dans le chapitre suivant, nous apprendrons à regrouper nos fonctions ayant un rapport dans un fichier pour constituer un module, mais n'anticipons pas.

La création de fonctions


Nous allons pour illustrer cet exemple reprendre le code de la table de multiplication, que nous avons vu dans le chapitre précédent et qui, décidément, n'en finit pas de vous poursuivre
langue.png
.

Nous allons emprisonner notre code calculant la table de multiplication par 7 dans une fonction que nous appellerons table_par_7.

On crée une fonction selon le schéma suivant :

Code : Python1
2
def nom_de_la_fonction(parametre1, parametre2, parametre3, parametreN):
# bloc d'instructions




Les blocs d'instructions nous courrent après aussi, quel enfer
langue.png
. Si l'on décortique la ligne de la définition de la fonction, on trouve dans l'ordre :


  • def : le mot-clé qui est l'abréviation de "define" (définir en anglais) et qui est le prélude à toute construction de fonction.
  • Le nom de la fonction, qui se nomme exactement comme une variable (nous verrons par la suite que ce n'est pas par hasard). N'utilisez pas un nom de variable déjà instanciée pour nommer une fonction.
  • La liste des paramètres qui seront fournis à la fonction lors de son appel, séparés par une virgule et encadrée par une parenthèse ouvrante et une fermante (là encore, les espaces sont optionnelles mais aident à la lisibilité).
  • Les deux points, encore et toujours, qui clôturent la ligne.


N.B. : les parenthèses sont obligatoires, quand bien même votre fonction n'attendrait aucun paramètre.

Le code pour mettre notre table de multiplication par 7 dans une fonction serait donc :

Code : Python1
2
3
4
5
6
def table_par_7():
nb = 7
i = 0 # Notre compteur ! L'auriez-vous oublié ?
while i<10: # Tant que i est strictement inférieure à 10,
print(i+1 , "*" , nb , "=" , (i+1)*nb)
i += 1 # on incrémente i de 1 à chaque tour de boucle.




Quand vous exécutez ce code à l'écran, rien ne se passe. Une fois que vous avez retrouvé les trois chevrons essayez d'appeler la fonction :

Code : Python Console 1
2
3
4
5
6
7
8
9
10
11
12
>>> table_par_7()
1 * 7 = 7
2 * 7 = 14
3 * 7 = 21
4 * 7 = 28
5 * 7 = 35
6 * 7 = 42
7 * 7 = 49
8 * 7 = 56
9 * 7 = 63
10 * 7 = 70
>>>




Bien, c'est, euh, exactement ce qu'on avait réussi à faire dans le chapitre précédent, et l'intérêt ne saute pas encore aux yeux, sauf que l'on peut appeler facilement la fonction et réafficher toute la table sans avoir besoin de tout réécrire. Enfin à part ça...

Mais, si on entre des paramètres pour pouvoir afficher la table de 5 ou de 8... ?


Oui, ce serait déjà bien plus utile. Je ne pense pas que vous ayez trop de mal à trouver le code de la fonction :

Code : Python1
2
3
4
5
def table(nb):
i = 0
while i<10: # Tant que i est strictement inférieure à 10,
print(i+1 , "*" , nb , "=" , (i+1)*nb)
i += 1 # on incrémente i de 1 à chaque tour de boucle.




Et là, vous pouvez entrer différents nombres en paramètres, table(8) pour afficher la table de multiplication par 8 par exemple.

On peut aussi envisager de passer en paramètre le nombre de valeurs à afficher dans la table.

Code : Python1
2
3
4
5
def table(nb, max):
i = 0
while i<max: # Tant que i est strictement inférieure à la variable max,
print(i+1 , "*" , nb , "=" , (i+1)*nb)
i += 1




Si vous entrez à présent table(11, 20) , l'interpréteur vous affichera la table de 11, de 1*11 à 20*11. Magique non ?
magicien.png


Dans le cas où on utilise plusieurs paramètres sans les nommer, comme ici, il faut respecter l'ordre d'appel des paramètres, cela va de soi. Si vous commencez à mettre le nombre d'affichages en premier paramètre alors que dans la définition c'était le second, vous risquez d'avoir quelques surprises
blink.gif
. Il est possible d'appeler les paramètres dans le désordre mais il faut dans ce cas préciser leur nom, nous verrons ça plus bas.


Si vous précisez en second paramètre un nombre négatif, vous avez toutes les chances de créer une magnifique boucle infinie... vous pouvez l'empêcher en rajoutant des vérifications avant la boucle, comme par exemple si le nombre est négatif ou nul, je le mets à 10. D'un autre côté, ça va de soi qu'on ne précise pas de nombre négatif quand on vous demande un maximum dans ce cadre. En Python, on préférera mettre un commentaire en tête de fonction ou une docString comme on le verra plus bas, pour indiquer que max doit être positif, plutôt que de faire des vérifications qui au final feront perdre du temps. Une des phrases reflettant la philosophie du langage et qui peut s'appliquer à ce type de situation est we're all consenting adults here. : nous sommes entre adultes consentants (sous-entendu, quelques avertissements en commentaires sont plus efficaces qu'une restriction au niveau du code). On aura l'occasion de retrouver cette phrase plus loin, surtout quand on parlera des objets.

Valeurs par défaut des paramètres


On peut également préciser une valeur par défaut aux paramètres de la fonction. Vous pouvez par exemple dire que le nombre maximum d'affichages doit être de 10 par défaut (c'est-à-dire si l'utilisateur de votre fonction ne le précise pas). Cela se fait le plus simplement du monde :

Code : Python 1
2
3
4
5
6
7
8
9
10
def table(nb, max=10):
"""Fonction affichant la table de multiplication par nb
de 1*nb à max*nb

(max >= 0)
"""
i = 0
while i<max:
print(i+1 , "*" , nb , "=" , (i+1)*nb)
i += 1




Il suffit de rajouter =10 après max. A présent, vous pouvez appeler la fonction de deux façons : soit en précisant le numéro de la table et le nombre maximum d'affichages, soit en ne précisant que le numéro de la table (table(7) ). Dans ce dernier cas, max vaudra 10 par défaut.

J'en ai profité pour ajouter quelques lignes d'explications que vous aurez remarquées. Nous avons mis une chaîne de caractères, sans la capturer dans une variable, juste en-dessous de la définition de la fonction. Cette chaîne est ce qu'on appelle une docstring que l'on pourrait traduire par une chaîne d'aide. Si vous faites help(table), c'est ce message que vous verrez apparaître. Documenter vos fonctions est également une bonne habitude à prendre. Comme vous le voyez on indente cette chaîne et on la met entre triple guillemets. Si la chaîne est sur une seule ligne on pourra mettre les trois guillemets clôturant la chaîne sur la même ligne, sinon on préférera sauter une ligne avant de fermer cette chaîne pour des raisons de lisibilité. Tout le texte d'aide est indenté au même niveau que le code de la fonction.

Enfin, sachez que l'on peut appeler des paramètres par leur nom. Cela est utile pour une fonction comptant un certain nombre de paramètres qui ont tous une valeur par défaut. Vous pouvez aussi utiliser cette méthode sur une fonction sans paramètre par défaut, mais c'est moins courant.

Prenons un exemple de définition de fonction :

Code : Python1
2
def fonc(a=1, b=2, c=3, d=4, e=5):
print("a =",a,"b =",b,"c =",c,"d =",d,"e =",e)




Simple, n'est-ce pas ? Et bien, vous avez de nombreuses façons d'appeler cette fonction. En voici quelques exemples :

InstructionRésultat fonc() a = 1 b = 2 c = 3 d = 4 e = 5 fonc(4) a = 4 b = 2 c = 3 d = 4 e = 5 fonc(b=8, d=5) a = 1 b = 8 c = 3 d = 5 e = 5 fonc(b=35, c=48, a=4, e=9) a = 4 b = 35 c = 48 d = 4 e = 9

Je ne pense pas que des explications suplémentaires s'imposent. Si vous voulez changer la valeur d'un paramètre, vous entrez son nom, un signe égal, et une valeur (qui peut être une variable bien entendu). Peu importe les paramètres que vous précisez (comme vous le voyez, dans cet exemple ou tous les paramètres ont une valeur par défaut, vous pouvez appeler la fonction sans paramètres), peu importe l'ordre d'appel des paramètres.

Signature d'une fonction


On entend par "signature de fonction" les éléments qui permettent au langage de l'identifier. En C++ par exemple, la signature d'une fonction est constituée de son nom et des types de ses paramètres. Cela veut dire que l'on peut trouver plusieurs fonctions du même nom mais avec différents paramètres dans leur définition. Au moment de l'appel de fonction, le compilateur recherche la fonction qui s'applique à cette signature.

En Python comme vous avez pu le voir, on ne précise pas les types des paramètres. Dans ce langage, la signature d'une fonction est tout simplement son nom. Cela signifie que vous ne pouvez définir deux fonctions du même nom (l'ancienne définition est écrasée par la nouvelle si vous le faites).

Code : Python1
2
3
4
5
6
7
8
9
def exemple():
print("Un exemple d'une fonction sans paramètres")

exemple()

def exemple(): # on redéfinit la fonction exemple
print("Un autre exemple de fonction sans paramètres")

exemple()




A la ligne 1 on définit la fonction exemple. On l'appelle une première fois à la ligne 4. On redéfinit à la ligne 6 la fonction exemple. L'ancienne définition est écrasée et l'ancienne fonction ne pourra plus être appelée.

Retenez simplement que, comme pour les variables, un nom de fonction ne renvoie que vers une fonction précise, on ne peut surcharger de fonctions en Python.

L'instruction return


Ce que nous avons fait était intéressant, mais nous n'avons pas encore fait le tour des possibilités de la fonction. Et d'ailleurs, même à la fin de ce chapitre, il nous restera quelques petites fonctionnalités à voir. Si vous vous souvenez bien, il existe des fonctions comme print qui ne retournent rien (attention, retourner et afficher ne sont pas identiques) et des fonctions, telles que input ou type qui retournent une valeur. Vous pouvez capturer cette valeur en mettant une variable devant (exemple variable2 = type(variable1) ). En effet, les fonctions travaillent en général sur des données et retournent le résultat obtenu, suite à un calcul par exemple.

Prenons un exemple simple : une fonction chargée de mettre au carré une valeur passée en paramètre. Je vous rappelle que Python en est parfaitement capable sans avoir à coder une nouvelle fonction, mais c'est pour l'exemple
clin.png
.

Code : Python1
2
def carre(valeur):
return valeur * valeur




L'instruction return signifie qu'on va retourner la valeur, pour pouvoir la récupérer ensuite et la stocker dans une variable par exemple. Cette instruction arrête le déroulement de la fonction, du code placé après le return ne s'exécutera pas.

Code : Python1
variable = carre(5)




La variable variable contiendra après cette instruction 5 au carré, c'est-à-dire 25.

Sachez que l'on peut retourner plusieurs valeurs que l'on sépare par des virgules, et que l'on peut les capturer dans des variables également séparées par des virgules, mais je m'attarderai plus loin sur cette particularité. Retenez simplement la définition d'une fonction, les paramètres, les valeurs par défaut, l'instruction return et ce sera déjà bien
smile.png
.
À la découverte des modules

Jusqu'ici, nous avons travaillé avec les fonctions de Python chargées au lancement de l'interpréteur. Il y en a déjà un certain nombre, et nous pourrions continuer et finir cette première partie sans utiliser de module Python... ou presque. Mais il faut bien que je vous montre cette possibilité des plus intéressantes à un moment donné !

Les modules, qu'est-ce que c'est ?


Un module est grossièrement un bout de code que l'on a enfermé dans un fichier. On emprisonne ainsi des fonctions, des variables, toutes ayant un rapport entre elles. Ainsi, si l'on veut travailler avec les fonctionnalités prévues par le module (celles qui ont été enfermées dans le module), vous n'avez qu'à importer le module et utiliser ensuite toutes les fonctions et variables prévues.

Il existe un grand nombre de modules disponibles avec Python sans avoir besoin d'installer de bibliothèques suplémentaires. Pour cette partie, nous prendrons l'exemple du module math qui contient, comme son nom l'indique, des fonctions mathématiques. Inutile de vous inquiéter, nous n'allons pas nous attarder sur le module-même pour coder une calculatrice scientifique, nous verrons surtout les différentes méthodes d'importation
smile.png
.

La méthode import


En ouvrant l'interpréteur Python, les fonctionnalités du module math ne sont pas incluses. Il s'agit en effet d'un module, il vous appartient de l'importer si vous vous dites "tiens, mon programme risque d'avoir besoin de fonctions mathématiques". Nous allons voir une première syntaxe d'importation.

Code : Python Console1
2
>>> import math
>>>




La syntaxe est facile à retenir : le mot-clé import qui signifie "importer" en anglais, et le nom du module, ici math.

Après l'exécution de cette instruction, rien ne se passe... en apparence. En réalité, Python vient d'importer le module math. Toutes les fonctions mathématiques contenues dans ce module sont maintenant accessibles. Pour appeler une fonction du module, il faut taper le nom du module, un point "." puis le nom de la fonction. C'est la même syntaxe pour appeler des variables du module. Voyons un exemple :

Code : Python Console1
2
3
>>> math.sqrt(16)
4
>>>




Comme vous le voyez, la fonction sqrt du module math retourne la racine carrée du nombre passé en paramètre.

Mais, comment je suis censé savoir quelles fonctions existent, et que fait math.sqrt dans ce cas précis ?


J'aurais dû vous montrer cette fonction bien plus tôt, car oui, c'est une fonction qui va nous donner la solution. Il s'agit de help , qui prend en paramètre la fonction ou le module sur lequel vous demandez de l'aide. L'aide est fournie en anglais, mais c'est de l'anglais technique, c'est-à-dire une forme de l'anglais que vous devrez maîtriser pour programmer, si ce n'est pas le cas. Une grande majorité de la documentation est en anglais, bien que vous puissiez en trouver pas mal en français maintenant.

Code : Python Console 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
>>> help(math)
Help on built-in module math:

NAME
math

FILE
(built-in)

DESCRIPTION
This module is always available. It provides access to the
mathematical functions defined by the C standard.

FUNCTIONS
acos(...)
acos(x)

Return the arc cosine (measured in radians) of x.

acosh(...)
acosh(x)

Return the hyperbolic arc cosine (measured in radians) of x.

asin(...)
-- Suite --




Si vous parlez un minimum l'anglais, vous avez accès à une description exhaustive des fonctions du module math. Vous voyez en haut de la page le nom du module, le fichier le contenant, puis la description du module. Ensuite se trouve une liste des fonctions avec une courte description de chacune.

Tapez "q" pour revenir à la fenêtre d'interpréteur, la touche "espace" pour avancer d'une page, la touche "entrer" pour avancer d'une ligne. Vous pouvez également passer un nom de fonction en paramètre de la fonction help .

Code : Python Console1
2
3
4
5
6
7
8
9
>>> help(math.sqrt)
Help on built-in function sqrt in module math:

sqrt(...)
sqrt(x)

Return the square root of x.

>>>




Ne mettez pas les parenthèses habituelles après le nom de la fonction. C'est en réalité la référence de la fonction que vous envoyez à help . Si vous rajoutez les parenthèses ouvrantes et fermantes après le nom de la fonction, vous devrez préciser une valeur et, si vous le faites, c'est la valeur retournée par math.sqrt qui sera analysée, soit un nombre (entier ou flottant).

Nous reviendrons plus tard sur le concept des références des fonctions. Si vous avez compris pourquoi il ne fallait pas mettre de parenthèses après le nom de la fonction dans help , tant mieux. Sinon, ce n'est pas grave, nous y reviendrons en temps voulu.

Utiliser un espace de noms spécifique


En vérité, quand vous tapez import math , un espace de noms se crée, portant le nom math, contenant les variables et fonctions du module "math". Quand vous tapez math.sqrt(25) , vous précisez à Python que vous souhaitez exécuter la fonction sqrt contenue dans l'espace de noms math . Cela signifie que vous pouvez avoir, dans l'espace de noms principal, une autre fonction sqrt que vous avez défini. Il n'y aura pas de conflit entre la fonction que vous avez créée et que vous appellerez grâce à l'instruction sqrt et la fonction sqrt du module math que vous appellerez grâce à l'instruction math.sqrt .

Mais concrètement, un espace de noms c'est quoi ?


Il s'agit de regrouper certaines fonctions et variables avec un préfixe spécifique. Prenons un exemple concret :

Code : Python1
2
3
import math
a = 5
b = 33.2





  • Dans l'espace de noms principal, celui qui ne nécessite pas de préfixe et que vous utilisez depuis le début de ce tutoriel, on trouve :
    • La variable a
    • La variable b
    • Le module math qui se trouve dans un espace de nom s'appelant math également. Dans cet espace de nom, on trouve :
      • La fonction sqrt
      • La variable pi
      • Et bien d'autres fonctions et variables...


C'est aussi l'intérêt des modules : des variables et fonctions sont stockées à part, bien à l'abri dans un espace de noms, sans risque de conflit avec vos propres variables et fonctions. Mais dans certains cas, vous pourrez vouloir changer le nom de l'espace de noms dans lequel le module importé sera stocké.

Code : Python1
2
import math as mathematiques
mathematiques.sqrt(25)




Qu'est-ce qu'on a fait là ?


On a simplement importé le module math en lui spécifiant qu'il ne devrait pas être contenu dans l'espace de noms math, mais mathematiques. Cela permet simplement de contrôler un peu mieux les espaces de nom des modules que vous importerez. Dans la plupart des cas, vous n'utiliserez pas cette fonctionnalité j'imagine, mais au moins vous savez qu'elle existe. Quand on se penchera sur les packages, vous vous souviendrez probablement de cette possibilité.

Une autre méthode d'importation : from ... import ...


Il existe une autre méthode d'importation qui ne fonctionne pas tout à fait de la même façon. En fonction du résultat attendu, j'utilise l'une ou l'autre méthode indifféremment. Si nous reprenons notre exemple du module math. Admettons que nous avons uniquement besoin dans notre programme de la fonction retournant la valeur absolue d'une variable. Dans ce cas, nous n'allons importer que la fonction, au lieu d'importer tout le module.

Code : Python Console1
2
3
4
5
6
>>> from math import fabs
>>> fabs(-5)
5
>>> fabs(2)
2
>>>




Pour ceux qui n'ont pas encore étudié les valeurs absolues, il s'agit tout simplement de l'opposé de la variable si elle est négative, et de la variable elle-même si elle est positive. Une valeur absolue est ainsi toujours positive.

Vous aurez remarqué qu'on ne met plus le préfixe math. devant le nom de la fonction. En effet, nous l'avons importé avec la méthode from qui charge la fonction depuis le module indiqué et le place dans l'interpréteur au même plan que les fonctions existantes de l'interpréteur, tout comme print . Si vous avez compris les explications sur les espaces de noms, print et fabs sont dans le même espace de noms (principal).

Vous pouvez appeler toutes les variables et fonctions d'un module en tapant "*" à la place du nom de la fonction à importer.

Code : Python Console1
2
3
4
5
>>> from math import *
>>> sqrt(4)
2
>>> fabs(5)
5




A la ligne 1 de notre programme, l'interpréteur a parcouru toutes les fonctions et variables du module math et les a importées directement dans l'espace de noms principal sans les emprisonner dans l'espace de noms math.

Bilan


Quelle méthode faut-il utiliser ?


Vaste question ! Je dirais que c'est à vous de voir. La seconde méthode a l'avantage inestimable que l'on n'a pas besoin de taper le nom du module avant d'utiliser ses fonctions. L'inconvénient de cette méthode est que si on utilise plusieurs modules de cette manière, et que par hasard, deux fonctions du même nom existent dans deux modules différents, l'interpréteur gardera la dernière fonction appelée (je vous rappelle qu'il ne peut y avoir deux variables ou fonctions du même nom). Conclusion... c'est à vous de voir en fonction de vos besoins
langue.png
.