Python : les sous-programmes PDF
Document Details
Uploaded by Deleted User
Tags
Summary
Ce document est un cours sur l'utilisation des sous-programmes, notamment les fonctions et procédures en Python. Il couvre la motivation, l'exemple du fichier mon_index.py, les différentes explications, ainsi que la notion des variables en Python.
Full Transcript
Python : les sous-programmes Python : les sous-programmes 1 / 28 Motivation Objectif Le but des sous-programmes est de permettre au programmeur de définir ses propres instructions (procédures) ou ses propres expressions (fonctions) sous le forme de sous-programmes. Procédure Une procédu...
Python : les sous-programmes Python : les sous-programmes 1 / 28 Motivation Objectif Le but des sous-programmes est de permettre au programmeur de définir ses propres instructions (procédures) ou ses propres expressions (fonctions) sous le forme de sous-programmes. Procédure Une procédure est un sous-programme qui n’a pas de résultat. Exemple : print() Fonction Une fonction est un sous-programme qui retourne un résultat. Exemple : abs(), randint(), etc. Remarque En Python tout est fonction : Une procédure est une fonction qui renvoie None. Python : les sous-programmes 2 / 28 Exemple (fichier mon_index.py) """Exemple de sous-programme (fonction). """ def index(sequence, element): """Chercher l'indice de la première occurrence de 'elemnt' dans 'sequence' :param sequence: la séquence :param element: l'élément cherché :returns: l'indice de la première occurrence de 'element' :raises ValueError: l'élément n'est pas dans la séquence """ for indice, elt in enumerate(sequence): if elt == element: # On l'a trouvé ! return indice else: # jamais trouvé raise ValueError('élement non trouvé') if __name__ == "__main__": # Le fichier est exécuté comme programme principal assert index([1, 4, 2, 6], 1) == 0 assert index([1, 4, 2, 6], 4) == 1 assert index([1, 4, 2, 6], 2) == 2 assert index([1, 4, 2, 6], 6) == 3 assert index('Bonjour', 'j') == 3 try: ok = False index([1, 4, 2, 6], 7) # doit lever ValueError except ValueError: ok = True assert ok, "l'exception ValueError aurait dû se produire !" Python : les sous-programmes 3 / 28 Explications 1. Généralement, on définit un sous-programme dans un fichier (extension.py). 2. En Python, tout sous-programme est fonction (renvoie None si pas de résultat) 3. La définition d’une fonction commence par le mot-clé def. 4. La signature (première ligne) inclut le nom de la fonction suivie de ses paramètres entre parenthèses. 5. Le bloc qui suit (noter les deux-points et l’indentation !) inclut : un commentaire de documentation les instructions exécutées quand la fonction est appelée 6. La documentation est normalisée : voir PEP 257 et différents styles une phrase pour décrire l’objectif, suivie d’une ligne blanche une description plus détaillée (conditions d’utilisation, effets) une description des paramètres, du résultat et des éventuelles exceptions 7. Les instructions sont les instructions déjà vues : Principe : la spécification décrit l’objectif (R0), les instructions le comment (voir raffinages) L’instruction return arrête l’exécution de la fonction et est suivie de la valeur retournée par la fonction 8. On peut bien sûr définir plusieurs sous-programmes dans un même fichier 9. La fin du fichier est exécutée lorsque le fichier est chargé __name__ vaut '__main__' si le fichier est exécuté et non importé (import) ici, on l’utilise pour tester la fonction. On verra d’autres manières de le faire. Attention, la dernière instruction lève une exception. 10. Les fonctions dont le nom commence par un souligné (_) sont réputées locales au module (fichier). un module en Python est un fichier.py qui contient du code Python, et il est utilisé pour organiser et réutiliser le code de manière efficace. Python : les sous-programmes 4 / 28 Différents types de paramètres Considérons les appels suivants : len("bonjour") # 1 paramètre positionnel print(421) # 1 paramètre positionnel print('n =', n) # mais il peut y en avoir un nombre quelconque print(a, b, sep=' ; ', end='.\n') # 2 paramètes positionnels # et deux paramètres nommés (end et sep) Question : Plusieurs sous-programmes nommés print ? Non, pas de surchage en Python. Il n’y qu’un seul print ! Tous les paramètres positionnels sont des paramètres Vocabulaire : effectifs, mais tous les effectifs ne sont pas positionnels. paramètre formel (parameter) : lors de la définition du sous-programme exemple : len(obj) ==> obj est un paramètre formel paramètre effectif ou réel (argument) : lors de l’appel du sous-programme exemple : len(“bonjour”) ==> obj (formel) référence “bonjour” (effectif) paramètre positionnel : l’association entre effectif et formel se fait grâce à la position paramètre nommé : l’association entre effectif et formel se fait par le nom du paramètre formel nombre variable de paramètres effectifs : non connu lors de la spécification du SP exemple : print, max, min, all, any, etc. Remarque : un paramètre peut être à la fois positionnel et nommé. Python : les sous-programmes 10 / 28 Paramètres positionnels et paramètres nommés Paramètres positionnels Un paramètre positionnel est identifié par sa position. Si vous commencez à nommer des paramètres, nommez-les tous après le Le ième paramètre effectif correspond au ieme paramètre formel. premier nommé. def f(a, b): f(a=1, b=2, c=3) # Correct Positionnels Toujours avant les nommés. print(a, b) Dès qu’un nommé est utilisé, tous les f(1, 2) # a est lié à 1 et b à 2 (affiche : 1 2) arguments suivants doivent être nommés. Appel en nommant les paramètres Lors de l’appel, on peut nommer les paramètres. a=1 est passé en nommé, mais 2 est passé en positionnel après f(a='un', b=1) # Affiche : un 1 un paramètre nommé. f(b='un', a=1) # Affiche : 1 un Python ne permet pas de passer des arguments positionnels f(1, b='ok') # Affiche : 1 ok après des arguments nommés. f(b=2) # TypeError: f() missing 1 required positional argument: 'a' f(a=1, 2) # SyntaxError: positional argument follows keyword argument f(1, 2, a=1) # TypeError: f() got multiple values for argument 'a' Règles Tous les paramètres formels doivent recevoir une et une seule valeur Quand on commence à nommer un paramètre, on ne peut plus utiliser les positionnels Python : les sous-programmes 11 / 28 Valeur par défaut d’un paramètre Règle : valeur par défaut On peut donner une valeur par défaut à un paramètre formel positionnel. Il faut donner une valeur par défaut à tous les paramètres positionnels suivants. Lors de l’appel, si on omet un paramètre effectif, sa valeur par défaut sera utilisée. Exemple def f(a, b=2, c=3): print(a, b, c) f(1) # 1 2 3 f(1, 0, 'x') # 1 0 x f(1, 0) # 1 0 3 f(1, c='x') # 1 2 x Danger : les valeurs par défaut sont évaluées lors de la définition de la fonction def g(x, l = []): l.append(x) return l g(1) # g(2) # [1, 2] Comment faire pour avoir une nouvelle liste à chaque fois ? Python : les sous-programmes 12 / 28 Nombre variable de paramètres Principe *args et **kargs : récupérer respectivement les arguments positionnels et nommés en surplus. Remarque : on peut changer les noms ‘args’ et ‘kargs’ ? Exemple def f(a, b=2, c=3, *p, **k): print('a={}, b={}, c={}, p={}, k={}'.format(a, b, c, p, k)) f(1) # a=1, b=2, c=3, p=(), k={} f(1, 4) # a=1, b=4, c=3, p=(), k={} f(1, c=5) # a=1, b=2, c=5, p=(), k={} f(9, 8, 7, 6, 5, d=4, e=3) # a=9, b=8, c=7, p=(6, 5), k={'d': 4, 'e': 3} f(z=1, y=2, a=5) # a=5, b=2, c=3, p=(), k={'y': 2, 'z': 1} a=5 est passé en argument nommé, donc il remplace la valeur par défaut ou l'argument positionnel. b=2 utilise la valeur par défaut, car il n'a pas été spécifié. c=3 utilise la valeur par défaut, car il n'a pas été spécifié. p=() est vide car aucun argument positionnel supplémentaire n'a été passé. k={'y': 2, 'z': 1} contient les arguments nommés restants (y=2, z=1). Python : les sous-programmes 13 / 28 Paramètres seulement nommés (keywork-only argument) Principe C’est un paramètre formel qui ne peut pas être initialisé avec un paramètre formel positionnel mais seulement un paramètre effectif nommé. Exemple : ‘end’ et ‘sep’ de print Syntaxe def f(a, *p, b): print("a = {}, p = {}, b = {}".format(a, p, b)) f(1, 2, 3) # TypeError: f() missing 1 required keyword-only argument: 'b' f(1, 2, 3, b=4) # a = 1, p = (2, 3), b = 4 Python : les sous-programmes 14 / 28 Exercices Problème de TypeError avec range(stop=5) : Comme mentionné précédemment, range ne prend pas de paramètres nommés, donc appeler range(stop=5) provoque une TypeError. La signature correcte serait de passer stop en paramètre positionnel. 1. On considère la fonction index qui calcule l’indice de la première occurrence d’un élément dans une séquence à partir de l’indice début inclu jusqu’à indice fin exclu. Si l’indice fin est omis, on cherche jusqu’au dernier élément de la séquence. Si l’indice de début est omis, la recherche commence au premier élément (indice 0). Donner la signature de cette fonction. 2. Donner la signature de la fonction range. Dans sa forme générale, elle prend en paramètre l’entier de début, l’entier de fin et un pas. Si le pas est omis, il vaut 1. Si on ne donne qu’un seul paramètre effectif, il correspond à l’entier de fin et l’entier de début vaut 0. 3. L’appel range(stop=5) provoque TypeError: range() does not take keyword arguments. Est-ce que ceci remet en cause la signature proposée ? 4. Donner la signature d’une fonction max qui calcule le plus grand de plusieurs éléments. 5. Donner la signature de print. la fonction printf combine la méthode format pour formater une chaîne de caractères avec des arguments positionnels 6. Que fait la fonction suivante ? et utilise la fonction print pour afficher la chaîne formatée avec des arguments nommés supplémentaires. def printf(format, *args, **argv): printf("Bonjour, {}!", "Alice", end=" :)") print(format.format(*args), **argv) # Sortie : Bonjour, Alice! :) print(*objects, sep=' ', end='\n', file=None, flush=False) *objects : Un nombre variable d'objets à imprimer. Chaque objet est converti en chaîne de caractères. file : Un objet de type fichier où la sortie doit être envoyée. Par défaut, c'est sys.stdout. flush : Un booléen qui, s'il est True, force le vidage du flux de sortie. Par défaut, c'est False. Python : les sous-programmes 15 / 28 Mode de passage des paramètres Question Que peut faire un sous-programme sur les paramètres et que verra le sous-programme appelant ? Exemple def f1(s): Cette fonction prend une liste s en argument et ajoute -1 à la fin de cette liste. s.append(-1) def f2(s): Cette fonction prend un argument s et le réassigne à la chaîne de caractères 'résultat ?'. s = 'résultat ?' Cependant, cette réaffectation n'a d'effet que dans la portée locale de la fonction f2. La fonction f1 utilise s.append(-1), ce qui modifie directement l'objet liste (un objet liste = [0, 1] ; f1(liste) mutable). liste # ??? [0, 1, -1]. tuple = (0, 1) ; f1(tuple) La fonction f1 essaie d'appeler append sur tuple, mais cela provoque une erreur, car un tuple est immuable (il ne peut pas être modifié après sa création). tuple # ??? Résultat : une exception est levée ici, et le programme s'arrête. f2(liste) Dans f2,la ligne s = 'résultat ?' assigne une nouvelle valeur ('résultat ?') à la variable liste # ??? locale s. Cela ne modifie pas l'objet d'origine, car s n'est qu'une copie de la référence. f2(tuple) , liste reste inchangée à [0, 1, -1] tuple # ??? (0, 1). Python : les sous-programmes 16 / 28 Nom local et nom global Principe Quand on initialise un nom dans une fonction, on définit un nom local qui n’existe que tant que l’exécution de la fonction n’est pas terminée. Exemple a = 10 # variable globale print('dans f(), a = ', a) affichera la valeur de la variable globale a, soit 10. def f(): a = 5 Lorsque Python voit a = 5 dans f(), il traite a comme une variable locale dans toute la fonction. print('dans f(), a = ', a) Ainsi, lorsque le print(a) est exécuté, a n’est pas encore initialisée localement, ce qui provoque une erreur. 10 print(a) ; f() ; print(a) dans f(), a = 5 Avec global a, la variable a dans f() fait référence à la Que donne cette exécution ? 10 variable globale, pas à une nouvelle variable locale. La ligne a = 5 modifie la valeur de la variable globale. Que se passe-t-il si on supprime a = 5 ? Résultat: Avant f(), print(a) affiche 10. Que se passe-t-il si on fait print(a) avant a = 5 ? Dans f(), print('dans f(), a = ', a) affiche dans f(), a = 5. Que se passe-t-il si on ajoute global a en début de f() ? Après f(), print(a) affiche 5. nonlocal Donner accès à un nom d’un contexte englobant (et non global). Python : les sous-programmes 17 / 28 La récursivité Définition Un sous-programme récursif est un sous-programme dont l’implantation contient un appel à lui même. Exemple : factorielle def fact(n): if n >> from xc import m1 ; m1.sp() xc, mais vous ne pouvez pas accéder directement à m1 en utilisant xc.m1. Cela >>> from xc.m1 import sp ; sp() provoque une erreur AttributeError si vous essayez de faire xc.m1 car m1 n'est pas >>> from xc.m1 import sp as rsp ; rsp() directement accessible via le paquetage à moins que vous ne l'importiez spécifiquement. Python : les sous-programmes 23 / 28 Les fonctions comme données Les fonctions sont de objets def f1(): print("C'est f1 !") type(f1) # f2 = f1 # Un autre nom sur la fonction attachée à f1 f2() # affiche "C'est f1 !" f2.__name__ # 'f1' Accéder au nom de la fonction En Python, chaque fonction possède un attribut __name__ qui contient le nom de la fonction sous forme de chaîne de caractères. def g(f): Passage de f1 comme argument à une autre fonction (g) print('début de g') f() # f doit être « appelable » (callable) print('fin de g') Lorsque vous passez f1 à g, f1 est exécutée à l'intérieur de g. g(f1) # "début de g" puis "C'est f1 !" puis "fin de g" Les lambdas : fonctions courtes, anonymes, avec une seule expression cube = lambda x : x ** 3 # à éviter Lambda : Fonction courte, sans nom, avec une seule expression.Les lambdas sont utiles pour des tâches simples, comme des opérations à une ligne, mais pour des def cube(x): # Mieux ! Plus clair !fonctions plus complexes, il est préférable d'utiliser def. return x ** 3 Python : les sous-programmes 24 / 28 Calcul d’un zéro d’une fonction continue def zero(f, a, b, *, precision=10e-5): # par dichotomie if f(a) * f(b) > 0: raise ValueError() if a > b: a, b = b, a La fonction zero utilise la méthode de dichotomie pour rechercher une racine d'une fonction continue sur un intervalle donné. while b - a > precision: La méthode divise progressivement l'intervalle en deux jusqu'à ce que milieu = (a + b) / 2 l'intervalle soit suffisamment petit pour donner une approximation de la racine avec la précision spécifiée. if f(a) * f(milieu) > 0: a = milieu else: b = milieu return (a + b) / 2 def equation(x): return x ** 2 - 2 * x - 15 assert abs(5 - zero(equation, 0, 15))