1-fonctions
 

Sous-programme : fonction, procédure

version 2017, PhL.

 

Les fonctions de ... Monsieur Jourdain !

Fonctions mathématiques : trigonométriques, logarithmes, exponentielle, puissances ...

Est caractérisée par

  • son nom : cos(...)
  • ses arguments d'entrée : $\pi$/3
  • une valeur de retour (résultat, destination) : écran, une variable résultat c, le terme d'une expression : cos(pi/3)**2 + sin(pi/3)**2

Boites noires :

  • spécification = ce que ça fait : OK
  • implémentation = comment ça le fait : ??

Exemple

In [1]:
from math import cos, pi
c = cos(pi/3)
print(c)
 
0.5000000000000001
In [ ]:
 
 
  • Ligne 1 : fonction cos et valeur pi disponibles

  • Ligne 2 : appel de la fonction cos(...) l'argument effectif pi/3.

  • Ligne 2 : valeur-résultat affectée dans la variable c

  • Ligne 3 : affichage de la valeur retournée

Les **(...)** : c'est à ça qu'on reconnait une fonction !

Rmq. : précision numérique ?

In [2]:
from math import *

c = cos(pi/3)
s = sin(pi/3)
un = c*c + s*s

print("cos*cos + sin*sin = ", un)
 
cos*cos + sin*sin =  1.0
 

Vocabulaire :

  • python : fonction à la place de sous-programme
  • argument ou paramètre
 

Pourquoi des fonctions

  • Eviter de ré-écrire
  • Simplifier la lecture donc la compréhension
  • Ré-utiliser l'existant
  • Découper un processus compliquées en une suite d'étapes plus simples : modularité
  • Découpage appliqué récursivement : cad appliqué pour chaque étape jusqu'à que ce soit assez simple !
 

Exercice :

Algorithme synthétique qui affiche :

Carcassonne est la préfecture du département 11
------------------------------------------------
Perpignan est la préfecture du département 66
------------------------------------------------
Montpellier est la préfecture du département 34
------------------------------------------------
Foix est la préfecture du département 09
------------------------------------------------
In [3]:
# Un peu synthétique : une boucle
for v,d in ("Carca", "11"), ("Perpi","66"), ("Montpel", "34"), ("Foix", "09"):
    print(v, "est la préfecture du département", d)
    print("------------------------------------------------")
    
 
Carca est la préfecture du département 11
------------------------------------------------
Perpi est la préfecture du département 66
------------------------------------------------
Montpel est la préfecture du département 34
------------------------------------------------
Foix est la préfecture du département 09
------------------------------------------------
In [4]:
# définition
def afficher_pref_dpt(ville, num_dpt):
    print(ville, "est la préfecture du département", num_dpt)
    print("------------------------------------------------")

# 4 appels
afficher_pref_dpt("Carca", "11")
afficher_pref_dpt("Perpi", "66")
afficher_pref_dpt("Montpell", "34")
afficher_pref_dpt("Foix", "09")

print()

# 4 appels synthétiques
for (v,d) in ("Carca", "11"), ("Perpi","66"), ("Montpell", "34"), ("Foix", "09"):
    afficher_pref_dpt(v,d)
    
    
 
Carca est la préfecture du département 11
------------------------------------------------
Perpi est la préfecture du département 66
------------------------------------------------
Montpell est la préfecture du département 34
------------------------------------------------
Foix est la préfecture du département 09
------------------------------------------------

Carca est la préfecture du département 11
------------------------------------------------
Perpi est la préfecture du département 66
------------------------------------------------
Montpell est la préfecture du département 34
------------------------------------------------
Foix est la préfecture du département 09
------------------------------------------------
In [ ]:
 
 

Définition et appels de fonction

NE PAS CONFONDRE : **LA définition** vs. **LES appels**
 

Définition d'une fonction

Généralement :

  • le nom de la fonction
  • les arguments formels d'entrées : leurs nombres, leurs types, l'identificateur de chacun
  • les sorties : leurs nombres, leurs types, l'identificateur de chacun

Python :

