Plan du site  
français  English
pixel
pixel

Articles - Étudiants SUPINFO

Chapitre 03 - Sous-programmes

Généralités algorithmiques

On va présenter dans cette partie la notion de sous-programme d'un point de vue purement algorithmique. Tous les concepts définis seront ensuite explicités lors d'un exercice récapitulatif.

Les spécificités du Python seront elles étudiées dans la partie suivante.

Principe

Un sous-programme est un bloc d’instructions réalisant une certaine tâche. Il possède un nom et est exécuté lorsqu’on l’appelle.

Un script bien structuré contiendra un programme dit principal, et plusieurs sous-programmes dédiés à des fonctionnalités spécifiques.

Quand le programme principal fait appel à un sous–programme, il suspend son propre déroulement, exécute le sous–programme en question, et reprend ensuite son fonctionnement

Trois grands avantages

On décèle au moins trois avantages majeurs à structurer notre code avec des sous-programmes.

Eviter la duplication de code

Il est en effet très fréquent qu’un même bloc de code soit amené à être exécuté plusieurs fois, à divers endroits du programme. Plutôt que de copier/coller ce bloc, on l'encapsulera dans un sous-programme que l'on appelera aux moments opportuns.

[Note]

On parle aussi de factorisation de code.

Favoriser la réutilisation

Un sous-programme écrit pour résoudre un problème donné pourra servir de nouveau dans un autre contexte.

On pourra ainsi créer des librairies de sous-programmes.

Améliorer la conception

L'idée est très simple à comprendre : on réduit la complexité d’un programme en le découpant en sous-programmes plus simples. On crée ainsi ce que l’on appelle une hiérarchie de tâches.

Dans la mesure du possible, on essaiera de limiter un sous-programme à la réalisation d'une seule tâche.

Notion de paramètre

Un sous-programme sert donc à effectuer un traitement générique. Ce traitement porte sur des données, dont la valeur pourra ainsi changer d’un appel du sous-programme à un autre. Ce que l’on appelle paramètres ce sont justement ces données transmises au sous-programme par le programme principal.

Lors de l’implémentation d’un sous-programme, on va donc préciser la liste de tous les paramètres qu’il va utiliser :

Lors de l’utilisation d’un sous-programme, on va alors préciser la valeur de chacun des paramètres qu’il possède :

[Note]

À noter qu’un sous-programme peut très bien ne pas posséder de paramètres.

Selon les langages, on précisera ou non le type de chacun des paramètres. En Python cela ne sera pas le cas, comme nous le verrons dans la suite.

[Note]

Tous les langages ne gèrent pas de la même façon le passage des paramètres. Certains effectuent un passage par valeur (le C par exemple, voir cours 1GCC), c'est-à-dire que les sous-programmes créent des copies de leurs paramètres et ne manipulent donc pas leurs versions originales. D'autres effectuent un passage par référence (comme le Python) ce qui signifie que les sous-programmes créent des alias de leurs paramètres et donc en fait les manipulent directement. Il est clair qu'un passage par valeur interdit la modification des paramètres alors qu'un passage par référence l'autorise.

A noter que certains langages permettent les deux comme le C++, voir 2CPP à ce sujet.

Variables locales versus variables globales

Pour fonctionner un sous-programme peut également avoir besoin d’utiliser des variables qui lui sont propres. On parle alors de variables locales. Ce sont par exemple des résultats de calculs intermédiaires, des compteurs de tours dans une structure itérative, etc. Ces variables ne sont accessibles qu’au sein du sous-programme qui les définit et utilise.

Un sous-programme reçoit donc des données à traiter, les paramètres, et pour ce faire peut avoir besoin de variables locales :

On a vu qu’un sous-programme pouvait utiliser des données transmises par le programme principal sous forme de paramètres. Il peut également manipuler directement des variables définies par le programme principal. On parle alors de variables globales. Il s’agit d’une possibilité mais également d’une mauvaise pratique car cela limite énormément la réutilisabilité des codes d’un projet à un autre.

