Dans cet extrait de code, je peux imprimer la valeur du compteur depuis l'intérieur de la fonction Bar
def foo():
counter = 1
def bar():
print("bar", counter)
return bar
bar = foo()
bar()
Mais lorsque j'essaie d'incrémenter un compteur depuis l'intérieur de la fonction de barre, une erreur UnboundLocalError est générée.
UnboundLocalError: local variable 'counter' referenced before assignment
Extrait de code avec instruction d’incrémentation en format.
def foo():
counter = 1
def bar():
counter += 1
print("bar", counter)
return bar
bar = foo()
bar()
Avez-vous uniquement un accès en lecture aux variables de la fonction externe dans une fermeture Python?
Vous ne pouvez pas muter les variables de fermeture dans Python 2. En Python 3, que vous semblez utiliser en raison de votre print()
, vous pouvez les déclarer nonlocal
:
def foo():
counter = 1
def bar():
nonlocal counter
counter += 1
print("bar", counter)
return bar
Sinon, l'affectation dans bar()
rend la variable locale et, comme vous n'avez pas affecté de valeur à la variable dans l'étendue locale, la tentative d'accès y est une erreur.
Dans Python 2, ma solution de contournement préférée ressemble à ceci:
def foo():
class nonlocal:
counter = 1
def bar():
nonlocal.counter += 1
print("bar", nonlocal.counter)
return bar
Cela fonctionne car la mutation d'un objet mutable ne nécessite pas de changer le nom de la variable. Dans ce cas, nonlocal
est la variable de fermeture et elle n'est jamais réaffectée. seul son contenu est modifié. D'autres solutions utilisent des listes ou des dictionnaires.
Vous pouvez aussi utiliser un cours pour tout, comme @naomik le suggère dans un commentaire.
Pourquoi Python ne peut-il pas incrémenter les variables en fermeture?
Le message d'erreur est explicite. Je propose quelques solutions ici.
nonlocal
(idéal, mais uniquement avec Python 3)__call__
Définissez un attribut de compteur sur votre fonction manuellement après l'avoir créé:
def foo():
foo.counter += 1
return foo.counter
foo.counter = 0
Et maintenant:
>>> foo()
1
>>> foo()
2
>>> foo()
3
Ou vous pouvez configurer automatiquement la fonction:
def foo():
if not hasattr(foo, 'counter'):
foo.counter = 0
foo.counter += 1
return foo.counter
De même:
>>> foo()
1
>>> foo()
2
>>> foo()
3
Ces approches sont simples, mais rares, et il est peu probable que quelqu'un visualise votre code sans votre présence.
Les moyens les plus courants d’atteindre vos objectifs varient en fonction de votre version de Python.
nonlocal
En Python 3, vous pouvez déclarer nonlocal:
def foo():
counter = 0
def bar():
nonlocal counter
counter += 1
print("bar", counter)
return bar
bar = foo()
Et cela augmenterait
>>> bar()
bar 1
>>> bar()
bar 2
>>> bar()
bar 3
C'est probablement la solution la plus idiomatique à ce problème. Dommage que ce soit limité à Python 3.
Vous pouvez déclarer une variable globale, puis incrémenter dessus, mais cela encombre l'espace de noms du module. Ainsi, la solution de contournement idiomatique pour éviter de déclarer une variable globale consiste à pointer sur un objet mutable contenant le nombre entier sur lequel vous souhaitez incrémenter, de sorte que vous n'essayiez pas de réaffecter le nom de la variable:
def foo():
counter = [0]
def bar():
counter[0] += 1
print("bar", counter)
return bar
bar = foo()
et maintenant:
>>> bar()
('bar', [1])
>>> bar()
('bar', [2])
>>> bar()
('bar', [3])
Je pense que cela est supérieur aux suggestions qui impliquent la création de classes simplement pour contenir votre variable incrémentante. Mais pour être complet, voyons ça.
class Foo(object):
def __init__(self):
self._foo_call_count = 0
def foo(self):
self._foo_call_count += 1
print('Foo.foo', self._foo_call_count)
foo = Foo()
et maintenant:
>>> foo.foo()
Foo.foo 1
>>> foo.foo()
Foo.foo 2
>>> foo.foo()
Foo.foo 3
ou même implémenter __call__
:
class Foo2(object):
def __init__(self):
self._foo_call_count = 0
def __call__(self):
self._foo_call_count += 1
print('Foo', self._foo_call_count)
foo = Foo2()
et maintenant:
>>> foo()
Foo 1
>>> foo()
Foo 2
>>> foo()
Foo 3
Comme vous ne pouvez pas modifier les objets immuables en Python, il existe une solution de contournement dans Python 2.X:
Utiliser la liste
def counter(start):
count = [start]
def incr():
count[0] += 1
return count[0]
return incr
a = counter(100)
print a()
b = counter(200)
print b()