Les fonctions

Non, non, rassurez-vous, pas de maths ici ( quoique...), la notion de fonction en informatique est assez différente de celle utilisée en mathématiques.

Vous avez en fait déjà utilisé des fonctions ( print(), input(),...), mais à quoi exactement correspond cette notion ?

Certaines parties de programme peuvent ( ou doivent ) être parfois utilisées plusieurs fois; il est donc nécessaire de pouvoir les « appeler » sans pour autant ré-écrire à chaque fois l'ensemble du code. Ces parties de programmes peuvent être regroupées dans des fonctions qui faciliteront la lecture et la compréhension du programme principal.

Engrenages

Voici un exemple qui illustre la nécessité et l'intérêt de ce concept.

Supposons que vous deviez écrire un script qui calcule le volume d'un parallélépipède pour différentes valeurs de ses côtés. On pourrait écrire par exemple quelque chose comme :


# cas n°1
longueur = 5
largeur = 10
hauteur = 8
volume = longueur * largeur * hauteur

# cas n°2
longueur = 15
largeur = 12
hauteur = 10
volume = longueur * largeur * hauteur

# cas n°3
longueur = 10
largeur = 10
hauteur = 20
volume = longueur * largeur * hauteur
		

On constate tout de suite que l'on a écrit à peu près 3 fois la même chose, en ne modifiant à chaque fois que la valeur des variables...

Il serait donc judicieux de coder une seule fois les instructions communes aux différentes parties du script ci-dessus, tout en permettant à ces instructions de "s'adapter" à des valeurs de variables différentes :



		fonction permettant de calculer le volume :
			volume = longueur x largeur x hauteur

		# cas n°1
		utiliser la fonction avec les valeurs 5, 10 et 8

		# cas n°2
		utiliser la fonction avec les valeurs 15, 12 et 10

		# cas n°3
		utiliser la fonction avec les valeurs 10, 10 et 20
		

Le script est ainsi beaucoup plus concis et lisible, sa maintenance et son débogage en seront très facilités !!

Une fonction est donc une sorte de "programme dans le programme".

Fonctions en Python et vocabulaire

Comme dans tout langage, une fonction en Python suit des règles d'écriture et d'utilisation précises.

Définition d'une fonction

La définition d'une fonction correspond à l'écriture des instructions qui effectueront la tache pour laquelle elle est conçue.

Dans le cas d'un langage compilé ( comme C, Java, C++,...), la définition des fonctions peut se faire n'importe où dans le code, en dehors du corps principal du programme.
Dans le cas des langages de script comme Python, la définition des fonctions doit se faire avant leur utilisation dans le code, donc généralement en tout début de script, après les éventuelles instructions d'importation de module(s).


	def nom_de_la_fonction(paramètres):
		.....................
		Instruction(s)
		.....................
		return résultat
		
  • en Python, la définition d'une fonction commence par le mot-clé def
  • une fonction a un nom; les mêmes restrictions que pour les noms de variables s'appliquent ici aussi
  • une fonction peut avoir des paramètres : il s'agit de une ( ou plusieurs ) variables ( séparées par des virgules ) qui "récupéreront" les valeurs que l'on aura "envoyées" à la fonction pour qu'elle travaille avec ( ces valeurs sont appelées arguments de la fonction, nous verrons plus loin comment on passe des arguments à une fonction ).
    Les paramètres sont optionnels. Une fonction peut en avoir zéro, un ou plusieurs.
  • les instructions correspondant au rôle de la fonction sont indentées par rapport au mot-clé def.
  • à la fin de la fonction, le mot clé return suivi du résultat du traitement ( une valeur particulière ou le contenu d'une variable ) permet de retourner ( c'est à dire, "renvoyer" ) le résultat du travail de la fonction. Attention, le fait qu'une fonction retourne un résultat ne veut pas dire qu'elle l'affiche !...
    Une fonction peut retourner plusieurs résultats différents ( séparés par des virgules : il s'agit en réalité d'un tuple de valeurs ), ou aucun ( on parle plutôt dans ce cas de procédure.)

Pour l'exemple d'introduction, on pourrait ainsi définir une fonction calcul_volume() de la façon suivante :


def calcul_volume(longueur, largeur, hauteur): # la fonction a besoin de 3 paramètres pour faire le calcul du volume
	volume = longueur * largeur * hauteur
	return volume
		

Une fonction retourne donc un résultat d'un certain type.

Les questions du type et du ( ou des ) paramètre(s) d'une fonction sont celles qu'il faut TOUJOURS se poser avant de construire une fonction.

Quelques exemples de définition de fonctions :

Appel d'une fonction