Un sous-programme peut donc accéder aux variables globales définies dans le programme principal :

Les variables globales étant propres au programme principal, y avoir recours limitera beaucoup la réutilisabilité d'un sous-programme dans un autre projet.

[Warning]

Dans la mesure du possible (ce qui est souvent le cas) on évitera l'utilisation de variables globales.

Deux types de sous-programmes

En algorithmique on distingue deux types de sous-programmes :

  • Les procédures, qui modifient l’état du programme sans retourner de résultat.

  • Les fonctions, qui elles ont pour but de retourner un résultat.

Procédures

Une procédure est ainsi un sous-programme qui ne retourne pas de résultat.

Son rôle est donc d’effectuer des effets de bords, i.e. d'avoir une action sur des objets ou variables qui existent en dehors de la procédure.

Cela peut être par exemple un affichage à l’écran ou une écriture dans un fichier, une modification de variables globales ou de paramètres, etc.

Fonctions

Une fonction est quand à elle un sous-programme qui va retourner un résultat au programme principal. La signification d'une fonction est donc la même qu'en Mathématiques.

En théorie une fonction ne réalise pas d’effets de bord, mais en pratique on pourra se le permettre, toujours en gardant en tête la prohibition des variables globales.

[Note]

Tous les langages de programmation ne distinguent pas nommément ces deux types de sous–programmes. En Python, en C et en C++, on ne manipule ainsi a priori que des fonctions, bien qu’en pratique la distinction se fasse au moment de l'implémentation.

En Pascal ou en PL/SQL, voir cours 2ORC à ce sujet, les deux types de sous-programmes sont par contre clairement séparés.

Exercice

On propose maintenant au lecteur un petit exercice récapitulatif.

Exercice corrigé

1.1.

Donner un sens au schéma ci-dessous, et identifier les concepts définis dans cette première partie.

Rappellons qu'un nombre parfait est un nombre égal à la somme de ses diviseurs stricts. Par exemple 28 est parfait, car 28 = 1+2+4+7+14.

On a ici un programme principal nombresParfaits qui utilise (directement) deux sous-programmes :

  • obtenirBorneMax : fonction sans paramètres qui retourne l'entier jusqu'auquel on recherchera les nombres parfaits. Il est saisi par l'utilisateur.

  • AfficherNombresParfaitsJusqu'à : procédure prenant un entier en paramètre et qui va afficher sur la console la liste des nombres parfaits jusqu'à cet entier. Elle a besoin d'une variable locale (compteur de tours dans une structure for) et elle utilise :

    • estParfait : fonction prenant un entier en paramètre et qui retourne un booléen indiquant si cet entier est parfait ou non. Elle utilise :

      • sommeDesDiviseurs : fonction prenant un entier en paramètre et qui retourne la somme des diviseurs de cet entier. Elle a besoin d'une variable locale (compteur de tours dans une structure for) et elle utilise :

        • estUnDiviseur : fonction prenant en paramètre deux entiers et qui retourne un booléen indiquant si le premier entier divise le second.

Les sous-programmes en Python

On va étudier dans cette partie les particularités du Python vis-à-vis de la notion de sous-programme.

Procédures

L'implémentation d'une procédure commence par le mot clé def, se poursuit par son nom et la liste de ses paramètres. Un bloc est alors reservé aux instructions de la procédure, délimité comme d'habitude par un : et une indentation.

En résumé cela donne cette syntaxe générale :

def maProcedure(para1,para2,...,paraN):
    bloc d’instructions de la procédure
[Note]

On essaiera de respecter la convention classique qui stipule que le nom des procédures doit être en lowerCamelCase.

[Note]

À noter qu’une procédure peut très bien ne pas posséder de paramètres comme nous le verrons sur des exemples.

Example 1.1. Nos premières procédures

Une procédure prenant en paramètre les dimensions d'un rectangle, et qui affiche sur la console son périmètre et son aire :

def rectangle(x,y):
    print("périmètre :",2*(x+y),",aire :",x*y)

Une procédure sans paramètre qui affiche sur la console la valeur du nombre d'or, i.e 1 + 5 2 :

