Chapitre 12 : Fonctions

         1 – Principe

    En programmation, les fonctions sont très utiles pour réaliser plusieurs fois la même opération au sein d’un programme. Elles permettent également de rendre le code plus lisible et plus clair en le fractionnant en blocs logiques.


Vous connaissez déjà certaines fonctions Python, par exemple math.cos(angle) du module math renvoie le cosinus de la variable angle exprimée en radian. Vous connaissez aussi des fonctions internes à Python comme range() ou len(). Pour l’instant, une fonction est à vos yeux une sorte de «boîte noire »:

À laquelle vous passez une (ou zero ou plusieurs) valeur(s) entre parenthèses. Ces valeurs sont appelées arguments.
Qui effectue une action. Par exemple random.shuffle() permute aléatoirement une liste.
Et qui renvoie éventuellement un résultat.
Par exemple si vous appelez la fonction range() en lui passant la valeur 5 (range(5)), celle-ci vous renvoie une liste de nombres entiers de 0 à 4 ([0, 1, 2, 3, 4]).

Au contraire, aux yeux du programmeur une fonction est une portion de code effectuant une action bien particulière. Avant de démarrer sur la syntaxe, revenons sur cette notion de «boîte noire »:

Une fonction effectue une tâche. Pour cela, elle reçoit éventuellement des arguments et renvoie éventuellement un résultat. Ce qui se passe en son sein n’intéresse pas directement l’utilisateur. Par exemple, il est inutile de savoir comment la fonction math.cos() calcule un cosinus, on a juste besoin de savoir qu’il faut lui passer en argument un angle en radian et qu’elle renvoie le cosinus de cet angle. Ce qui se passe au sein de la fonction ne regarde que le programmeur (c’est-à-dire vous dans ce chapitre).
Chaque fonction effectue en général une tâche unique et précise. Si cela se complique, il est plus judicieux d’écrire plusieurs fonctions (qui peuvent éventuellement s’appeler les unes les autres). Cette modularité améliore la qualité générale et la lisibilité du code. Vous verrez qu’en Python, les fonctions présentent une grande flexibilité.

         2 – Définition

    Pour définir une fonction, Python utilise le mot-clé def et si on veut que celle-ci renvoie une valeur, il faut utiliser le mot-clé return. Par exemple :

Code python

>>> def carre(x):
… return x**2

>>> print carre(2)
4
Remarquez que la syntaxe de def utilise les : comme les boucles for, while ainsi que les tests if, un bloc d’instrutions est donc attendu. De même que pour les boucles et les tests, l’indentation de ce bloc d’instructions (i.e. le corps de la fonction) est obligatoire.

Dans l’exemple précédent, nous avons passé un argument à la fonction carre() qui nous a retourné une valeur que nous avons affichée à l’écran. Que veut dire valeur retournée ? Et bien cela signifie que cette dernière est stockable dans une variable :

Code python

>>> res = carre(2)
>>> print res
4
Ici, le résultat renvoyé par la fonction est stockée dans la variable res. Notez qu’une fonction ne prend pas forcément un argument et ne renvoie pas forcément une valeur, par exemple :

Code python

>>> def hello():
… print « bonjour »

>>> hello()
bonjour
Dans ce cas la fonction hello() se contente d’imprimer la chaîne de caractères « hello » à l’écran. Elle ne prend aucun argument et ne renvoie aucun résultat. Par conséquent, cela n’a pas de sens de vouloir récupérer dans une variable le résultat renvoyé par une telle fonction . Si on essaie tout de même, Python affecte la valeur None qui signifie «rien »en anglais:

Code python

>>> x = hello()
bonjour
>>> print x
None

         3 – Passage d’arguments

    Le nombre d’argument(s) que l’on peut passer à une fonction est variable. Nous avons vu ci-dessus des fonctions auxquelles on passait 0 ou 1 argument. Dans les chapitres précédentes, vous avez vu des fonctions internes à Python qui prenaient au moins 2 arguments, pour rappel souvenez-vous de range(1,10) ou encore range(1,10,2). Le nombre d’argument est donc laissé libre à l’initiative du programmeur qui est en train de développer une nouvelle fonction.

