Function history then python
Functionnal Programming Paradigm in a nutshell
History
Writing Functions
La définition théorique en Python :
et en pratique une fois implémenté :
# les fonctions doivent être déclarées avant de pouvoir être appelées !
def somme(a,b):
return a + b
def multiply(a,b):
return a * b
# On stocke et on affiche la valeur retournée :
result_somme = somme(1,4)
print(result_somme)
# Ou on affiche directement les valeurs retournées
print (somme(1,4)) # (1)
print (multiply(2,7))
a = 5
b = 2
# On peux passer des variables directement
print( somme(2,a))
# ou en les modifiant/ faisant des calculs intermédiaires
print( multiply(a + 2,3 * 2 + b))
# les appels de fonctions sont empilables sans aucune limite
print (somme(multiply(2,3),somme(4,b + multiply(2,8)))) # (2)
- L'appel de fonction se fait en appelant le nom de la fonction suivi des arguments entre parenthèses.
- Les arguments peuvent être calculés avant execution de la fonction, on peut donc facilement empiler les appels de fonctions comme dans cet exemple.
Portées des variables : globale et locale
Par portée, il faut entendre la durée de vie des variables dans votre programme ou un bloc d'instruction dans votre programme.
Une variable est soit :
- globale : visible de partout dans le programme
- locale : seulement visible dans le bloc d'instruction dans laquelle elle a été déclarée.
Les variables globales sont toutes celles qui ne sont pas dans une fonction, donc dans le corps du module, et qui ont été déclarées en entête de programme.
Elles sont visibles de partout dans votre programme, mais nous verrons par la suite qu'elles sont uniquement modifiables si le programmeur utilise le mot clef global
Essayons de comprendre la différence concrétement via ces exemples de programme :
X = 99 # (1)
def foo1():
Y = 55 # (2)
print( Y)
foo1()
print (Y) # ne marchera pas # (3)
# On tente de redéfinir X dans cette fonction foo()
def foo2():
X = 88 # (4)
foo2()
print( X ) # X n'a pas bougé ...
# La aussi, ce code ne changera pas la valeur de la variable globale X = 99
def foo3(X):
X = X + 1 # (5)
foo3(X)
print (X)
# Accès à la variable globale en lecture
def foo4(Y):
# Portée locale
Z = Y + X # (6)
return Z
foo4(1)
# Accès à la variable globale en écriture
def foo5():
global X
X = X + 1 # (7)
foo5()
print( X)
- Déclarée avant et en dehors d'une fonction (dans le corps du module donc), la variable
Xest considérée comme une variable globale. - Y est déclaré dans le corps de la fonction, c'est une variable locale, c'est à dire temporaire.
- Si on tente d'y accéder après appel de la fonction, on se rend bien compte qu'elle a disparue. Le seul moyen de récupérer une (ou plusieurs) valeur est donc de faire un renvoi avec
return - A partir du moment où il y a affectation dans le corps d'une fonction, Python déclare cette nouvelle variable comme une variable locale, peu importe qu'elle existe par ailleurs.
Autrement dit, elle [red]masque la variable
Xdéclarée en globale. De la même façon que précédemment, la variableXdisparait/meurt à la fin de l'execution de la fonction, c'est une variable temporaire en quelque sorte. - A partir du moment où vous assignez une valeur à une variable dans une fonction, Python considère qu'il s'agit d'une variable locale. Ici vous aurez une erreur, car il va tenter d'incrémenter la variable locale X, hors celle-ci n'existe pas dans cette fonction.
- Z et Y sont des variables locales. Concernant X, si vous faites appel à une variable globale, et qu'elle n'a pas été redéfinie, python est capable de re-trouver par déduction la valeur de votre variable globale. Toutefois, celle ci ne sera accessible qu'en lecture et pas en écriture ( car comme vu précédemment toute nouvelle affectation entraine la création d'une nouvelle variable locale).
- Il existe un mot clef
globalpermettant de passer outre cette limitation vu en 6, et permettant d'accéder en écriture à votre variable globale. Cette utilisation est clairement déconseillée car pouvant entrainer de nombreuses incohérences dans votre programme..
Voyons pourquoi dès à présent dans cet exemple :
X = 99
def func1():
global X
X = 88
def func2():
global X
X = 42
func1()
func2()
# ne donnera pas le même resultat que pour
func2()
func1()
Voir l'execution détaillé en ligne
Compte tenu de ce programme, il est très clair ici qu'il risque de favoriser des conflits sur X, qui est devenu une ressource partagée !
L'ordre d'appels des fonctions aura donc une incidence sur la valeur finale de X, ce qui est clairement un problème (pour la recherche d'erreur par exemple) ...
Danger
Pour faire simple, les variables globales sont à éviter dans 99% des cas.
Passage d'arguments et retour de données
Quelques règles sur les arguments, et le passage d'arguments à des fonctions en python.
- Les arguments sont passés par assignation, autrement dit il n'y a pas de re-copie des valeurs dans une nouvelle variable locale lors du transfert comme dans certains langages (cf C, C++) .
- Peu importe donc le nom de vos arguments, ils peuvent recouper des noms de variable déjà existant ailleurs dans votre programme puisque nous savons que leurs portées sont locales.
- Changer un/plusieurs élément(s) dans un objet mutable a une incidence sur le programme appelant .
def modif(a,b):
a = 2 # (1)
b[0] = 5 # (2)
x = 1
alist = [1, 2]
modif(x,alist)
print (x) # valeur inchangé
print (alist) # valeur changé
aest une variable locale qui récupère la valeur de la variablexlors de l'appel de la fonction. L'assignation n'a pas d'effet sur la variablex, seul la variablealocale sera modifiée ici.- La liste passée ici en paramètre à une variable locale. Toutefois, une liste est un objet mutable, donc modifiable sur place ! Nous ne changeons pas
b, mais un élément de la liste représentée par b, ce qui aura à la fin de l'execution une répercution sur la variablelist
Voir l'execution détaillé en ligne
Un apercu des différentes techniques est donné dans la FAQ de Python, mais comme celle-ci l'indique la meilleure façon reste de renvoyer des données uniquement via le mot clef return. Cela permet d'éviter au maximum les surprises en assurant un côté volontairement déterministe à votre fonction, autrement dit : je sais ce qui rentre, je sais ce qui sort
Voici un exemple de code source bien écrit
- Comme la fonction manipule des variables locales, peu importe que les noms d'arguments se recoupent +
sacouxouvdans la définition de la méthodecalcul(..)ne change rien.
Voir l'execution détaillé en ligne
Danger
Pour la pédagogie, voici un exemple de code source qu'il vous faudra absolument éviter !
1. Seul nbOr est une variable locale 2. On accède à sac en variable globale 3. Et on le modifie ainsi, c'est mal ! :)Le mot clef return implique quand il est rencontré par le programme, l'arrêt du traitement de la fonction, et le retour du résultat.
Ce qui n'exclue pas la possibilité d'avoir plusieurs fonctions return dans un même programme, qui renvoie un résultat en fonction de condition différentes.
Si on veut avoir plusieurs points de retours possibles dans une fonction :
sac = int(input("nombre de pièces dans votre sac ?"))
def douane(sac):
taxe = 15
if sac > taxe:
print("par ici la monnaie")
return sac - taxe
else:
print("pas de taxe pour les pauvres")
return sac
print( douane(sac))
Si on veut retourner plusieurs valeurs depuis notre fonction, c'est tout à fait possible avec la syntaxe suivante pour votre return :
sac = 52
def calcul(sac, nbOr): # (1)
status = "pauvre"
sac = sac + nbOr
if sac > 100 :
status = "riche"
return sac, status
valeur, status = calcul(sac,80)
print (f"Vous êtes {status} avec {valeur} d'or")
Et pour passer des fonctions en paramètre à des fonctions ? C'est une fonctionnalité particulièrement puissante qui permet de rendre les programmes encore plus génériques.
sac = 3
def somme(a,b):
return a + b
def multiply(a,b):
return a * b
def calcul(sac,nbOr, fn): # (1)
sac = fn(sac , nbOr)
return sac
print( calcul(sac,20, somme))
print( calcul(sac,20, multiply))
Comme d'autres objets, les fonctions peuvent être stockées et appelées via des listes.