def afficheNombreDor():
    print((1+sqrt(5))/2)

Une fois déclarée, on peut utiliser une procédure comme une instruction prédéfinie du langage.

Ainsi, quand on souhaite en exécuter une, on appelle la procédure en question par son nom, en lui passant autant de paramètres qu’elle en possède. Ces paramètres peuvent être des variables ou des valeurs explicites.

Example 1.2. Utilisation de nos procédures

On appelle la procédure rectangle d'abord avec des valeurs explicites comme paramètres, puis avec des variables :

rectangle(5,2)
longueur,largeur = 6,3
rectangle(longueur,largeur)
afficheNombreDor()
périmètre : 14 ,aire : 10
périmètre : 18 ,aire : 18
1.618033988749895

Fonctions

L'implémentation d'une fonction comporte les mêmes éléments que celle d'une procédure : mot clé def, liste de ses paramètres, usage du : et de l'indentation. A cela s'ajoute le mot clé return suivi de ce que la fonction doit renvoyer.

Voici donc la syntaxe générale :

def maFonction(para1,para2,...,paraN):
    bloc d’instructions de la fonction
    return valeur
[Note]

Une fonction peut très bien retourner plusieurs valeurs, il suffit de séparer celles-ci par des virgules.

[Note]

Une fonction peut contenir plusieurs fois la commande return, mais elle cesse son fonctionnement dès qu’elle en rencontre une.

Example 1.3. Nos premières fonctions

Une fonction prenant en paramètre un nombre et qui retourne son cube :

def cube(x):
    return x*x*x

Une fonction prenant en paramètre deux nombres et qui retournera ces deux nombres dans l'ordre croissant :

def calculMiniMaxi(x,y):
    if x<y:
        return x,y
    else:
        return y,x

Ce dernier exemple montre bien qu'une fonction peut retourner plusieurs valeurs.


Pour utiliser une fonction, on l'appelle par son nom en lui passant autant de paramètres qu’elle en possède. Pour ne pas perdre la(les) valeur(s) retournée(s), on l'(es) incorporera par exemple dans une opération d’affichage, d’affectation, etc.

[Warning]

Ce dernier point est vraiment à prendre en considération par tout débutant, qui aura souvent tendance à utiliser une fonction comme une procédure.

Example 1.4. Utilisation de nos fonctions

Avec le résultat de la fonction cube on réalise un affichage, et avec ceux de la fonction calculMiniMaxi on procède à deux affectations :

print(cube(5))
a,b = 5,-2
min,max = calculMiniMaxi(a,cube(b))
print("Minimum :",min,", Maximum :",max)
125
Minimum : -8 , Maximum : 5

[Note]

Dans l'exemple précédent, une ligne de commande uniquement constituée par exemple de cube(5) se contenterait de calculer cette valeur mais la "perdrait" aussi vite... Il faut donc s’en servir avec par exemple un affichage, ce qui a été fait ici, une affectation ou encore comme paramètre d’une autre fonction.

Duck Typing

Comme le lecteur a pu le constater, en Python on ne précise pas les types attendus des paramètres des sous-programmes. Cela implique que l’on peut utiliser un sous-programme avec des paramètres de n’importe quel type, à la condition que les opérations du sous-programme soient compatibles avec les types des paramètres. Cette grande souplesse s'appelle le "Duck Typing".

[Note]

On reviendra sur ce principe sous d'autres formes dans le cours 2OOP.

Cette possibilité n'est pas offerte par tous les langages, loin s'en faut.

Example 1.5. Une fonction illustrant le Duck Typing

Cette fonction d'addition est compatible avec des nombres ou avec des chaînes de caractères :

def addition(x,y):
    return x + y

print(addition(666,1))
print(addition("Brown ",'Sugar'))
667
Brown Sugar

Pour finir cette partie, on propose au lecteur une petite citation pittoresque qui illustre ce concept et justifie son nom de "Duck Typing" :

If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck.

Dans sa version française :

