J'essaie d'implémenter une fermeture en Python 2.6 et j'ai besoin d'accéder à une variable non locale mais il semble que ce mot clé ne soit pas disponible dans python 2.x. Comment accéder aux variables non locales dans les fermetures dans ces versions de python?
117
def inner(): print d; d = {'y': 1}
. Ici,print d
lit externed
créant ainsi une variable non localed
dans la portée interne.X = 1
lie simplement le nomX
à un objet particulier (etint
à la valeur1
).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 deouter
. Seulement ce n'est pas une variable, puisque cela est interdit, mais un attribut d'objet (l'objet étant la fonctioninner
elle-même). C'est très moche (notez que l'attribut ne peut être créé qu'une fois lainner
fonction définie) mais semble efficace.la source
inc()
et adec()
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, dansdec()
une ligne commeinc.value -= 1
.Plutôt qu'un dictionnaire, il y a moins d'encombrement dans une classe non locale . Modification de l' exemple de @ ChrisB :
ensuite
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é.
la source
__slots__ = ()
et en créant un objet au lieu d'utiliser la classe, par exemplecontext.z = 3
, lèverait unAttributeError
. 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,
devrait fonctionner comme prévu (impression 3). Cependant, remplacer la valeur de x ne fonctionne pas, par exemple,
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é):
la source
def ns(): pass
suivie dens.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érencens.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:
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.
la source
f = outer()
, puis plus tardg = outer()
,f
le compteur de sera réinitialisé. C'est parce qu'ils partagent tous deux une seuleouter.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 appelerouter
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 nomouter
dans sa portée englobante . Et donc on aurait tout aussi bien pu utiliser, par écritouter.y
, n'importe quel autre nom à la place deouter
, à condition qu'il soit connu pour être lié dans cette portée. Est-ce correct?outer.y
utiliser le nominner.y
(puisqueinner
est lié à l'intérieur de l'appelouter()
, qui est exactement la portée que nous voulons), mais en mettant l'initialisationinner.y = 0
après la définition de inner (comme l'objet doit exister lorsque son attribut est créé), mais bien sûr avantreturn inner
?Voici quelque chose inspiré d'une suggestion faite par Alois Mahdal dans un commentaire concernant une autre réponse :
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).
Notez que les deux versions fonctionnent à la fois en Python 2 et 3.
la source
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 le
global
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.
Une alternative est le piratage de certaines portées:
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.la source
nonlocal
.locals()
crée un dictionnaire desouter()
sections locales au moment oùinner()
est défini, mais changer ce dictionnaire ne change pas lev
inouter()
. Cela ne fonctionnera plus lorsque vous aurez plus de fonctions internes qui souhaitent partager une variable fermée. Dites ainc()
etdec()
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):
la source
Étendre la solution élégante Martineau ci-dessus à un cas d'utilisation pratique et un peu moins élégant que j'obtiens:
la source
Utiliser une variable globale
Personnellement, je n'aime pas les variables globales. Mais ma proposition est basée sur la réponse https://stackoverflow.com/a/19877437/1083704
où l'utilisateur doit déclarer une variable globale
ranks
, chaque fois que vous devez appeler lereport
. Mon amélioration élimine le besoin d'initialiser les variables de fonction de l'utilisateur.la source
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.