Estoy tratando de implementar un cierre en Python 2.6 y necesito acceder a una variable no local, pero parece que esta palabra clave no está disponible en Python 2.x. ¿Cómo se debe acceder a las variables no locales en los cierres en estas versiones de Python?
117

def inner(): print d; d = {'y': 1}. Aquí,print dlee el exteriordcreando así una variable no localden el ámbito interior.X = 1simplemente vincula el nombreXcon un objeto en particular (yintcon el valor1).X = 1; Y = Xune dos nombres al mismo objeto exacto. De todos modos, algunos objetos son mutables y puedes cambiar sus valores.La siguiente solución está inspirada en la respuesta de Elias Zamaria , pero contrariamente a esa respuesta, maneja múltiples llamadas de la función externa correctamente. La "variable"
inner.yes local a la llamada actual deouter. Solo que no es una variable, ya que está prohibido, sino un atributo de objeto (el objeto es la función eninnersí). Esto es muy feo (tenga en cuenta que el atributo solo se puede crear después deinnerque se define la función) pero parece efectivo.fuente
inc()ydec()regresaron de exterior que incrementar y disminuir un contador compartido. Luego, debe decidir a qué función adjuntar el valor actual del contador y hacer referencia a esa función desde las otras. Lo que parece algo extraño y asimétrico. Por ejemplo, endec()una línea comoinc.value -= 1.En lugar de un diccionario, hay menos desorden en una clase no local . Modificando el ejemplo de @ ChrisB :
Luego
Cada llamada externa () crea una clase nueva y distinta llamada contexto (no simplemente una nueva instancia). Por lo tanto, evita que @ Nathaniel tenga cuidado con el contexto compartido.
fuente
__slots__ = ()y crear un objeto en lugar de usar la clase, por ejemplo,context.z = 3generaría unAttributeError. Es posible para todas las clases, a menos que hereden de una clase que no define espacios.Creo que la clave aquí es lo que quieres decir con "acceso". No debería haber ningún problema con la lectura de una variable fuera del alcance del cierre, por ejemplo,
debería funcionar como se esperaba (impresión 3). Sin embargo, anular el valor de x no funciona, por ejemplo,
todavía imprimirá 3. Según mi entendimiento de PEP-3104, esto es lo que la palabra clave nonlocal debe cubrir. Como se menciona en el PEP, puede usar una clase para lograr lo mismo (un poco complicado):
fuente
def ns(): passseguida dens.x = 3. No es bonito, pero es un poco menos feo para mí.class Namespace: x = 3?nses un objeto global, por lo que puede hacer referencians.xa nivel de módulo en laprintdeclaración al final .Hay otra forma de implementar variables no locales en Python 2, en caso de que alguna de las respuestas aquí no sea deseable por cualquier motivo:
Es redundante usar el nombre de la función en la declaración de asignación de la variable, pero me parece más simple y limpio que poner la variable en un diccionario. El valor se recuerda de una llamada a otra, al igual que en la respuesta de Chris B.
fuente
f = outer()y luego lo haceg = outer(),fel contador de 'se restablecerá. Esto se debe a que ambos comparten una solaouter.yvariable, en lugar de que cada uno tenga su propia variable independiente. Aunque este código parece más agradable desde el punto de vista estético que la respuesta de Chris B, su forma parece ser la única forma de emular el alcance léxico si desea llamaroutermás de una vez.outer.yno implica nada local a la llamada de función (instancia)outer(), pero asigna a un atributo del objeto de función que está vinculado al nombreouteren su ámbito adjunto . Y, por tanto, se podría haber utilizado igualmente, por escritoouter.y, cualquier otro nombre en lugar deouter, siempre que se sepa que está vinculado a ese ámbito. ¿Es esto correcto?outer.yusar el nombreinner.y(ya queinnerestá vinculado dentro de la llamadaouter(), que es exactamente el alcance que queremos), pero poniendo la inicializacióninner.y = 0después de la definición de interno (ya que el objeto debe existir cuando se crea su atributo), pero, por supuesto, antesreturn inner?Aquí hay algo inspirado en una sugerencia que Alois Mahdal hizo en un comentario con respecto a otra respuesta :
Actualizar
Después de mirar hacia atrás en esto recientemente, me sorprendió lo parecido a un decorador que era, cuando me di cuenta de que implementarlo como uno lo haría más genérico y útil (aunque podría decirse que hacerlo degrada su legibilidad hasta cierto punto).
Tenga en cuenta que ambas versiones funcionan tanto en Python 2 como en 3.
fuente
Hay una verruga en las reglas de alcance de Python: la asignación hace que una variable sea local al alcance de la función que la encierra inmediatamente. Para una variable global, resolvería esto con la
globalpalabra clave.La solución es introducir un objeto que se comparte entre los dos ámbitos, que contiene variables mutables, pero se hace referencia a sí mismo a través de una variable que no está asignada.
Una alternativa es la piratería de algunos ámbitos:
Es posible que pueda descubrir algunos trucos para obtener el nombre del parámetro
outery luego pasarlo como varname, pero sin depender del nombreouterque le gustaría, debe usar un combinador Y.fuente
nonlocal.locals()crea un diccionario deouter()s de los locales en el momentoinner()se define pero cambiando ese diccionario qué no cambiar elvenouter(). Esto ya no funcionará cuando tenga más funciones internas que quieran compartir una variable cerrada. Decir unainc()ydec()ese incremento y disminuir un contador compartido.nonlocales una característica de Python 3.nonlocalen Python 2 en general . Tus ideas no cubren el caso general, sino solo el que tiene una función interna. Eche un vistazo a esta esencia para ver un ejemplo. Ambas funciones internas tienen su propio contenedor. Necesita un objeto mutable en el alcance de la función externa, como ya sugirieron otras respuestas.nonlocalpalabra clave introducida en Python 3.Otra forma de hacerlo (aunque es demasiado detallado):
fuente
Al extender la elegante solución de Martineau anterior a un caso de uso práctico y algo menos elegante, obtengo:
fuente
Usa una variable global
Personalmente, no me gustan las variables globales. Pero, mi propuesta se basa en la respuesta https://stackoverflow.com/a/19877437/1083704
donde el usuario necesita declarar una variable global
ranks, cada vez que necesite llamar areport. Mi mejora elimina la necesidad de inicializar las variables de función del usuario.fuente
inner, pero no puede asignarla, pero puede modificar sus claves y valores. Esto evita el uso de variables globales.