Si un animal ressemble à un canard, nage comme un canard et cancane comme un canard, alors il s’agit probablement d’un canard.

Et pour les cinéphiles amateurs de dialogues truculents :

Y a pas à dire, dans la vie il faut toujours se fier aux apparences. Quand un homme a un bec de canard, des ailes de canard et des pattes de canard, c'est un canard. Et ce qui est vrai pour les canards est vrai aussi pour les petits merdeux.

--Michel Audiard

Valeurs par défaut des paramètres

Les paramètres d’un sous-programme peuvent comporter des valeurs par défaut qui seront utilisées si on n'en transmet pas d'autres.

Concrètement, deux cas se présentent :

  • Lors de l’appel on ne précise pas de valeurs pour les paramètres en question et le sous-programme utilise celles par défaut.

  • Si on précise des valeurs ce sont celles-ci qui sont utilisées.

Example 1.6. Une procédure avec des valeurs par défaut pour tous ses paramètres

Tous les paramètres de la procédure rectangle ont des valeurs par défaut, ce qui rend licite l’appel de ce sous-programme sans paramètres :

def rectangle(x=3,y=1):
    print("périmètre :",2*(x+y),"aire :",x*y)

rectangle()
rectangle(2)
rectangle(7,5)
périmètre : 8 aire : 3
périmètre : 6 aire : 2
périmètre : 24 aire : 35

Dans le cas où l'on fournit moins de valeurs que de paramètres, on utilise ces valeurs pour les paramètres les plus à gauche. ainsi l'appel "rectangle(2)" se fera avec x égal à 2 et y égal à 1.

Pour utiliser uniquement la valeur par défaut de x, on devra lors de l'appel indiquer le nom du paramètre à remplacer :

rectangle(y=2)
périmètre : 10 aire : 6

Example 1.7. Une procédure avec des valeurs par défaut pour certains de ses paramètres

La même procédure que précédemment, mais cette fois ci seul le paramètre y admet une valeur par défaut :

def rectangle(x,y=1):
    print("périmètre :",2*(x+y),"aire :",x*y)

rectangle(2)
rectangle(7,5)
périmètre : 6 aire : 2
périmètre : 24 aire : 35

Une contrainte à bien prendre en considération, on est obligé de mettre "à droite" les paramètres admettant des valeurs par défaut. Si l'on transforme notre procédure comme cela :

def rectangle(x=1,y):
    print("périmètre :",2*(x+y),"aire :",x*y)

L'interpréteur nous répondra :


Paramètres immuables

Les paramètres de type int, bool, float, complex et str sont immuables. Cela signifie que si l’on passe une variable de l’un de ces types comme paramètre à un sous-programme, celui-ci ne pourra pas en modifier sa valeur.

[Note]

On verra dans le chapitre suivant consacré aux types de données complexes, que d'autres types de paramètres sont eux muables.

[Note]

Cette non modification de ces types de paramètres ne provient pas d'un problème de passage par valeur, car rappelons-le en Python tous les passages de paramètres sont effectués par référence.

Example 1.8. Une tentative (infructueuse) pour doubler un nombre

Cette procédure ne modifie pas un entier passé en paramètre car le type int est immuable :

def doubler(x):
    x *= 2

a = 3
print("valeur de a avant :",a)
doubler(a)
print("valeur de a après :",a)
valeur de a avant : 3
valeur de a après : 3

Si l'on souhaite modifier une variable de l'un de ces types, on ne comptera donc pas sur un passage de paramètre. On utilisera plutôt une valeur retournée par une fonction.

Example 1.9. Une autre tentative (réussie celle-ci) pour doubler un nombre

On modifie notre variable en lui réaffectant la valeur retournée par une fonction :

def doubler(x):
    return 2*x

a = 3
print("valeur de a avant :",a)
a = doubler(a)
print("valeur de a après :",a)
valeur de a avant : 3
valeur de a après : 6

Variables locales et globales

Rappelons, voir ici, qu'une variable locale est définie à l’intérieur d’un sous-programme et n'est accessible qu’au sein de celui-ci. Leur but étant d'assurer le bon fonctionnement du sous-programme.

