Skip to content

Function history then python

Functionnal Programming Paradigm in a nutshell

History

Writing Functions


Pseudo code utilisé pour les exercices

La définition théorique en Python :

def nomFonction (arguments1, arguments2, ... argumentsN):
   #...traitements...
   return value

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)
  1. L'appel de fonction se fait en appelant le nom de la fonction suivi des arguments entre parenthèses.
  2. 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)
  1. Déclarée avant et en dehors d'une fonction (dans le corps du module donc), la variable X est considérée comme une variable globale.
  2. Y est déclaré dans le corps de la fonction, c'est une variable locale, c'est à dire temporaire.
  3. 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
  4. 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 X déclarée en globale. De la même façon que précédemment, la variable X disparait/meurt à la fin de l'execution de la fonction, c'est une variable temporaire en quelque sorte.
  5. 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.
  6. 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).
  7. Il existe un mot clef global permettant 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é
  1. a est une variable locale qui récupère la valeur de la variable x lors de l'appel de la fonction. L'assignation n'a pas d'effet sur la variable x, seul la variable a locale sera modifiée ici.
  2. 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 variable list

Voir l'execution détaillé en ligne


Avant execution du corps de la fonction, l'assignation est la suivante

Après execution du corps de la fonction, voici le résultat

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

sac = 3
def calcul(sac,nbOr): # (1)
   sac = sac + nbOr
   return sac

print( calcul(sac,20))
  1. Comme la fonction manipule des variables locales, peu importe que les noms d'arguments se recoupent + sac ou x ou v dans la définition de la méthode calcul(..) 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 !

sac = 3
def calcul(nbOr): # (1)
    global sac # (2)
    sac = sac + nbOr # (3)
calcul(20)
print(sac)
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.

# 1 dimension avec des fonctions
def somme(a,b):
  return a + b

def multiply(a,b):
  return a * b

listd = [somme,multiply]
print( listd[0](1,2))
print( listd[1](2,9))

Voir l'execution détaillé en ligne