L'appel d'une fonction correspond au moment où on l’exécute; cet appel prend la forme d'une instruction généralement placée dans le corps principal du programme. ( mais pas obligatoirement : une fonction peut appeler une autre fonction ! )

Lors de l'appel d'une fonction, il se passe en réalité deux processus au même endroit du script :

  • on passe à la fonction certains arguments qui seront "récupérés" par les paramètres de la fonction, puis elle exécute la tache pour laquelle elle est conçue.
  • on "revient" ensuite dans le corps principal du script une fois l’exécution de la fonction terminée, et on récupère le résultat éventuellement retourné par la fonction

( Tout se passe donc comme si l'instruction d'appel avait été remplacée dans le code par les instructions de la fonction. )

Appel de fonction

L'appel d'une procédure ( fonction sans résultat retourné ) se fait simplement en indiquant son nom, avec entre parenthèses, la liste du ( ou des ) arguments(s) à lui passer, dans le même ordre que celui des paramètres dans sa définition.( si la procédure n'a aucun paramètre, on ne lui passe aucun argument ! ).
Les arguments peuvent être des valeurs, des noms de variables, des expressions, etc...


nom_de_la_fonction(arg1, arg2,...)
			

Lors de l'appel d'une fonction par contre, il faut en plus "faire quelque chose" du résultat retourné par la fonction, comme l'afficher ou l'affecter à une variable :


print(nom_de_la_fonction(arg1, arg2,...))  # affichage de la valeur retournée par la fonction

result = nom_de_la_fonction(arg1, arg2,...)	# la variable result contiendra la valeur retournée par la fonction
			

Quelques exemples d'appel de fonction ou de procédure :


# définition des fonctions et procédures

def message_de_bienvenue( nom ):
	print('Bonjour', nom ,'et bienvenue dans ce programme.')

def volume_sphere(r):
	volume = (4/3)*pi*r**3
	return volume

def division( n1, n2 ):
	quotient = n1 // n2
	reste = n1 % n2
	return quotient, reste

def calcul_volume(longueur, largeur, hauteur): # la fonction a besoin de 3 paramètres pour faire le calcul du volume
	volume = longueur * largeur * hauteur
	return volume

# Programme principal
pi = 3.1415

nom = input('Quel est votre nom ?')
message_de_bienvenue( nom ) # appel de la procédure d'affichage du message d'accueil

volume = volume_sphere(5) # calcul du volume d'une sphère de rayon r = 5
print( volume )

volume = calcul_volume(10, 5, 2)
print( volume )

quotient, reste = division(125, 23) # il faut passer 2 arguments, et récupérer 2 résultats !!
print('Quotient =', quotient)
print('Reste =', reste )
		

On peut remarquer qu'il n'y a pas d'ordre particulier pour la définition des fonctions, du moment que cette définition se fait avant l'appel de la fonction.

Portée des variables

Dans l'exemple précédent, vous vous êtes ( peut-être ) étonnés de voir le même nom de variable ( volume, nom, quotient,... ) dans le programme principal et dans la définition des fonctions; s'agit-il pour autant de la même variable ? En réalité, c'est un peu plus complexe...

Variables locales et globales

  • Une variable créée à l'intérieur d'une fonction est appelée une variable locale.
  • Une variable créée en dehors de toutes les fonctions ( donc dans le corps principal du programme ) est appelée une variable globale.

Portée des variables

La portée d'une variable indique la zone du code dans laquelle cette variable est utilisable, ou au contraire, à quels endroits du script elle est "inconnue" :

  • Les variables globales sont accessibles à toutes les instructions du script : leur portée est donc le script en entier, programme principal comme fonction(s).
  • une variable locale a une portée limitée à la fonction dans laquelle elle est définie.

Quand un script rencontre le nom d'une variable dans une fonction, il va rechercher cette variable en élargissant son champ de recherche : il cherche d'abord cette variable dans la portée de la fonction ( et notamment dans ses paramètres ); s'il ne la trouve pas, il passe alors au "niveau supérieur" et la recherche dans le corps principal du script. Si il ne la trouve toujours pas, alors il génère une erreur.

Exemple 1

Si on reprend une partie du script du paragraphe précédent :


# définition des fonctions et procédures

def message_de_bienvenue( nom ):
	print('Bonjour', nom ,'et bienvenue dans ce programme.')

def volume_sphere(r):
	volume = (4/3)*pi*r**3
	return volume

def division( n1, n2 ):
	quotient = n1 // n2
	reste = n1 % n2
	return quotient, reste

# Programme principal
pi = 3.1415 # variable globale

nom = input('Quel est votre nom ?')
message_de_bienvenue( nom ) # appel de la procédure d'affichage du message d'accueil

volume = volume_sphere(5) # calcul du volume d'une sphère de rayon r = 5
print( volume )

quotient, reste = division(125, 23) # il faut récupérer 2 résultats !!
print('Quotient =', quotient)
print('Reste =', reste )
		

Cette notion de portée est fondamentale en informatique, elle existe dans tous les langages. C'est ce qui permet aux ordinateurs de gérer efficacement leur mémoire.

Il faut bien en tenir compte et y faire attention, c'est la source de pas mal d'erreurs parfois délicates à corriger : tenter d’accéder à une variable locale à une fonction alors que l'on n'est pas dans cette fonction engendrera une erreur d'exécution, comme si cette variable n'avait jamais été définie du tout...

Inversement, toujours donner le même nom à des variables locales et globales ( comme c'est le cas dans les exemples précédents ) n'est pas une bonne pratique, même si "ça marche !"; donner des noms différents permet de rendre le code plus lisible et de bien distinguer ce qui est local ou global.

Exemple 2


def surface_cercle( r ):
	pi = 3.1415926
	s = pi*r**2
	return s

print(pi)
surface = surface_cercle( 12 )
print(surface)
		

Traceback (most recent call last):
  File "cercle.py", line 7, in 
    print(pi)
NameError: name 'pi' is not defined
		

Le script génère une erreur : la tentative d'affichage de la variable pi dans le programme principal se solde par un échec, cette variable n'étant que locale à la fonction.

La solution est bien évidemment de définir la variable pi dans le programme principal, et ainsi de la rendre globale :


def surface_cercle( r ):
	s = pi*r**2
	return s

pi = 3.1415926
print(pi)
surface = surface_cercle( 12 )
		

3.1415926
452.3893344

>>>
		

Mais attention : c'est une très mauvaise pratique de multiplier les variables globales; il faut limiter leur nombre au maximum, sinon de nombreuses confusions sont à prévoir, comme modifier une variable globale alors qu'on croyait modifier une variable locale ( ça n'aura pas du tout le même effet !! ).
Pensez-y quand vous codez !

QCM d'entraînement

Exercices

Méthode de travail avec les fonctions

Il est important d'adopter une méthode rigoureuse lors de la construction d'une fonction.

On commence d'abord à réfléchir aux spécifications de cette fonction : quels sont les paramètres qu'elle nécessite ( et donc quels seront les arguments qu'il faudra lui passer ) ( les préconditions ), qu'est-ce qu'elle doit retourner, quel est le type de la variable de retour, etc...( les postconditions ).

Il est d'usage de préciser toutes ces informations dans la docstring ( = "documentation string" ) de la fonction : il s'agit de commentaires placés au début de la définition de la fonction et qui constituent sa documentation
Voici un exemple pour une fonction vue dans le cours :


def division( n1, n2 ):
	"""
	Fonction pour calculer le quotient et le reste de la division de deux nombres.

	Pré :
		deux nombres n1, n2
	Post :
		deux entiers dans l'ordre : quotient, reste de la division de n1 par n2
	"""

	quotient = n1 // n2
	reste = n1 % n2
	return quotient, reste
			

On précise ainsi quelles sont les spécifications en entrée et en sortie de la fonction, mais de plus, cette docstring est alors accessible depuis l'interpréteur Python grâce à la fonction help() ( ceci est valable pour toute fonction ! ) :


>>> help(division)
Help on function division in module __main__:

division(n1, n2)
    Fonction pour calculer le quotient et le reste de la division de deux nombres.

    Pré :
    	deux nombres n1, n2
    Post :
    	deux entiers dans l'ordre : quotient, reste de la division de n1 par n2


>>>
			

DORÉNAVANT, DE LA MÈME MANIÈRE QUE VOTRE CODE DOIT ÊTRE COMMENTÉ, VOS FONCTIONS DEVRONT TOUJOURS FOURNIR A L'UTILISATEUR UNE DOCSTRING.

Fonction cube (fichier exercice_A25.py)

Écrire une fonction cube() qui retourne le cube de son paramètre.

Fonction somme version 1.0 (fichier exercice_A26.py)

Définir une fonction somme1() qui prend en paramètres 3 nombres ( entiers ou pas ) et qui retourne leur somme.

Fonction à l'envers (fichier exercice_A28.py)

Définir une fonction envers() qui prend comme paramètre une chaîne de caractères, et qui retourne la chaîne avec ses caractères en sens inverse.

Procédure table de multiplication (fichier exercice_A30.py)

Définir une fonction qui prend un entier en paramètre, et qui affiche la table de multiplication de cet entier ( multiples de 1 à 9 seulement )

Utiliser ensuite cette fonction pour afficher les tables de multiplication de 1 à 10.