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 dlit externe dcréant ainsi une variable non locale ddans la portée interne.
X = 1lie simplement le nom Xà un objet particulier (et intà la valeur 1). X = 1; Y = Xlie 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.yest 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 innerelle-même). C'est très moche (notez que l'attribut ne peut être créé qu'une fois la innerfonction 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(): passsuivie de ns.x = 3. Ce n'est pas joli, mais c'est un peu moins moche à mes yeux.
class Namespace: x = 3?
nsest un objet global, c'est pourquoi vous pouvez faire référence ns.xau niveau du module dans l' printinstruction à 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(), fle 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 outerplus d'une fois.
outer.yn'implique rien de local à l'appel de fonction (instance) outer(), mais affecte à un attribut de l'objet fonction qui est lié au nom outerdans 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.yutiliser le nom inner.y(puisque innerest 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, outervous 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 vin 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é.
nonlocalest une fonctionnalité python 3.
nonlocaldans 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é.
nonlocalmot 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.