%load_ext ipythontutorla portée d’une variable (ou scope) consiste
à répondre à deux questionsquand je référence une variable X,
à quelle variable je fais référence ?quand j’affecte (un objet) à une variable X,
depuis quelles parties de mon code je peux accéder à cette variable ?
portée lexicale¶
Python utilise la portée lexicale,
c’est-à-dire que la portée des variables
est déterminée exclusivement
en fonction de leur place dans le code source
déclaration ?¶
dans d’autres langages, il y a nécessité de déclarer une variable
avant de s’en servir (typiquement les langages compilés)ce n’est pas le cas en Python
toutefois:
le fait d’affecter une variable joue ce rôle-là
et il y a aussi bien sûr les paramètres de la fonction
# ici la variable `y` n'est pas considérée
# comme déclarée puisqu'on se contente
# de la lire, et qu'on ne l'affecte pas
# (pas de code avec `y = ...`
def foo(x):
print(x) # <-- une variable locale
# (paramètre)
print(y) # <-- PAS une variable locale
# (et donc ici BOOM)# ici au contraire la variable y
# est locale à la fonction
# comme le paramètre x
def foo(x):
y = 10 # <-- on "déclare" y
print(x) # <-- une variable locale
# (paramètre)
print(y) # <-- aussi (car affectée
# plus haut dans foo)règle LEGB¶
une variable est cherchée dans cet ordre LEGB
L comme Local
nom déclaré dans la fonction où il est référencé
E comme fonctions Englobantes
nom déclaré dans les fonctions englobant la fonction où il est référencé (de l’intérieur vers l’extérieur)
G comme Global
nom déclaré dans le fichier hors d’une fonction
B comme Built-in
nom provenant du module builtins
variable globale¶
du coup toutes les variables affectées à l’extérieur d’une classe ou fonction sont globales
i.e. susceptibles d’être lues depuis tout le code dans le fichier (on dit un module)
GLOBALE = 10
def foo():
print("from foo:", GLOBALE)
def bar():
print("from bar:", GLOBALE)
bar()
foo()from foo: 10
from bar: 10
exemple de visibilité (1)¶
def foo():
level1 = 10
def bar():
level2 = 20
def tutu():
level3 = 30
print("from tutu:", level1, level2, level3)
print("from bar: ", level1, level2) # level3 NOT visible
tutu()
print("from foo: ", level1) # level2 or level3 NOT visible here
bar()foo()from foo: 10
from bar: 10 20
from tutu: 10 20 30
exemple de visibilité (2) cassé¶
une variable ne peut pas être à la fois globale et locale !
L = [1, 2]
def f():
# ici on pourrait penser utiliser la globale
L.append(3)
# mais en fait non, ici on dit que L est locale !
L = 1
try:
f()
except UnboundLocalError:
print("OOPS")OOPS
exemple de visibilité (2) revu¶
pour réparer, on peut:
enlever le
L = 1qui ne sert à rien :)ou encore passer la globale en paramètre
L = [1, 2]
def f(L):
# ici L est le paramètre donc une locale
L.append(3)
#
L = 1
f(L)
print(L)[1, 2, 3]
attention aux classes¶
attention que ce système ne s’étend pas aux classes
en effet les symboles définis au premier niveau dans une instruction class
sont rangés comme des attributs de la classe
et à ce titre ils ne sont pas accessibles lexicalement
class Foo:
class_variable = 10
def method(self):
# in this scope the symbols
# 'class_variable' and `method`
# ARE NOT lexically visible !!
passglobal et nonlocal¶
mais revenons à nos fonctions:
on peut donc utiliser (lire) dans une fonction
une variable définie au dehors / au dessusmais du coup on ne peut pas la modifier (affecter)
puisque si on essaie de l’affecter cela est considéré
comme une déclaration de variable localec’est à cela que servent les mots clefs
globalounonlocal
exemple avec global (1)¶
# écrire une globale depuis une fonction
G = 10
def modify_G(x):
# une fois la variable déclarée
global G
# je peux l'affecter
G = x
modify_G(1000)
# combien vaut G ?
G1000# à votre avis
# que se passe-t-il si on n'utilise
# pas global
G = 10
def does_not_modify_G(x):
G = x
does_not_modify_G(1000)
# combien vaut G ?
G10pourquoi ?
dans la deuxième forme, on a juste créé une deuxième variable G qui est locale à la fonction, et “cache” la globale, qui donc n’est pas modifiée
exemple avec global (2)¶
# un exemple un peu plus tordu
# car ici dans la fonction
# on lit et on écrit G
G = 10
def increment_G():
global G
G = G + 10
increment_G()
# combien vaut G ?
G20# que se passe-t-il ici
# d'après vous ?
G = 10
def increment_G():
# pas de 'global'
G = G + 10
try:
increment_G()
except UnboundLocalError:
print("OOPS !!")
# combien vaut G ?
GOOPS !!
10pourquoi ?
ce qui se passe ici c’est: on commence par lire G; mais comme G est affectée dans increment_G, c’est une variable locale à la fonction (et donc pas la globale); mais elle n’a pas encore de valeur ! d’où l’erreur UnboundLocalError
faut-il utiliser global ?¶
utiliser des variables globales est - presque toujours - une mauvaise idée
car cela gêne la réutilisabilité
la bonne manière est de
ne pas utiliser de variable globale
penser aux classes
exemple archi-classique
la configuration d’une application
est souvent implémentée comme un singleton
spécificités de global¶
la déclaration
globaldoit apparaître avant l’utilisation
c’est mieux de la mettre en premier dans le bloc
une variable déclarée
globalet assignée dans une fonction
est automatiquement créée dans le module
même si elle n’existait pas avant
exemple avec nonlocal¶
# nonlocal est très utile pour implémenter une cloture
def make_counter():
# cette variable est capturée dans la cloture
counter = 0
def increment():
nonlocal counter
counter += 1
return counter
# on retourne la fonction, qui a "capturé" le compteur
return incrementc1 = make_counter()
c1()
c1()2c2 = make_counter()
c2()
c2()
c2()3c1()3c2()4les noms de builtins¶
ce sont les noms prédéfinis, comme
listouenumerateouOSErrorgrâce à la règle LEGB, pas besoin de les importer
par contre, on peut redéfinir un nom de
builtinsdans son programmec’est une mauvaise idée et une source de bug
python ne donne aucun warning dans ce cas
dans ce cas - comme toujours -
pylintest un outil très utile
les noms de builtins¶
# on peut accéder à la variable `__builtins__`
# qui est .. une variable *builtin*
__builtins__<module 'builtins' (built-in)># ou encore on peut
# importer le module `builtins`
import builtins# je n'en montre que 5 pour garder de la place
dir(builtins)[-5:]['super', 'tuple', 'type', 'vars', 'zip']# en fait il y en a vraiment beaucoup !
len(dir(__builtins__))160errors = (x for x in dir(builtins) if 'Error' in x or 'Warning' in x)
columns, width = 4, 18
for i, error in enumerate(errors, 1):
print(f"{error:^{width}}", end=" ")
if i % columns == 0:
print() ArithmeticError AssertionError AttributeError BlockingIOError
BrokenPipeError BufferError BytesWarning ChildProcessError
ConnectionAbortedError ConnectionError ConnectionRefusedError ConnectionResetError
DeprecationWarning EOFError EncodingWarning EnvironmentError
FileExistsError FileNotFoundError FloatingPointError FutureWarning
IOError ImportError ImportWarning IndentationError
IndexError InterruptedError IsADirectoryError KeyError
LookupError MemoryError ModuleNotFoundError NameError
NotADirectoryError NotImplementedError OSError OverflowError
PendingDeprecationWarning PermissionError ProcessLookupError RecursionError
ReferenceError ResourceWarning RuntimeError RuntimeWarning
SyntaxError SyntaxWarning SystemError TabError
TimeoutError TypeError UnboundLocalError UnicodeDecodeError
UnicodeEncodeError UnicodeError UnicodeTranslateError UnicodeWarning
UserWarning ValueError Warning ZeroDivisionError
others = (x for x in dir(builtins)
if not ('Error' in x or 'Warning' in x or '__' in x))
columns, width = 6, 16
for i, other in enumerate(others, 1):
print(f"{other:^{width}}", end=" ")
if i % columns == 0:
print() BaseException BaseExceptionGroup Ellipsis Exception ExceptionGroup False
GeneratorExit KeyboardInterrupt None NotImplemented StopAsyncIteration StopIteration
SystemExit True abs aiter all anext
any ascii bin bool breakpoint bytearray
bytes callable chr classmethod compile complex
copyright credits delattr dict dir display
divmod enumerate eval exec execfile filter
float format frozenset get_ipython getattr globals
hasattr hash help hex id input
int isinstance issubclass iter len license
list locals map max memoryview min
next object oct open ord pow
print property range repr reversed round
runfile set setattr slice sorted staticmethod
str sum super tuple type vars
zip