Example 1.10. Une fonction utilisant des variables locales

Une fonction calculant la somme des n premiers entiers, avec deux variables locales utilitaires, somme et i :

def sommeEntiers(n):
    somme = 0
    for i in range(n+1):
        somme += i
    return somme

print(sommeEntiers(6))
21

L'utilisation de ces variables locales en dehors du sous-programme est interdite, comme nous le rappelle l'interpréteur Python si on tente de le faire :

print(somme)
Traceback (most recent call last):
  File "/Users/laurentgodefroy/Desktop/Codes1ADS.py", line 221, in <module>
    print(somme)
NameError: name 'somme' is not defined

[Note]

Rien à voir avec le sujet de cette sous-partie, mais pour calculer cette somme on a utilisé la technique présentée au module précédent : initialiser une variable à 0, et à chaque itération rajouter un nouveau terme.

Une variable globale est quand à elle définie en dehors de tout sous-programme. Une telle variable sera alors visible et utilisable par tous les sous-programmes du module courant.

[Note]

Par module on entend tout simplement fichier ".py". Voir un peu plus loin.

La rencontre d'un nom de variable dans un sous-programme déclenche une recherche LGI :

  1. Recherche d’une variable locale correspondant à ce nom.

  2. Recherche d’une variable globale correspondant à ce nom.

  3. Recherche d’un nom interne au langage.

On va maintenant expliciter cela dans une série d'exemples à unique vocation pédagogique. Que le lecteur n'y cherche donc pas un sens profond.

Example 1.11. Une première recherche LGI :

On a ici une variable globale i :

def exemple1():
    print(i)

i = 666
exemple1()
666

La commande print(i) du sous-programme déclenche la recherche de la variable i. Elle est introuvable comme variable locale, on la recherche donc comme variable globale et on la trouve alors.


Example 1.12. Une seconde recherche LGI :

On a ici une variable globale i et une variable locale du même nom :

def exemple2():
    i = 111
    print(i)

i = 666
exemple2()
print(i)
111
666

La commande print(i) du sous-programme déclenche la recherche de la variable i et la trouve comme variable locale. En dehors du sous-programme c'est bien sûr uniquement la variable globale i qui est accessible.


Example 1.13. Une troisième recherche LGI :

On a ici aussi une variable globale i et une variable locale du même nom :

def exemple3():
    print(i)
    i = 111

i = 666
exemple3()
Traceback (most recent call last):
  File "/Users/laurentgodefroy/Desktop/Codes1ADS.py", line 243, in <module>
    exemple3()
  File "/Users/laurentgodefroy/Desktop/Codes1ADS.py", line 238, in exemple3
    print(i)
UnboundLocalError: local variable 'i' referenced before assignment

La commande print(i) du sous-programme fait référence à la variable globale i alors que la commande i = 111 crée une variable locale du même nom. Cette ambiguïté est bien sûr interdite, d’où le message d’erreur.


Les variables globales ne sont a priori pas modifiables par des sous-programmes.

Pour que cela soit possible, il faut le signaler explicitement dans le sous-programme à l’aide de l’instruction global :

global var

Sans cette instruction, les affectations au sein d’un sous-programme sont considérées comme des créations de variables locales. Ce que l'on a constaté sur les exemples précédents.

Example 1.14. Une modification d'une variable globale

La variable globale i va pouvoir être modifiée grâce à l'utilisation du mot clé global :

def exemple4():
    global i
    print(i)
    i = 111
    print(i)

i = 666
exemple4()
print(i)
666
111
111

L'instruction global indique que l’on va nécessairement utiliser la variable globale i. L’instruction i = 111 ne va donc pas créer une variable locale, mais modifier la valeur de la variable globale. L’ambiguïté relevée dans l’exemple précédent n’existe donc plus.


Finissons cette sous-partie en réitérant cette mise en garde :

[Warning]

L’usage des variables globales est à limiter au maximum. Si un sous-programme à besoin de valeurs pour fonctionner, il est plus optimal (au sens de la structuration et de la réutilisabilité du code) de lui transmettre ces valeurs en utilisant des paramètres.