Une particularité des fonctions en Python est que vous n’êtes pas obligé de préciser le type des arguments que vous lui passez, dès lors que les opérations que vous effectuez avec ces arguments sont valides. Python est en effet connu comme étant un langage au typage dynamique, c’est-à-dire qu’il reconnaît pour vous le type des variables au moment de l’exécution, par exemple :

Code python

>>> def fois(x,y):
… return x*y

>>> fois(2,3)
6
>>> fois(3.1415,5.23)
16.430045000000003
>>> fois(‘to’,2)
‘toto’
L’opérateur * reconnait plusieurs types (entiers, réels, chaînes de caractères), notre fonction est donc capable d’effectuer plusieurs tâches !

Un autre gros avantage de Python est que ses fonctions sont capables de renvoyer plusieurs valeurs à la fois, comme dans cette fraction de code :

Code python

>>> def carre_cube(x):
… return x**2,x**3

>>> carre_cube(2)
(4, 8)
Vous voyez qu’en réalité Python renvoie un objet séquentiel qui peut par conséquent contenir plusieurs valeurs. Dans notre exemple Python renvoie un objet tuple car on a utilisé une syntaxe de ce type. Notre fonction pourrait tout autant renvoyer une liste :

Code python

>>> def carre_cube2(x):
… return [x**2,x**3]

>>> carre_cube2(3)
[9, 27]

Enfin, il est possible de passer un ou plusieurs argument(s) de manière facultative et de leur attribuer une valeur par défaut :

Code python

>>> def useless_fct(x=1):
… return x

>>> useless_fct()
1
>>> useless_fct(10)
10
Notez que si on passe plusieurs arguments à une fonction, le ou les arguments facultatifs doivent être situés après les arguments obligatoires. Il faut donc écrire def fct(x, y, z=1):.

         4 – Portée des variables

    Il est très important lorsque l’on manipule des fonctions de connaître la portée des variables. Premièrement, on peut créer des variables au sein d’une fonction qui ne seront pas visibles à l’extérieur de celle-ci ; on les appelle variables locales. Observez le code suivant :

Code python

>>> def mafonction():
… x = 2
… print ‘x vaut’,x,’dans la fonction’

>>> mafonction()
x vaut 2 dans la fonction
>>> print x
Traceback (most recent call last):
File « <stdin> », line 1, in ?
NameError: name ‘x’ is not defined
Lorsque Python exécute le code de la fonction, il connait le contenu de la variable x. Par contre, de retour dans le module principal (dans notre cas, il s’agit de l’interpréteur Python), il ne la connaît plus d’où le message d’erreur. De même, une variable passée en argument est considérée comme locale lorsqu’on arrive dans la fonction:

Code python

>>> def mafonction(x):
… print ‘x vaut’,x,’dans la fonction’

>>> mafonction(2)
x vaut 2 dans la fonction
>>> print x
Traceback (most recent call last):
File « <stdin> », line 1, in ?
NameError: name ‘x’ is not defined
Deuxièmement, lorsqu’une variable déclarée à la racine du module (c’est comme cela que l’on appelle un programme Python), elle est visible dans tout le module.

Code python

>>> def mafonction():
… print x

>>> x = 3
>>> mafonction()
3
>>> print x
3
Dans ce cas, la variable x est visible dans le module principal et dans toutes les fonctions du module. Toutefois, Python ne permet pas la modification d’une variable globale dans une fonction:

Code python

>>> def mafonction():
… x = x + 1

>>> x=1
>>> mafonction()
Traceback (most recent call last):
File « <stdin> », line 1, in <module>
File « <stdin> », line 2, in fct
UnboundLocalError: local variable ‘x’ referenced before assignment
L’erreur renvoyée montre que Python pense que x est une variable locale qui n’a pas été encore assignée. Si on veut vraiment modifier une variable globale dans une fonction, il faut utiliser le mot-clé global :

Code python

>>> def mafonction():
… global x
… x = x + 1

