Fonctions ========= Une fonction est un objet qui permet d'effectuer des opérations en fonction de données qu'on lui transmet. L'intérêt premier d'une fonction est de permettre d'appliquer un même algorithme à différentes données. Une fonction permet en outre d'isoler un code de manière à le rendre indépendant de l'espace de noms courant. Les fonctions peuvent être définies dans un fichier à part, ce qui permet de constituer un module. .. _fonction_python: Définition d'une fonction ------------------------- La **définition d'une fonction** se fait avec le mot clé :py:obj:`def`, suivi du nom de la fonction, de la liste des paramètres entre parenthèses, suivie du symbole ``:`` (deux points). Le code de la fonction est décalé d'une indentation par rapport au mot :py:obj:`def`. Voici une fonction qui prend un nombre en argument et renvoie ce nombre multiplié par deux : .. code-block:: python :linenos: def double(x): y = x*2 return y La variable nommée ``x`` figurant dans l'entête de la fonction est un **paramètre**, permettant de transmettre un **argument** au code de la fonction. En principe, le paramètre désigne la variable (c.a.d. le nom) alors que l'argument désigne l'objet effectivement transmis lors de l'appel de la fonction. Voici comment utiliser cette fonction : >>> f(5) 10 Dans ce cas, l'argument transmis à la fonction est un entier de valeur 5, qui est créé au moment de l'appel. Comme on le voit sur cet exemple, le mot-clé :py:obj:`return` permet à la fonction de renvoyer un résultat. Le nom de la fonction et la liste de ses paramètres constituent la **signature** de la fonction. Les seules informations apportées par la signature sont le nom de la fonction et les noms de ses paramètres (et bien sûr leur nombre), mais aucune information n'est apportée ni sur les types des arguments, ni sur le type de l'objet renvoyé par :py:obj:`return`. Il faut noter qu'une fonction est un objet (au même titre qu'un entier ou une liste par exemple) : >>> type(double) Le nom de la fonction est un nom de variable. Il est donc possible de définir une autre variable faisant référence à la même fonction : .. code-block:: python :linenos: d = double >>> d(5) 10 Il s'en suit qu'une fonction peut être transmise en argument d'une autre fonction. Variables locales et paramètres ------------------------------- Le bloc de code qui se trouve dans la fonction utilise un espace de noms (pour les variables) indépendant de l'espace de noms du code qui appelle la fonction. La variable ``y`` qui est utilisée dans la fonction est une **variable locale**, c'est-à-dire une variable définie dans l'espace de noms local à la fonction. Cette variable disparaît lorsque la fonction se termine. On dit aussi que la **portée** de la variable se limite à la fonction. Si le code qui appelle la fonction possède une variable de même nom, il n'y a pas interférence entre les deux : >>> y = 0 >>> f(5) 10 >>> y 0 L'unique paramètre de la fonction a le nom ``x``. Ce nom devient automatiquement le nom d'une variable locale dans la fonction. Le mot clé :py:obj:`return` permet à la fonction de renvoyer un objet. Si la fonction se termine sans rencontrer :py:obj:`return`, l'objet :py:obj:`None` est renvoyé. Lors de l'appel de la fonction ci-dessus, un entier de valeur 5 est créé et une variable locale nommée ``x`` est créée, faisant référence à cet entier. Il est tout à fait possible de modifier cette variable locale : .. code-block:: python :linenos: def double(x): x = x*2 return x >>> x = 10 >>> double(x) 20 >>> x 10 On constate que l'appel de la fonction ne modifie par l'objet référencé par la variable globale ``x``. Au moment où elle est créée, la variable locale ``x`` fait bien référence au même objet que la variable globale ``x``. Cependant, la ligne 2 de la fonction a pour effet de créer un nouvel objet de type :py:obj:`int` (car ce type d'objet est non mutable), contenant le résultat de l'opération de multiplication. Il s'en suit que l'objet initial n'est pas modifié. Il en est tout autrement lorsque l'objet passé en argument est un objet mutable, par exemple une liste : .. code-block:: python :linenos: def changer_element(L,i,e): L[i] = e return(L) >>> X = [1,2] >>> changer_element(X,i,0) >>> X [0,2] Dans ce cas, la liste référencée par la variable globale ``X`` devient référencée par la variable locale ``X``, mais la ligne 2 modifie le contenu de la liste. La liste référencée par la variable globale ``X`` est donc bien modifiée. Cette différence de comportement est due au fait que les listes sont des objets mutables alors que les entiers ne le sont pas. L'effet précédent (la modification de la liste passée en argument) peut être recherché mais, si on souhaite absolument l'éviter, il est prudent de convertir la liste en n-uplet avant de la transmettre. Voici une fonction qui a deux paramètres, permettant de lui transmettre deux arguments : .. code-block:: python :linenos: def puissance(x,n): return x**n En Python, les variables ne sont pas typées. En particulier, les variables locales définies comme paramètres de la fonction ne sont pas typées. Cela signifie que les types des objets auxquels ces variables font référence sont a priori quelconque, ce qui permet une grande souplesse dans l'utilisation de la fonction : >>> puissance(2,2) # les arguments sont des entiers 4 >>> puissance(2.5,2) # le premier argument est un flottant, l'autre un entier 6.25 Il peut arriver que le type d'un objet transmis en argument ne soit pas un type prévu par le concepteur de la fonction, par exemple : >>> puissance("2",2) Traceback (most recent call last): File "", line 1, in puissance("2",2) File "", line 2, in puissance return x**n TypeError: unsupported operand type(s) for ** or pow(): 'str' and 'int' On voit sur cet exemple l'inconvénient des variables de paramètre non typées : le type des variables n'est pas vérifié par l'interpréteur. Dans le meilleur des cas, cela conduit à une erreur qui se déclenche lorsqu'on tente une opération non permise avec ce type, ce qui interrompt le cours d'exécution (comme ci-dessus) mais dans d'autres cas, le problème peut passer inaperçu et provoquer une erreur à un autre endroit du code, ou même un résultat faux mais sans erreur. Il est toujours possible d'ajouter une vérification du type de l'objet : .. code-block:: python :linenos: def puissance(x,n): if (type(x)!=float and type(x)!=int) or (type(n)!=float and type(n)!=int) : return None return x**n Certains paramètres peuvent avoir une valeur par défaut, mais ils doivent être placés après les autres paramètres : .. code-block:: python :linenos: def volume_gaz_parfait(P,T,n=1): R = 8.31 return n*R*T/P Dans ce cas, on peut faire les deux appels suivants : >>> volume_gaz_parfait(1e5,298,2) >>> volume_gaz_parfait(1e5,298) # n a la valeur par défaut (=1) Il est possible d'utiliser une variable globale à l'intérieur d'une fonction : .. code-block:: python :linenos: R = 8.31 def volume_gaz_parfait(P,T,n=1): return n*R*T/P Cette pratique est cependant déconseillée car elle rend la fonction inutilisable en dehors du contexte particulier où elle figure. Il est même possible de définir une variable globale au sein d'une fonction, en la faisant précéder du mot clé :py:obj:`global`, mais cette possibilité est à proscrire lors de la conception de la fonction. Objet renvoyé ------------- Comme déjà mentionné, le mot-clé :py:obj:`return` permet à la fonction de renvoyer un objet au code qui fait l'appel de la fonction. Les paramètres constituent ainsi les entrées de la fonction alors que l'objet renvoyé constitue la sortie. Plus précisément, lorsque l'appel de la fonction avec des arguments est rencontré dans une expression, le code de la fonction est exécuté puis l'objet renvoyé prend la place de l'appel dans l'expression. Il faut noter que l'instruction :py:obj:`return` interrompt l'exécution de la fonction. Dans le cas de retours conditionnels, il peut donc y avoir plusieurs :py:obj:`return` dans la fonction, comme dans l'exemple de la fonction :py:obj:`puissance` ci-dessus. Si la fin du bloc de code de la fonction se termine sans :py:obj:`return`, alors l'objet :py:obj:`None` est renvoyé. Il est bien sûr possible de renvoyer un n-uplet, ce que l'on fait lorsqu'on a besoin de renvoyer plusieurs objets, par exemple plusieurs nombres (rappelons que le n-uplet est lui-même un objet). Voici un exemple : .. code-block:: python :linenos: def barycentre(x1,x2,y1,y2): xG = (x1+x2)/2 yG = (y1+y2)/2 return xG,yG L'appel de la fonction donne un n-uplet : >>> barycentre(0,0,1,2) (0.0, 1.5) Il est possible de définir deux variables à partir de ce n-uplet : >>> x,y = barycentre(0,0,1,2)