Objectifs :
À la fin de cette séquence vous serez capable de
- Prototyper une fonction
- Décrire les préconditions sur les arguments.
- Décrire des postconditions sur les résultats.
1. Fonction
La spécification d'une fonction décrit à l'usager ce qu'il doit savoir sur la fonction pour l'utiliser correctement.
1.1. Annotation d'une fonction Python
Outre les docstrings, les annotations ajoutent aux fonctions Python de la clarté au code.
Cependant, elles ne sont utiles qu'aux programmeurs !
def volume(longueur, largeur, hauteur):
"""
Données : longueur, largeur et hauteur sont des nombres positifs
Résultat : La fonction retourne le volume d'un parallélépipède
"""
return longueur * largeur * hauteur
#Avec annotation
def volume(longueur: float, largeur: float, hauteur: float) -> float:
"""
Données : longueur, largeur et hauteur sont des nombres positifs
Résultat : La fonction retourne le volume d'un parallélépipède
"""
return longueur * largeur * hauteur
def somme(lst:list[int|float]) -> [int|float]:
total: [int|float] = 0
for nombre in lst:
somme += nombre
return total
Annotation des fonctions Python avec valeur par défaut
def volume(longueur: float, largeur: float, hauteur: float = 2.1) ->float:
"""
Données : longueur, largeur et hauteur sont des nombres positifs
Résultat : La fonction retourne le volume d'un parallélépipède
"""
return longueur * largeur * hauteur
#La fonction addition
def addition(a:[int|float], b:[int|float])->[int|float]:
"""
Paramètres: a et b sont des entiers
Résultat: La fonction renvoie la somme de a et b
"""
c:[int|float] = a + b
return c
addition(12, 15)
27
addition("12", "15")
'1215'
Ce qui pose problème !
1.3. Assertion : préconditions et postconditions
Les assertons sont utilisées dans les fonctions pour vérifier, d'une part, la validité des paramètres et, d'autre part, la validité de la sortie.
C'est un outil pour les programmeurs.
De manière générale, les assertions se divisent en trois catégories :
- Une condition préalable ou précondition ;
- Une postcondition ;
- Un invariant est quelque chose qui est toujours vraie à un moment donné à l'intérieur d'un morceau de code.
L'instruction assert de Python permet d'ajouter une assertion à un code ; elle teste une condition.
Si la condition est vraie, cela ne fait rien et le programme continue simplement à s'exécuter. Mais, si la condition d'assertion est fausse, Python lève une exception (arrête immédiatement le programme) et affiche un message d'erreur s'il est fourni.
Les conditions préalables ou préconditions d'une fonction sont toutes les conditions qui doivent être satisfaites aussi bien sur les paramètres que l'état global du programme avant de pouvoir appeler la fonction.
Elles permettent de vérifier, par exemple, que les arguments de la fonction sont correctement formatés. En conséquence, elles interceptent les entrées non valides.
#La fonction addition
def addition(a, b):
"""
Paramètres: a et b sont des entiers
Résultat: La fonction renvoie la somme de a et b
"""
#Précondition
assert type(a) == int
assert type(b) == int
c = a + b
return c
addition('12', '15')
--------------------------------------------------------------------------- AssertionError Traceback (most recent call last) <ipython-input-7-9bc3ae00b891> in <module> ----> 1 ajouter('12', '15') <ipython-input-6-968adb4c41f8> in ajouter(a, b) 4 Elle retourne la somme des nombres a et b 5 """ ----> 6 assert type(a) == int 7 assert type(b) == int 8 c = a + b AssertionError:
#La fonction addition
def addition(a, b):
"""
Paramètres: a et b sont des entiers
Résultat: La fonction renvoie la somme de a et b
"""
#Précondition
assert type(a) == int, "a doit être entier"
assert type(b) == int, "b doit être entier"
c = a + b
return c
Les postconditions d'une fonction sont toutes les conditions qui seront satisfaites sur la valeur de retour après l'appel de la fonction, les préconditions étant satisfaites. Elles servent, par ailleurs, de tests de base pour le code.
#La fonction addition
def addition(a, b):
"""
Elle retourne la somme des nombres a et b
"""
#Précondition
assert type(a) == int, "a doit être entier"
assert type(b) == int, "b doit être entier"
c = a + b
return c
#Postcondition
assert addition(5, 7) == 12
#Cela ne produira rien car les valeurs sont correctes.
Toutefois, les assertions n'ont pas pour but unique de détecter des erreurs : elles sont aussi une aide à la compréhension d'un programme.
Chaque assertion donne à la personne qui lit le programme une chance de vérifier (consciemment ou non) que sa compréhension correspond à ce que fait le code.
2. Retour sur une fonction renvoyant un tuple
Lorsqu'une fonction renvoie plusieurs valeurs, ses valeurs sont contenues dans un tuple.
Un exemple :
def divise(dividende:int, diviseur:int) -> tuple[int|int]:
"""
Paramètres : Dividende et diviseur sont des entiers naturels.
Sortie : Elle renvoie le quotient et le reste de la division entière de dividende par diviseur.
"""
assert type(dividende) == int and dividende >= 0
assert type(diviseur) == int and diviseur > 0
quotient = dividende // diviseur
reste = dividende % diviseur
resultat = (quotient, reste)
return resultat
#test
assert divise(13, 4) == (3, 1)
divise(13, 4)
(3, 1)
D'entrée, on ne sait pas à quoi correspond les nombres renvoyés !
Il faudra peut-être lire la documentation de la fonction car les nombres renvoyés eux-mêmes ne fournissent pas beaucoup d'informations sur leur signification individuelle.
Voici une fonction qui utilise un namedtuple pour clarifier la signification de chaque nombre renvoyé.
from collections import namedtuple
def divise(dividende:int, diviseur:int) -> tuple[int|int]:
"""
Paramètres : Dividende et diviseur sont des entiers naturels.
Sortie : Elle renvoie le quotient et le reste de la division entière de dividende par diviseur.
"""
assert type(dividende) == int and dividende >= 0
assert type(diviseur) == int and diviseur > 0
quotient = dividende // diviseur
reste = dividende % diviseur
res = (quotient, reste)
div = namedtuple("resultat", "quotient reste")
return div(quotient, reste) # ou div(*res)
#test
assert divise(13, 4) == (3, 1)
divise(13, 4)
resultat(quotient=3, reste=1)
3. Signature d'une fonction en Python
La signature d'une fonction est l'ensemble des paramètres (avec leurs noms, positions, valeurs par défaut et annotations de type), ainsi que l'annotation de retour d'une fonction. C'est-à-dire toutes les informations décrites à droite du nom de la fonction.
import inspect
sign = inspect.signature(divise)
print(sign)
(dividende: int, diviseur: int) -> tuple[int]
4. Une fonction particulière : la fonction lambda (dite fonction anonyme)
Une fonction lambda (ou fonction anonyme) est une fonction définie sans nom. Elle est définie directement à l'endroit où elle est utilisée, ce qui la rend très pratique pour de courtes expressions. Les fonctions lambda sont souvent utilisées pour des opérations simples et concises.
Syntaxe :
lambda arguments: expression
- arguments : une liste d'arguments, similaire à ceux définis dans une fonction traditionnelle.
- expression : une seule expression qui est évaluée et renvoyée comme résultat.
lambda x : x**2
<function __main__.<lambda>(x)>
(lambda x : x**2)(3)
9
Elle est équivalente à la fonction suivante :
def carre(x):
return x**2
carre(3)
9
Une fonction lambda peut être nommée mais cela est déconseillé.
carre = lambda x: x**2
carre(2)
4
Remarque : Les fonctions lambda sont judicieusement utilisées dans d'autres fonctions (fonctions d'ordre supérieur).
Un exemple : Ordonner une liste de tuples
La fonction sorted permet d'ordonner une liste.
help(sorted)
Help on built-in function sorted in module builtins: sorted(iterable, /, *, key=None, reverse=False) Return a new list containing all items from the iterable in ascending order. A custom key function can be supplied to customize the sort order, and the reverse flag can be set to request the result in descending order.
l = [5, 8, -1, 0, 3, 1]
#Ordonner ma_liste dans l'ordre croissant
l_ordonnee = sorted(l)
print(l_ordonnee)
[-1, 0, 1, 3, 5, 8]
lst = [(5, True, 'e'), (10, False, 'b'), (-1, True, 'z'), (8, True, 'a')]
#Il faut préciser, dans ce cas, sur quel élément du tuple porte le classement
Ordonner la liste lst dans l'ordre croissant suivant le troisième élément des tuples (qui ici est une lettre).
1e Méthode :
On définit d'abord une fonction qui permettra ensuite le classement.
#Première méthode
def lettre(elt) :
return elt[2]
lst_triee = sorted(lst, key=lettre)
print(lst_triee)
[(8, True, 'a'), (10, False, 'b'), (5, True, 'e'), (-1, True, 'z')]
#Ordre décroissant
lst_triee2 = sorted(lst, key=lettre, reverse=True)
print(lst_triee2)
[(-1, True, 'z'), (5, True, 'e'), (10, False, 'b'), (8, True, 'a')]
2e Méthode : Avec une fonction lambda, c'est plus concis.
lst_triee3 = sorted(lst, key=lambda elt:elt[2])
print(lst_triee3)
[(8, True, 'a'), (10, False, 'b'), (5, True, 'e'), (-1, True, 'z')]
#Ordre décroissant
lst_triee4 = sorted(lst, key=lambda elt:elt[2], reverse=True)
print(lst_triee4)
[(-1, True, 'z'), (5, True, 'e'), (10, False, 'b'), (8, True, 'a')]
Faites-vous plaisir : Primalité
Écrire une fonction est_premier_naif qui prend en entrée un entier et renvoie True si l'entier est premier, False sinon.
Un nombre premier est un entier naturel qui a exactement deux diviseurs distincts entiers et positifs : 1 et lui-même.