>>> x=1
>>> mafonction()
>>> x
2
Dans ce dernier cas, le mot-clé global a forcé la variable x a être globale plutôt que locale au sein de la fonction.

         5 – Portée des listes

    Soyez extrêmement attentifs avec les types modifiables (tels que les listes) car vous pouvez les changer au sein d’une fonction :

Code python

>>> def mafonction():
… liste[1] = -127

>>> liste = [1,2,3]
>>> mafonction()
>>> liste
[1, -127, 3]
De même que si vous passez une liste en argument, elle est tout autant modifiable au sein de la fonction :

Code python

>>> def mafonction(x):
… x[1] = -15

>>> y = [1,2,3]
>>> mafonction(y)
>>> y
[1, -15, 3]
Si vous voulez éviter ce problème, utilisez des tuples, Python renverra une erreur puisque ces derniers sont non modifiables ! Une autre solution pour éviter la modification d’une liste lorsqu’elle est passée en tant qu’argument, est de la passer explicitement (comme nous l’avons fait pour l’affectation) afin qu’elle reste intacte dans le programme principal.

Code python

>>> def mafonction(x):
… x[1] = -15

>>> y = [1,2,3]
>>> mafonction(y[:])
>>> y
[1, 2, 3]
>>> mafonction(list(y))
>>> y
[1, 2, 3]
Dans ces deux derniers exemples, une copie de y est créée à la volée lorsqu’on appelle la fonction, ainsi la liste y du module principal reste intacte.

         6 – Règle LGI

    Lorsque Python rencontre une variable, il va traiter la résolution de son nom avec des priorités particulières : d’abord il va regarder si la variable est locale, puis si elle n’existe pas localement, il vérifiera si elle est globale et enfin si elle n’est pas globale, il testera si elle est interne (par exemple la fonction len() est considérée comme une fonction interne à Python, i.e. elle existe à chaque fois que vous lancez Python). On appelle cette règle la règle LGI pour locale, globale, interne. En voici un exemple :

Code python

>>> def mafonction():
… x = 4
… print ‘Dans la fonction x vaut’, x

>>> x = -15
>>> mafonction()
Dans la fonction x vaut 4

Code python

>>> print ‘Dans le module principal x vaut’,x
Dans le module principal x vaut -15
Vous voyez que dans la fonction, x a pris la valeur qui lui était définie localement en priorité sur sa valeur définie dans le module principal.
Conseil : même si Python peut reconnaître une variable ayant le même nom que ses fonctions ou variables internes, évitez de les utiliser car ceci rendra votre code confus !

         7 – Exercices

    Conseil : pour ces exercices, écrivez des scripts dans des fichiers, puis exécutez-les dans un shell.

Prédisez le comportement de ce code sans le recopier dans un script ni dans l’interpréteur Python :

Code python

def hello(prenom):
print « Bonjour », prenom

hello(« Patrick »)
print x
Prédisez le comportement de ce code sans le recopier dans un script ni dans l’interpréteur Python :

Code python

x = 10

def hello(prenom):
print « Bonjour », prenom

hello(« Patrick »)
print x
Prédisez le comportement de ce code sans le recopier dans un script ni dans l’interpréteur Python :

Code python

x = 10

def hello(prenom):
print « Bonjour », prenom
print x

hello(« Patrick »)
print x
Prédisez le comportement de ce code sans le recopier dans un script ni dans l’interpréteur Python :

Code python

x = 10

def hello(prenom):
x = 42
print « Bonjour », prenom
print x

hello(« Patrick »)
print x
Créez une fonction qui prend une liste de bases et qui renvoie la séquence complémentaire d’une séquence d’ADN sous forme de liste.
À partir d’une séquence d’ADN ATCGATCGATCGCTGCTAGC, renvoyez le brin complémentaire (n’oubliez pas que la séquence doit être inversée).
Créez une fonction distance() qui calcule une distance euclidienne en 3 dimensions entre deux atomes. En reprenant l’exercice sur le calcul de la distance entre carbones alpha consécutifs de la protéine 1BTA (chapitre sur les chaînes de caractères), refaites la même chose en utilisant votre fonction distance().