Modules de fonctions

On va s'intéresser maintenant à la structure générale d'un projet.

Commençons par définir la notion de module de fonctions. Il s'agit tout simplement d'un fichier d’extension “.py” contenant des sous-programmes regroupés de façon cohérente.

Un projet sera alors constitué de plusieurs modules de fonctions et d'un module particulier, dit “main”, où l’on définit les variables du projet et où l’on utilise les fonctions des différents modules.

[Note]

Là encore l’auteur choisit délibérément de simplifier la présentation.

Un module peut également contenir la définition de constantes.

Pour pouvoir utiliser les fonctions d'un module dans un autre module, on doit au préalable l'importer, ce qui signifie tout simplement mettre ses fonctions à disposition.

On dispose de trois méthodes pour importer un module, chacune requiérant l'usage du mot clé import :

  1. On importe tout le module :

    from NomDuModule import *

    Pour utiliser une fonction, on l'appelera alors directement par son nom.

  2. On importe une seule fonction :

    from NomDuModule import uneFonction

    Pour l'utiliser la fonction en question on l'appelera directement par son nom.

  3. On importe tout le module :

    import NomDuModule

    Cette fois-ci pour utiliser une fonction on utilisera une syntaxe de la forme :

    NomDuModule.nomDeLaFonction(...)

Example 1.15. Utilisation des trois méthodes d'import :

Imaginons que l'on dispose d'un module "exemple.py" contenant ces deux fonctions :

def doubler(x):
    return 2*x

def tripler(x):
    return 3*x

Première possibilité, on importe tout le module et on on appelle une fonction par son nom :

from exemple import *
print(tripler(12))
36

Seconde possibilité, on importe juste une fonction :

from exemple import doubler
print(doubler(12))
24

Troisième possibilité, on importe tout le module, et on appelle une fonction en faisant précéder son nom par le nom du module :

import exemple
print(exemple.tripler(12))
36

[Note]

Les deux premières méthodes sont à utiliser avec précaution, car si un même nom de fonction est utilisé dans plusieurs modules différents que l'on importe, cela créerait une ambiguïté.

En cas de doutes on privilégiera donc la troisième.

Exercices

On propose au lecteur pour finir deux petits exercices d'applications immédiates de ces notions de sous-programme afin qu'il puisse tester sa bonne compréhension des bases.

Exercice corrigé : conversion de durées

1.1.

Ecrire une procédure prenant en paramètre une durée en secondes et qui affiche la conversion de cette durée en heures, minutes, secondes.

On utilise des quotients et restes de divisions euclidiennes pour arriver au résultat.

def conversion1(n):
    h = n // 3600
    m = (n - 3600*h) // 60
    s = n % 60
    print(h,"heures,",m,"minutes,",s,"secondes")

Exercice corrigé : une autre conversion de durées

1.1.

Ecrire une fonction prenant en paramètre une durée en heures, minutes, secondes et qui retourne le nombre total de secondes correspondant.

Le calcul est ici immédiat.

def conversion2(h,m,s):
    return 3600*h + 60*m + s
A propos de SUPINFO | Contacts & adresses | Enseigner à SUPINFO | Presse | Conditions d'utilisation & Copyright | Respect de la vie privée | Investir
Logo de la société Cisco, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société IBM, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Sun-Oracle, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Apple, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Sybase, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Novell, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Intel, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Accenture, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société SAP, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo de la société Prometric, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management Logo du IT Academy Program par Microsoft, partenaire pédagogique de SUPINFO, la Grande École de l'informatique, du numérique et du management

SUPINFO International University
Ecole d'Informatique - IT School
École Supérieure d'Informatique de Paris, leader en France
La Grande Ecole de l'informatique, du numérique et du management
Fondée en 1965, reconnue par l'État. Titre Bac+5 certifié au niveau I.
SUPINFO International University is globally operated by EDUCINVEST Belgium - Avenue Louise, 534 - 1050 Brussels