def nom(liste d'arguments formels):
... # le corps de la fonction
In [5]:
a = 17
b = 3.2
afficher_pref_dpt(a, b)
 
17 est la préfecture du département 3.2
------------------------------------------------
 

Appel d'une fonction

  • le nom
  • les arguments effectifs : les valeurs des arguments d'entrée
  • les identifiants des variables de sortie
 
NE PAS CONFONDRE : Argument **formel** vs. argument **effectif**
 

Le retour de valeur

  • Python : mot-clé return

    • return valeur, variable, expression
    • return None
  • Termine l'exécution du corps de la fonction

    • à la fin en général
    • pas toujours : pas nécessairement unique
In [6]:
def doubler(u):
    """ double la valeur entière u 
        Entrées : u (int)
        Retourne int 
    """
    return 2*u

def tripler(u):
    """ triple la valeur entière u 
        Entrées : u (int)
        Retourne int 
    """
    v = 3*u
    return v

def un():
    return 1

# des appels
d = doubler(2)
t = tripler(2)

print(d, t)

res = un()
print('un =', res)
 
4 6
un = 1
In [12]:
doubler(5.5)
Out[12]:
11.0
 

On verra ça plus loin mais help()est une fonction utile qui décrit ce que fait la fonction

In [8]:
help(doubler)
 
Help on function doubler in module __main__:

doubler(u)
    double la valeur entière u 
    Entrées : u (int)
    Retourne int

In [13]:
r = doubler(2)
a = 5
deux_a = doubler(a)

print(r, a, deux_a)
 
4 5 10
In [14]:
import math
#help(math)
doubler(math.pi)
Out[14]:
6.283185307179586
In [15]:
# plus douteux hélas ...
doubler('a')
Out[15]:
'aa'
In [16]:
def afficher_pref_dpt(ville, num_dpt):
    print(ville, "est la préfecture du département", num_dpt)
    print("------------------------------------------------")
    return None

# appel
afficher_pref_dpt("Carca", "11")
d = afficher_pref_dpt("Carca", "11")
print(d)
 
Carca est la préfecture du département 11
------------------------------------------------
Carca est la préfecture du département 11
------------------------------------------------
None
 
Les arguments d'entrée peuvent être des valeurs, des variables, des fonctions, ... ou être absent
In [17]:
def tetu():
    return 1


res = tetu() # appel et affectation du résultat
print('res = ', res)   # affichage

# boucle d'appels à tetu() dans un print()  
for i in range(5):
    print('appel tetu:', tetu())
        
# appel sans affectation, ni print. 
# L'interpréteur python affiche sa valeur en Out[..] 
doubler(1)
 
res =  1
appel tetu: 1
appel tetu: 1
appel tetu: 1
appel tetu: 1
appel tetu: 1
Out[17]:
2
 
NE PAS CONFONDRE : `return` vs. `print`
CONSEIL : séparer les E/S des traitements
 
Exemple avec plusieurs return
  1. Une fonction qui calcule et renvoit la valeur absolue d'un entier
  2. Une fonction puissance qui calcule et renvoit $x^n$ pour $n>0$
    • $x^n = x \times x^{n-1}$
    • pas la peine de perdre trop de temps pour calculer $0^n = 0$
In [18]:
def val_abs(x):
    ''' calcule la valeur absolue du flottant x 
    entrée : x (float)
    retourne float
    '''
    if x < 0:
        return -x
    else:
        return x
    
a = -5.0
b = 3.3
z = val_abs(0.0)
ra = val_abs(a)
rb = val_abs(b)
print(z, ra, rb, val_abs(0.0))
 
0.0 5.0 3.3 0.0
 

Signature d'une fonction vs. corps d'une fonction

Vocabulaire : signature = en-tête = spécification

signature : décrit ce que fait la fonction

corps : décrit comment elle le fait

  • Signature :
    • nécessaire et suffisant pour utiliser la fonction (appel)
    • help(nom_f) affiche la signature de la fonction nom_f
  • En quoi ça consiste ? il faut compléter la syntaxe python en explicitant
    • rôle : une phrase
    • pour chaque argument formel : son type, [valeur par défaut]
    • le type du/des argument retourné

Exemple de signature :

def doubler(u):
    """ double la valeur entière u 
        Entrées : u (int)
        Retourne int 
    """

Exemple de corps : il contient aussi la signature

def doubler(u):
    """ double la valeur entière u 
        Entrées : u (int)
        Retourne int 
    """
    return 2*u

Application et help(...) :

In [19]:
help(doubler)
 
Help on function doubler in module __main__:

doubler(u)
    double la valeur entière u 
    Entrées : u (int)
    Retourne int

 
La signature "ne fait rien". Elle _décrit_ comment on peut l'utiliser et ce qu'elle fait
Le corps _est_ le traitement qui _sera_ réalisé par l'appel de la fonction
L'appel _lance_ l'exécution de la fonction pour les arguments _effectifs_
 

Exemple signature, appel, corps et plusieurs arguments

In [ ]:
def puissance(x, n):
    '''
    calcule x**n de façon itérative pour
    x : float
    n : entier positif
    retourne float
    '''
    # corps de la fonction puissance
    r = 1.0
    for i in range(1,n+1):
        r = r * x
    return r

# appels avec des valeurs
mille = puissance(10.0, 3)
print("mille =", mille)

vrai_ou_faux = (puissance(3.1,2) == 3.1 * 3.1)
print(vrai_ou_faux)


# appels avec une variable
n = int(input("n premières puissances de 2 pour n = "))
for i in range(n):
    print(puissance(2, i))

print("cas particulier")
print(puissance(2.0, 0))

# appels avec des variables
p = float(input("n premières puissances de p pour p = "))
n = int(input("et n = "))
for i in range(n):
    print(puissance(p,i))
 

Ouvrons la boîte !

Variable locale vs. variable globale, portée des variables

Variable locale à une fonction : introduite dans le corps de la fonction pour permettre le traitement

Variable globale : définie dans le cadre appelant

Portée : zone où une variable est utilisable

Ces notions existent dans tous les langages de programmation mais elles varient significativement de l'un à l'autre. Ce qui suit est très fortement **lié à python** et se limite aux besoins de l'algorithmique.
In [ ]:
# a est une variable globale
a = 15
print("a:", a)

# x est une variable locale à f
def f():
    x = 1
    print("dans f: ", x)
    return x

r = f()
print("r=", r)
print("x=", x) 
 
  • Portée d'une variable locale limitée au corps de la fonction
    • logique : déclaration, affectation, puis instructions, ..., return
  • La portée d'une variable globale à l'intérieur d'une fontion varie en python
    • utilisation de sa valeur = OK
    • modification de sa valeur = NON (en général)
    • déclaration avec global si besoin de modifier
In [ ]:
a = 17


def f1(): 
    print("a dans f1 (a pas locale):", a)
    return None

def f2(): 
    a = 1
    print("a dans f2 (sans global) :", a)
    return None

def f3():
    global a # ainsi f3 accède à la variable de la ligne 1
    a = 1
    print("a dans f3 (avec global) :", a)
    return None

#
print("a extérieur :", a)

f1()
print("a extérieur :", a)

f2()
print("a extérieur :", a)

f3()
print("a extérieur :", a)

#print(x) 
 
ATTENTION : piégeux (et incomplet : attendre fonctions et tableaux)
CONSEIL : limiter les variables globales aux ... constantes !
 

Passage des arguments par valeur

Le corps de la fonction ne connait que les paramètres formels.
L'appel de la fonction définit les paramètres effectifs.
Comment le paramètre effectif devient-il connu par la fonction appelée ?
Qu'est-ce qui est connu de l'appelé ?

  • la valeur du paramètre effectif ?
  • la variable du paramètre effectif ? Se sont les questions du passage de paramètres appelant-appelé

Comment est transmis un argument effectif ?

  • si c'est une valeur : ok
  • si c'est une variable : valeur ? variable ?

Dépend des langages de programmation

python : passage par valeur

Explication :

  • transmission de la valeur de l'argument effectif
  • argument formel similaire à variable locale de la fonction
  • à l'extérieur de la fonction : pas de modification de la variable passée en argument
In [ ]:
u = 2
x = 5

def f(x):
    ''' incrémente x 
    entrée : x int
    retourne int '''
    r = x + 1 
    return r

def g(x):
    x = x + 1
    return x

print(f(u))
print(g(u))
print(u)
print()

print(f(x))
print(g(x))
print(x)
 
ATTENTION : piégeux (et incomplet : attendre fonctions et tableaux)

Exemple : une fonction de permutation de 2 entiers ... ?

In [ ]:
a = 1
b = 11

print("avant : a,b =", a, b)
t = a
a = b
b = t
print("après : a,b =", a, b)

def permuter(x, y):
    ''' attention : 
    les print dans cette fonction sont uniquement à but pédagogique
    '''
    print("dans permuter, avant x,y :", x, y)
    t = x
    x = y
    y = t
    print("dans permuter, après x,y :", x, y)
    return x, y
    
# marche pas !
a = 1
b = 11
print("avant fonction : a,b =", a, b)
permuter(a, b)
print("après fonction : a,b =", a, b)
 

Compléments

  • Notion d'effet de bord
  • pro/cons du passage par valeur
  • fonction vs. procédure
  • Vision logique du passage de paramètres : modes in, out, in_out
 

Synthèse

  • Fonction pour factoriser le traitement, structurer le déroulement d'un algo
  • définition vs. appel
  • signature vs. corps
  • argument formel vs. effectif
  • return vs. print
  • portée d'une variable locale vs. portée d'une variable globale
  • passage d'argument par valeur (python, non mutable)
 

Exercices en démonstration

  1. Fonctions min
    1. Ecrire la fonction min(a,b) : signature, appels, puis corps
    2. Proposer plusieurs écritures du corps : avec un seul return, avec deux, ...
    3. S'en servir pour écrire la fonction min(x, y, z) : signature, appels, puis corps
    4. Proposer plusieurs écritures du corps
  1. Géométrie du plan. Ecrire une fonction (signature puis corps) et un programme qui l'appelle pour résoudre chacun des problèmes suivants.
    1. je calcule la pente d'une droite définie par deux points dans le plan
    2. je calcule l'ordonnée d'un point situé sur une droite définie par sa pente et son ordonnée à l'origine
    3. je calcule l'ordonnée à l'origine d'une droite définie par deux points
    4. je vérifie si un point appartient ou non à une droite définie par deux points
    5. je calcule l'intersection de deux droites définies par deux paires de points en veillant à répondre dans tous les cas
In [ ]:
def min(a, b):
    '''retourne min(a,b)
    entrées : a,b int
    retourne int 
    -> version un seul return
    '''
    if a < b:
        m = a
    else:
        m = b
    return m

def min2(a, b):
    '''retourne min(a,b)
    entrées : a,b int
    retourne int 
    -> version deux return
    '''
    if a < b:
        return a
    else:
        return b

    
def min3a(a,b,c):
    '''retourne min(a,b,c)
    entrées : a,b,c int
    retourne int 
    -> version 1 return, 2 appels à min(a,b)
    '''
    if min(a, b) < c:
        m = min(a, b)
    else:
        m = c
    return m

def min3b(a,b,c):
    '''retourne min(a,b,c)
    entrées : a,b,c int
    retourne int 
    -> version 1 return, 1 appel à min(a,b)
    '''
    m_ab = min(a, b)
    if m_ab < c:
        m = m_ab
    else:
        m = c
    return m

def min3(a,b,c):
    '''retourne min(a,b,c)
    entrées : a,b,c int
    retourne int 
    -> version fonctionnelle : 2 appels imbriqués 
    '''
    return min(min(a, b), c)


# des appels
x = 23
y = 44
z = 27
m_xyz = min3a(x, y, z)
print(m_xyz)
m_xyz = min3b(x, y, z)
print(m_xyz)
m_xyz = min3(x, y, z)
print(m_xyz)
In [ ]: