Python Language
Portée variable et liaison
Recherche…
Syntaxe
- global a, b, c
- non local a, b
- x = quelque chose # lie x
- (x, y) = quelque chose # lie x et y
- x + = quelque chose # lie x. De même pour tous les autres "op ="
- del x # lie x
- pour x dans quelque chose: # lie x
- avec quelque chose comme x: # lie x
- sauf exception comme ex: # lie ex à l'intérieur du bloc
Variables globales
Dans Python, les variables à l'intérieur des fonctions sont considérées comme locales si et seulement si elles apparaissent dans la partie gauche d'une instruction d'affectation ou dans une autre occurrence de liaison. sinon, une telle liaison est recherchée dans des fonctions englobantes, jusqu'au niveau global. Cela est vrai même si l'instruction d'affectation n'est jamais exécutée.
x = 'Hi'
def read_x():
print(x) # x is just referenced, therefore assumed global
read_x() # prints Hi
def read_y():
print(y) # here y is just referenced, therefore assumed global
read_y() # NameError: global name 'y' is not defined
def read_y():
y = 'Hey' # y appears in an assignment, therefore it's local
print(y) # will find the local y
read_y() # prints Hey
def read_x_local_fail():
if False:
x = 'Hey' # x appears in an assignment, therefore it's local
print(x) # will look for the _local_ z, which is not assigned, and will not be found
read_x_local_fail() # UnboundLocalError: local variable 'x' referenced before assignment
Normalement, une affectation à l'intérieur d'une étendue ombrera les variables externes du même nom:
x = 'Hi'
def change_local_x():
x = 'Bye'
print(x)
change_local_x() # prints Bye
print(x) # prints Hi
Déclarer un nom global
signifie que, pour le reste de la portée, toute affectation au nom se produira au niveau supérieur du module:
x = 'Hi'
def change_global_x():
global x
x = 'Bye'
print(x)
change_global_x() # prints Bye
print(x) # prints Bye
Le mot-clé global
signifie que les affectations auront lieu au niveau supérieur du module, et non au niveau supérieur du programme. Les autres modules nécessiteront toujours l'accès habituel aux variables du module.
En résumé: pour savoir si une variable x
est locale à une fonction, vous devez lire l' intégralité de la fonction:
- si vous avez trouvé
global x
, alorsx
est une variable globale - Si vous avez trouvé un
nonlocal x
, alorsx
appartient à une fonction englobante et n'est ni local ni global - Si vous avez trouvé
x = 5
oufor x in range(3)
ou une autre liaison, alorsx
est une variable locale - Sinon,
x
appartient à une étendue englobante (étendue de la fonction, étendue globale ou fonctions intégrées).
Variables locales
Si un nom est lié à l' intérieur d'une fonction, il est accessible par défaut uniquement dans la fonction:
def foo():
a = 5
print(a) # ok
print(a) # NameError: name 'a' is not defined
Les constructions de flux de contrôle n'ont aucun impact sur la portée (à l'exception de except
), mais l'accès à la variable qui n'a pas encore été attribué est une erreur:
def foo():
if True:
a = 5
print(a) # ok
b = 3
def bar():
if False:
b = 5
print(b) # UnboundLocalError: local variable 'b' referenced before assignment
Les opérations de liaison communes sont des affectations, for
boucles et des affectations augmentées telles que a += 5
Variables non locales
Python 3 a ajouté un nouveau mot-clé appelé nonlocal . Le mot clé nonlocal ajoute un remplacement de portée à la portée interne. Vous pouvez tout lire à ce sujet dans PEP 3104 . Ceci est mieux illustré avec quelques exemples de code. L'un des exemples les plus courants est de créer une fonction pouvant s'incrémenter:
def counter():
num = 0
def incrementer():
num += 1
return num
return incrementer
Si vous essayez d'exécuter ce code, vous recevrez une erreur UnboundLocalError car la variable num est référencée avant d'être affectée dans la fonction la plus interne. Ajoutons nonlocal au mixage:
def counter():
num = 0
def incrementer():
nonlocal num
num += 1
return num
return incrementer
c = counter()
c() # = 1
c() # = 2
c() # = 3
Fondamentalement, nonlocal
- nonlocal
vous permettra d'attribuer des variables dans une portée externe, mais pas une portée globale. Donc, vous ne pouvez pas utiliser nonlocal
dans notre fonction de counter
, car cela tenterait d’attribuer une portée globale. Essayez-le et vous obtiendrez rapidement une SyntaxError
. Au lieu de cela, vous devez utiliser nonlocal
dans une fonction imbriquée.
(Notez que la fonctionnalité présentée ici est mieux implémentée en utilisant des générateurs.)
Occurrence de liaison
x = 5
x += 7
for x in iterable: pass
Chacune des instructions ci-dessus est une occurrence de liaison - x
devient lié à l’objet désigné par 5
. Si cette instruction apparaît dans une fonction, alors x
sera la fonction locale par défaut. Voir la section "Syntaxe" pour une liste des instructions de liaison.
Les fonctions ignorent la portée de la classe lors de la recherche de noms
Les classes ont une portée locale lors de la définition, mais les fonctions de la classe n'utilisent pas cette étendue lors de la recherche de noms. Comme les lambdas sont des fonctions et que les compréhensions sont implémentées à l'aide de la portée des fonctions, cela peut entraîner un comportement surprenant.
a = 'global'
class Fred:
a = 'class' # class scope
b = (a for i in range(10)) # function scope
c = [a for i in range(10)] # function scope
d = a # class scope
e = lambda: a # function scope
f = lambda a=a: a # default argument uses class scope
@staticmethod # or @classmethod, or regular instance method
def g(): # function scope
return a
print(Fred.a) # class
print(next(Fred.b)) # global
print(Fred.c[0]) # class in Python 2, global in Python 3
print(Fred.d) # class
print(Fred.e()) # global
print(Fred.f()) # class
print(Fred.g()) # global
Les utilisateurs peu familiarisés avec le fonctionnement de cette étendue peuvent s'attendre à ce que b
, c
et e
impriment la class
.
De PEP 227 :
Les noms dans la portée de la classe ne sont pas accessibles. Les noms sont résolus dans la portée de la fonction la plus interne. Si une définition de classe se produit dans une chaîne de portées imbriquées, le processus de résolution ignore les définitions de classe.
De la documentation de Python sur la dénomination et la liaison :
La portée des noms définis dans un bloc de classe est limitée au bloc de classe; il ne s'étend pas aux blocs de code des méthodes - cela inclut les expressions de compréhension et de générateur car elles sont implémentées en utilisant une étendue de fonction. Cela signifie que les éléments suivants échoueront:
class A: a = 42 b = list(a + i for i in range(10))
Cet exemple utilise les références de cette réponse de Martijn Pieters, qui contient une analyse plus approfondie de ce comportement.
La commande del
Cette commande a plusieurs formes liées mais distinctes.
del v
Si v
est une variable, la commande del v
supprime la variable de son étendue. Par exemple:
x = 5
print(x) # out: 5
del x
print(x) # NameError: name 'f' is not defined
Notez que
del
est une occurrence de liaison , ce qui signifie que, sauf indication contraire explicite (en utilisant desnonlocal
ouglobal
),del v
affecterav
local à la portée actuelle. Si vous envisagez de supprimerv
dans une étendue externe, utiliseznonlocal v
ouglobal v
dans la même portée de l'instructiondel v
.
Dans tout ce qui suit, l’intention d’une commande est un comportement par défaut mais n’est pas appliquée par le langage. Une classe peut être écrite de manière à invalider cette intention.
del v.name
Cette commande déclenche un appel à la v.__delattr__(name)
.
L'intention est de rendre le name
attribut indisponible. Par exemple:
class A:
pass
a = A()
a.x = 7
print(a.x) # out: 7
del a.x
print(a.x) # error: AttributeError: 'A' object has no attribute 'x'
del v[item]
Cette commande déclenche un appel à v.__delitem__(item)
.
L'intention est que cet item
n'appartienne pas au mappage implémenté par l'objet v
. Par exemple:
x = {'a': 1, 'b': 2}
del x['a']
print(x) # out: {'b': 2}
print(x['a']) # error: KeyError: 'a'
del v[a:b]
Cela appelle réellement v.__delslice__(a, b)
.
L'intention est similaire à celle décrite ci-dessus, mais avec des tranches - des plages d'éléments au lieu d'un seul élément. Par exemple:
x = [0, 1, 2, 3, 4]
del x[1:3]
print(x) # out: [0, 3, 4]
Voir aussi Garbage Collection # La commande del
Portée locale vs globale
Quelle est la portée locale et globale?
Toutes les variables Python accessibles à un moment donné dans le code sont de portée locale ou globale .
L'explication est que la portée locale inclut toutes les variables définies dans la fonction en cours et que la portée globale inclut les variables définies en dehors de la fonction en cours.
foo = 1 # global
def func():
bar = 2 # local
print(foo) # prints variable foo from global scope
print(bar) # prints variable bar from local scope
On peut inspecter quelles variables sont dans quelle portée. Les fonctions intégrées locals()
et globals()
renvoient l'ensemble des étendues en tant que dictionnaires.
foo = 1
def func():
bar = 2
print(globals().keys()) # prints all variable names in global scope
print(locals().keys()) # prints all variable names in local scope
Que se passe-t-il avec les conflits de noms?
foo = 1
def func():
foo = 2 # creates a new variable foo in local scope, global foo is not affected
print(foo) # prints 2
# global variable foo still exists, unchanged:
print(globals()['foo']) # prints 1
print(locals()['foo']) # prints 2
Pour modifier une variable globale, utilisez le mot-clé global
:
foo = 1
def func():
global foo
foo = 2 # this modifies the global foo, rather than creating a local variable
La portée est définie pour tout le corps de la fonction!
Cela signifie qu'une variable ne sera jamais globale pour la moitié de la fonction et locale par la suite, ou inversement.
foo = 1
def func():
# This function has a local variable foo, because it is defined down below.
# So, foo is local from this point. Global foo is hidden.
print(foo) # raises UnboundLocalError, because local foo is not yet initialized
foo = 7
print(foo)
De même, l'opposite:
foo = 1
def func():
# In this function, foo is a global variable from the begining
foo = 7 # global foo is modified
print(foo) # 7
print(globals()['foo']) # 7
global foo # this could be anywhere within the function
print(foo) # 7
Fonctions dans les fonctions
Il peut exister de nombreux niveaux de fonctions imbriquées dans les fonctions, mais au sein d’une même fonction, il n’ya qu’une seule portée locale pour cette fonction et la portée globale. Il n'y a pas de portées intermédiaires.
foo = 1
def f1():
bar = 1
def f2():
baz = 2
# here, foo is a global variable, baz is a local variable
# bar is not in either scope
print(locals().keys()) # ['baz']
print('bar' in locals()) # False
print('bar' in globals()) # False
def f3():
baz = 3
print(bar) # bar from f1 is referenced so it enters local scope of f3 (closure)
print(locals().keys()) # ['bar', 'baz']
print('bar' in locals()) # True
print('bar' in globals()) # False
def f4():
bar = 4 # a new local bar which hides bar from local scope of f1
baz = 4
print(bar)
print(locals().keys()) # ['bar', 'baz']
print('bar' in locals()) # True
print('bar' in globals()) # False
global
vs nonlocal
(Python 3 uniquement)
Ces deux mots-clés sont utilisés pour obtenir un accès en écriture aux variables qui ne sont pas locales pour les fonctions en cours.
Le mot-clé global
déclare qu'un nom doit être traité comme une variable globale.
foo = 0 # global foo
def f1():
foo = 1 # a new foo local in f1
def f2():
foo = 2 # a new foo local in f2
def f3():
foo = 3 # a new foo local in f3
print(foo) # 3
foo = 30 # modifies local foo in f3 only
def f4():
global foo
print(foo) # 0
foo = 100 # modifies global foo
D'un autre côté, nonlocal
(voir Variables nonlocal
), disponible dans Python 3, prend une variable locale d'une portée englobante dans la portée locale de la fonction actuelle.
De la documentation Python sur nonlocal
:
L'instruction nonlocal fait que les identificateurs répertoriés font référence aux variables précédemment liées dans la portée englobante la plus proche, à l'exception des globales.
def f1():
def f2():
foo = 2 # a new foo local in f2
def f3():
nonlocal foo # foo from f2, which is the nearest enclosing scope
print(foo) # 2
foo = 20 # modifies foo from f2!