Réponses:
Les fonctions internes peuvent lire des variables non locales dans 2.x, mais pas les relier . C'est ennuyeux, mais vous pouvez le contourner. Créez simplement un dictionnaire et stockez vos données sous forme d'éléments. Il n'est pas interdit aux fonctions internes de muter les objets auxquels les variables non locales font référence.
Pour utiliser l'exemple de Wikipedia:
def outer():
d = {'y' : 0}
def inner():
d['y'] += 1
return d['y']
return inner
f = outer()
print(f(), f(), f()) #prints 1 2 3
def inner(): print d; d = {'y': 1}
. Ici, print d
lit externe d
créant ainsi une variable non locale d
dans la portée interne.
X = 1
lie simplement le nom X
à un objet particulier (et int
à la valeur 1
). X = 1; Y = X
lie deux noms au même objet exact. Quoi qu'il en soit, certains objets sont modifiables et vous pouvez modifier leurs valeurs.
La solution suivante est inspirée de la réponse d'Elias Zamaria , mais contrairement à cette réponse gère correctement plusieurs appels de la fonction externe. La "variable" inner.y
est locale à l'appel courant de outer
. Seulement ce n'est pas une variable, puisque cela est interdit, mais un attribut d'objet (l'objet étant la fonction inner
elle-même). C'est très moche (notez que l'attribut ne peut être créé qu'une fois la inner
fonction définie) mais semble efficace.
def outer():
def inner():
inner.y += 1
return inner.y
inner.y = 0
return inner
f = outer()
g = outer()
print(f(), f(), g(), f(), g()) #prints (1, 2, 1, 3, 2)
inc()
et a dec()
renvoyés de l'extérieur qui incrémentent et décrémentent un compteur partagé. Ensuite, vous devez décider à quelle fonction attacher la valeur de compteur actuelle et référencer cette fonction à partir des autres. Ce qui semble un peu étrange et asymétrique. Par exemple, dans dec()
une ligne comme inc.value -= 1
.
Plutôt qu'un dictionnaire, il y a moins d'encombrement dans une classe non locale . Modification de l' exemple de @ ChrisB :
def outer():
class context:
y = 0
def inner():
context.y += 1
return context.y
return inner
ensuite
f = outer()
assert f() == 1
assert f() == 2
assert f() == 3
assert f() == 4
Chaque appel external () crée une nouvelle classe distincte appelée context (pas simplement une nouvelle instance). Cela évite donc à @ Nathaniel de se méfier du contexte partagé.
g = outer()
assert g() == 1
assert g() == 2
assert f() == 5
__slots__ = ()
et en créant un objet au lieu d'utiliser la classe, par exemple context.z = 3
, lèverait un AttributeError
. C'est possible pour toutes les classes, sauf si elles héritent d'une classe ne définissant pas de slots.
Je pense que la clé ici est ce que vous entendez par «accès». Il ne devrait y avoir aucun problème avec la lecture d'une variable en dehors de la portée de fermeture, par exemple,
x = 3
def outer():
def inner():
print x
inner()
outer()
devrait fonctionner comme prévu (impression 3). Cependant, remplacer la valeur de x ne fonctionne pas, par exemple,
x = 3
def outer():
def inner():
x = 5
inner()
outer()
print x
imprimeront toujours 3. D'après ma compréhension de PEP-3104, c'est ce que le mot-clé non local est censé couvrir. Comme mentionné dans le PEP, vous pouvez utiliser une classe pour accomplir la même chose (un peu désordonné):
class Namespace(object): pass
ns = Namespace()
ns.x = 3
def outer():
def inner():
ns.x = 5
inner()
outer()
print ns.x
def ns(): pass
suivie de ns.x = 3
. Ce n'est pas joli, mais c'est un peu moins moche à mes yeux.
class Namespace: x = 3
?
ns
est un objet global, c'est pourquoi vous pouvez faire référence ns.x
au niveau du module dans l' print
instruction à la toute fin .
Il existe une autre façon d'implémenter des variables non locales dans Python 2, au cas où l'une des réponses ici ne serait pas souhaitable pour une raison quelconque:
def outer():
outer.y = 0
def inner():
outer.y += 1
return outer.y
return inner
f = outer()
print(f(), f(), f()) #prints 1 2 3
Il est redondant d'utiliser le nom de la fonction dans l'instruction d'affectation de la variable, mais cela me semble plus simple et plus propre que de mettre la variable dans un dictionnaire. La valeur est mémorisée d'un appel à l'autre, tout comme dans la réponse de Chris B.
f = outer()
, puis plus tard g = outer()
, f
le compteur de sera réinitialisé. C'est parce qu'ils partagent tous deux une seule outer.y
variable, plutôt que chacun ayant sa propre variable indépendante. Bien que ce code semble plus esthétique que la réponse de Chris B, sa manière semble être le seul moyen d'émuler la portée lexicale si vous souhaitez appeler outer
plus d'une fois.
outer.y
n'implique rien de local à l'appel de fonction (instance) outer()
, mais affecte à un attribut de l'objet fonction qui est lié au nom outer
dans sa portée englobante . Et donc on aurait tout aussi bien pu utiliser, par écrit outer.y
, n'importe quel autre nom à la place de outer
, à condition qu'il soit connu pour être lié dans cette portée. Est-ce correct?
outer.y
utiliser le nom inner.y
(puisque inner
est lié à l'intérieur de l'appel outer()
, qui est exactement la portée que nous voulons), mais en mettant l'initialisation inner.y = 0
après la définition de inner (comme l'objet doit exister lorsque son attribut est créé), mais bien sûr avant return inner
?
Voici quelque chose inspiré d'une suggestion faite par Alois Mahdal dans un commentaire concernant une autre réponse :
class Nonlocal(object):
""" Helper to implement nonlocal names in Python 2.x """
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
def outer():
nl = Nonlocal(y=0)
def inner():
nl.y += 1
return nl.y
return inner
f = outer()
print(f(), f(), f()) # -> (1 2 3)
Mettre à jour
Après y avoir repensé récemment, j'ai été frappé de voir à quel point il ressemblait à un décorateur - quand il m'est apparu que son implémentation en tant que tel le rendrait plus générique et utile (bien que cela dégrade sans doute sa lisibilité dans une certaine mesure).
# Implemented as a decorator.
class Nonlocal(object):
""" Decorator class to help implement nonlocal names in Python 2.x """
def __init__(self, **kwargs):
self._vars = kwargs
def __call__(self, func):
for k, v in self._vars.items():
setattr(func, k, v)
return func
@Nonlocal(y=0)
def outer():
def inner():
outer.y += 1
return outer.y
return inner
f = outer()
print(f(), f(), f()) # -> (1 2 3)
Notez que les deux versions fonctionnent à la fois en Python 2 et 3.
Il y a une verrue dans les règles de portée de python - l'affectation rend une variable locale à sa portée de fonction immédiatement englobante. Pour une variable globale, vous résoudriez cela avec leglobal
mot clé.
La solution est d'introduire un objet partagé entre les deux portées, qui contient des variables mutables, mais qui est lui-même référencé via une variable non affectée.
def outer(v):
def inner(container = [v]):
container[0] += 1
return container[0]
return inner
Une alternative est le piratage de certaines portées:
def outer(v):
def inner(varname = 'v', scope = locals()):
scope[varname] += 1
return scope[varname]
return inner
Vous pourrez peut-être trouver une astuce pour obtenir le nom du paramètre outer
, puis le passer en tant que varname, mais sans vous fier au nom, outer
vous voudriez utiliser un combinateur Y.
nonlocal
. locals()
crée un dictionnaire des outer()
sections locales au moment où inner()
est défini, mais changer ce dictionnaire ne change pas le v
in outer()
. Cela ne fonctionnera plus lorsque vous aurez plus de fonctions internes qui souhaitent partager une variable fermée. Dites a inc()
et dec()
que incrémentez et décrémentez un compteur partagé.
nonlocal
est une fonctionnalité python 3.
nonlocal
dans Python 2 en général . Vos idées ne couvrent pas le cas général mais juste celui avec une fonction interne. Jetez un œil à cet essentiel pour un exemple. Les deux fonctions internes ont leur propre conteneur. Vous avez besoin d'un objet mutable dans la portée de la fonction externe, comme d'autres réponses l'ont déjà suggéré.
nonlocal
mot clé introduit dans Python 3.
Une autre façon de le faire (même si c'est trop verbeux):
import ctypes
def outer():
y = 0
def inner():
ctypes.pythonapi.PyCell_Set(id(inner.func_closure[0]), id(y + 1))
return y
return inner
x = outer()
x()
>> 1
x()
>> 2
y = outer()
y()
>> 1
x()
>> 3
Étendre la solution élégante Martineau ci-dessus à un cas d'utilisation pratique et un peu moins élégant que j'obtiens:
class nonlocals(object):
""" Helper to implement nonlocal names in Python 2.x.
Usage example:
def outer():
nl = nonlocals( n=0, m=1 )
def inner():
nl.n += 1
inner() # will increment nl.n
or...
sums = nonlocals( { k:v for k,v in locals().iteritems() if k.startswith('tot_') } )
"""
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
def __init__(self, a_dict):
self.__dict__.update(a_dict)
Utiliser une variable globale
def outer():
global y # import1
y = 0
def inner():
global y # import2 - requires import1
y += 1
return y
return inner
f = outer()
print(f(), f(), f()) #prints 1 2 3
Personnellement, je n'aime pas les variables globales. Mais ma proposition est basée sur la réponse https://stackoverflow.com/a/19877437/1083704
def report():
class Rank:
def __init__(self):
report.ranks += 1
rank = Rank()
report.ranks = 0
report()
où l'utilisateur doit déclarer une variable globale ranks
, chaque fois que vous devez appeler le report
. Mon amélioration élimine le besoin d'initialiser les variables de fonction de l'utilisateur.
inner
, mais vous ne pouvez pas l'attribuer, mais vous pouvez modifier ses clés et ses valeurs. Cela évite l'utilisation de variables globales.