Pour définir une fonction dans un programme, c’est tout simple :
>>> def nom_fonction(liste de parametres) : ... instruction1 ... instruction2 ... etc.
Le décalage, comme toujours, est obligatoire, et la convention est d'utiliser des noms de fonction en lower_case : pas de majuscule, et des _ si nécessaire.
On peut utiliser le mot-clé return pour spécifier le retour de la fonction :
>>> def incremente(x) : ... y = x + 1 ... return y ... >>> incremente(5) 6
Si aucun retour n'est précisé, alors le retour sera None.
On peut appeler récursivement une fonction. Illustrons cela par une fonction factorielle...
>>> def facto(n) : ... if n == 1 : ... return 1 ... else : ... return n*facto(n-1) ... >>> facto(10)
Version pythonique :
>>> from itertools import accumulate >>> import operator >>> list(accumulate(range(1, 11), operator.mul))
La formule suivante donne la relation entre une température exprimée en degrés Celsius $c$ et la même température exprimée en degrés Fahrenheit $f$.
$ f = 32 + 1,8 \times c $
Donner le code d'un programme python qui
Pour une fonction, il existe des paramètres spéciaux capable d'intercepter un nombre indéfini de valeurs. La signature de la fonction devient polymorphique.
>>> def toto(x, *args):
... print(args)
...
>>> toto(5, 2, 'a')
(2, 'a')
>>> toto(5, 'a', r = 2)
File "<stdin>", line 1, in <module> TypeError: toto() got an unexpected keyword argument 'r' >>> >>> def toto(x, *args, **kw): ... print(args) ... print(kw) ... >>> >>> toto(5, 2, 'a') (2, 'a') {} >>> toto(5, 'a', r = 2) ('a',) {'r': 2}
Ces paramètres spéciaux sont à utiliser avec parcimonie.
Les generators, introduits dans la version 2.2, offrent une technique de programmation originale, qui permet à une fonction ou une méthode de renvoyer des résultats intermédiaires.
Le mot-clef return est remplacé par yield.
La première fois que ce mot est rencontré, l'exécution est stoppée et un objet de type generator est renvoyé, qui contient le contexte local de la fonction, et une méthode next
A chaque appel de cette méthode, la fonction est appelée jusqu'au prochain yield.
On définit une fonction avec trois yield (et aucun return). On va voir que
Définissons notre generator :
>>> def ca_compte(): ... yield 'Et de un' ... yield 'Et de deux' ... yield 'Et de trois' ... >>> je_compte = ca_compte()
Premier appel...
>>> next(je_compte) 'Et de un'
Et ça continue ainsi, de yield en yield...
>>> next(je_compte) 'Et de deux' >>> next(je_compte) 'Et de trois'
Enfin, une fois que tous les yield ont été appelés, une exception StopIteration est levée...
>>> next(jeCompte) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
La fonction next pour passer à la prochaine valeur, et la levée de l'exception StopIteration, rendent les generators compatibles avec les boucles :
>>> je_compte = ca_compte() >>> for element in je_compte: ... print(element) ... Et de un Et de deux Et de trois
Voici à quoi peuvent servir les generators, en l'occurence à programmer proprement la suite de Fibonacci :
>>> def fibonacci(): ... a, b = 0, 1 ... while True: ... yield b ... a, b = b, a+b ... >>> suite = fibonacci() >>> n = int(input('Combien de termes ? ')) >>> for k in range(n): ... print(next(suite), end=', ') ... 1, 1, 2, 3, 5, 8, 13, 21, 34, 55
On a mis un yield dans une boucle while infinie : on pourra donc appeler autant de fois que l'on veut next(suite). Cette manière de procéder peut se réutiliser dans plein de contextes différents.
On aurait pu procéder à une version récursive, en prenant bien soin de ne pas recalculer les termes précédemment calculés, via lru_cache:
from functools import lru_cache @lru_cache(maxsize=None) def fib_cache(n): if n<2: return n return fib_cache(n-1)+fib_cache(n-2)
Le generator suivant permet d'obtenir autant de nombres premiers que l'on veut :
>>> def nombre_premier(): ... nombre = 2 ... premiers = [2] ... yield nombre ... while True: ... nombre += 1 ... if len([k for k in premiers if nombre%k == 0]) == 0: ... yield nombre ... premiers.append(nombre) ... >>> premier = nombre_premier() >>> next(premier) 2 >>> next(premier) 3 >>> next(premier) 5 >>> next(premier) 7 >>> next(premier) 11
Les generators expressions (genexp) fournit une écriture concise pour les generators simples, en se basant sur les list comprehensions : les crochets de ces dernières sont remplacées par des parenthèses...
>>> gen = ( k for k in range(10) if k%3 == 0) >>> next(gen) 0 >>> next(gen) 3 >>> next(gen) 6 >>> next(gen) 9 >>> next(gen) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
Le module itertools offre des outils avancés pour travailler sur les generators.
Python étant un langage interprété, il ne connaît pas à l'avance le code qu'il va exécuter : à la différence d'autres langages, il n'y a pas d'optimisation des appels de fonctions fréquemment utilisées, par un appel à une adresse mémoire unique.
Pour contourner le problème, et gagner du temps à chaque nouvel appel d'une fonction donnée, on peut stocker cette dernière dans une variable locale, comme l'illustre le bout de code suivant :
>>> from time import time >>> def toto(N): ... x=float(N)/1000 ... x=1000*x ... >>> def sans(N): ... dd=time() ... for k in range(N): ... toto(k) ... return time()-dd ... >>> def avec(N): ... tt=toto ... dd=time() ... for k in range(N): ... tt(k) ... return time()-dd ...
Dans ce qui précède, la fonction sans appelle N fois la fonction toto, sans l'avoir stockée dans une variable au préalable, quand la fonction avec fait la même chose, en utilisant une variable locale pour toto. Regardons le nombre de fois où avec est plus rapide que sans...
>>> cpt = 0 >>> for k in range(1000): ... if sans(10000) > avec(10000): ... cpt+=1 ... >>> print(cpt/10) 93.2
On trouve donc un gain de temps dans 93,2% des cas.