Dans un récent shot data, on a abordé une différence fondamentale entre les boucles for et les list comprehensions en Python.

Voici le visuel de ce post pour un rappel rapide :

Visuel comparant boucle for et list comprehension en Python : accessibilité de la variable de boucle

En résumé :

  • Après l'exécution d'une boucle for, la variable de boucle reste accessible.

  • Après l'exécution d'une list comprehension, la variable de boucle N'EST PAS accessible.

Comme je l'ai mentionné dans ce shot data, c'est étroitement lié à la notion de scope en Python.

Voyons ça aujourd'hui.

Qu'est-ce que le scope d'une variable et pourquoi en a-t-on besoin ?

En termes simples, le scope d'une variable désigne les zones de ton programme dans lesquelles une variable donnée est accessible.

Ce concept n'est pas propre à Python. Il s'applique à la programmation en général.

L'objectif est de limiter l'accès aux variables à des zones précises du code, afin d'éviter qu'elles soient modifiées depuis n'importe quel endroit du programme.

Schéma illustrant le scope en Python : ScopeA ne peut pas modifier la variable b

Ainsi, quand tu accèdes à N'IMPORTE QUELLE variable, c'est le scope qui détermine entièrement comment les noms seront recherchés dans ton code.

En Python, la résolution de scope se fait sur quatre niveaux.

Schéma de la résolution de scope en Python sur quatre niveaux : local, englobant, global, built-in

Chaque fois que tu accèdes à un nom en Python (nom de variable, nom de fonction, nom de classe, etc.), l'interpréteur Python le recherche dans l'ordre suivant :

  1. Local

  2. Enclosing (englobant)

  3. Global

  4. Built-in

C'est ce qu'on appelle la règle LEGB.

Résolution de scope LEGB en Python

Voyons chacun de ces niveaux !

#1) Scope local

L'exemple le plus simple de scope local, ce sont des variables déclarées à l'intérieur d'une fonction, comme illustré ci-dessous :

Code Python : variable myVar déclarée à l'intérieur de la fonction funcA, exemple de scope local

La variable myVar a un scope local dans funcA, ce qui signifie qu'elle n'est accessible qu'à l'intérieur de la fonction.

Si tu essaies d'accéder à myVar en dehors de funcA, tu obtiens, sans surprise, une erreur car la variable est locale à funcA :

Code Python : erreur en accédant à myVar en dehors de funcA, variable de scope local

Ensuite, si tu as deux fonctions non imbriquées, tu remarqueras que les variables déclarées dans une fonction ne sont pas accessibles dans l'autre :

Code Python : deux fonctions non imbriquées dont les variables locales ne sont pas partagées

C'est le principe du scope local, et en Python, il a la priorité la plus haute lors de la résolution de scope.

Autrement dit, si le même nom de variable existe à plusieurs endroits dans un programme, Python utilisera la variable qui se trouve dans le scope local depuis lequel elle a été accédée.

Par exemple, dans le code ci-dessous, myVar est déclarée dans funcA et dans funcB. Quand on appelle funcA(), Python trouve myVar dans le scope local et l'affiche :

Code Python : myVar déclarée dans funcA et funcB, funcA() affiche la valeur du scope local

Si la variable est trouvée dans le scope local, parfait.

Sinon, Python passe au scope englobant (s'il existe) pour trouver la variable.

❝

Avant de passer au scope englobant…

Un point à toujours retenir sur le scope local : il est créé à chaque appel de fonction, c'est-à-dire qu'il n'est pas créé au moment de la définition de la fonction.

Tu peux donc avoir autant de scopes locaux distincts que de fois où tu appelles une fonction, y compris lors d'appels récursifs.

#2) Scope englobant (Enclosing)

En Python, le scope englobant apparaît quand tu as des fonctions imbriquées, c'est-à-dire une ou plusieurs fonctions à l'intérieur d'une autre fonction.

Par exemple, reprends le dernier code ci-dessus. Déclarons funcB à l'intérieur de funcA.

Le scope local et le scope englobant par rapport à funcB sont illustrés ci-dessous :

Code Python : funcB déclarée à l'intérieur de funcA, scopes local et englobant de funcB

Considère maintenant le code suivant, où myVar n'est pas disponible dans le scope local de funcB :

Code Python : myVar absente du scope local de funcB, déclarée dans la fonction englobante funcA

Comme le scope local ne contient pas de variable myVar, Python va d'abord vérifier s'il existe un scope englobant.

Oui, il y en a un : funcA !

Ensuite, il va vérifier si myVar est déclarée dans funcA.

Bien sûr que oui !

Python utilise la valeur déclarée dans le scope englobant pour myVar :

Code Python : funcB utilisant la valeur de myVar issue du scope englobant funcA
❝

Dans le code ci-dessus, si myVar n'était pas déclarée dans funcA, Python serait passé au scope global, qui vient ensuite dans l'ordre de priorité.

#3) Scope global

Le scope global, comme tu l'as probablement deviné, est le scope le plus large.

Ici, les variables et fonctions sont déclarées en dehors de tout bloc, fonction ou classe spécifique.

Elles sont donc accessibles (mais pas directement modifiables) depuis n'importe quelle partie du programme, peu importe où elles sont définies.

Par exemple, dans le code ci-dessous, ni funcA ni funcB n'ont la variable myVar définie dans leur scope local respectif :

Code Python : myVar absente des scopes locaux de funcA et funcB, valeur lue dans le scope global

Il n'y a pas non plus de scope englobant.

Python vérifie donc si myVar est disponible dans le scope global, et elle l'est effectivement.

Ainsi, lors des print, on obtient la valeur globale de myVar, à savoir 10.

En guise d'exercice, peux-tu dire quel sera l'output dans ce cas :

Code Python : exercice de résolution de scope avec prédiction de l'output du code

#4) Scope built-in

Le dernier dans l'ordre de priorité de la résolution de scope est le scope built-in.

Ce sont des noms de variables qui sont automatiquement chargés dans le scope global de Python quand tu :

  • exécutes un script,

  • ou ouvres une session Python interactive.

Ces variables built-in sont accessibles via le nom __builtins__, et il y en a plus de 150 :

Code Python : accès aux variables built-in via __builtins__, plus de 150 noms disponibles

Parmi les plus courants : float, int, len, set, dict, range, str, zip, et bien d'autres.

Si une variable n'est pas trouvée dans le scope local, ni dans le scope englobant, ni même dans le scope global, Python tente de la trouver dans le scope built-in.

Et c'est ainsi que fonctionne la résolution de scope en Python.

Retour à l'exemple : list comprehension vs. boucle for 👇

Visuel comparant boucle for et list comprehension en Python : accessibilité de la variable de boucle

La variable de boucle (loop_var) n'est pas accessible après l'exécution d'une list comprehension parce qu'elle est définie dans son scope local.

En revanche, la variable de boucle est définie dans le scope global dans la boucle for du code ci-dessus. Elle reste donc accessible après l'exécution de la boucle.

J'espère que tu as appris quelque chose de nouveau aujourd'hui.

Envie d'aller plus loin ? DataXp t'attend : formations et parcours pour progresser en data science, machine learning et IA.

👉 À toi de jouer : sais-tu comment modifier une variable globale depuis le scope local d'une fonction ?


Commentaires

Avatar

ou participer

À lire ensuite

Des shots dans la même veine, à lire entre deux tâches.

Voir tous les shots
caret-right