palabra clave no local en Python 2.x

Estoy intentando 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 debería uno acceder a variables no locales en cierres en estas versiones de python?

Las funciones internas pueden leer variables no locales en 2.x, pero no volver a enlazarlas . Esto es molesto, pero puedes solucionarlo. Simplemente cree un diccionario y almacene sus datos como elementos en él. Las funciones internas no tienen prohibido mutar los objetos a los que se refieren las variables no locales.

Para usar el ejemplo 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 

La siguiente solución está inspirada en la respuesta de Elias Zamaria , pero contrariamente a esa respuesta maneja correctamente las múltiples llamadas de la función externa. La “variable” inner.y es local a la llamada actual de inner.y . Solo que no es una variable, ya que eso está prohibido, sino un atributo de objeto (el objeto es la función inner ). Esto es muy feo (tenga en cuenta que el atributo solo se puede crear después de que se haya definido inner función inner ) pero parece ser efectivo.

 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) 

En lugar de un diccionario, hay menos desorden en una clase no local . Modificando el ejemplo de @ ChrisB:

 def outer(): class context: y = 0 def inner(): context.y += 1 return context.y return inner 

Entonces

 f = outer() assert f() == 1 assert f() == 2 assert f() == 3 assert f() == 4 

Cada llamada externa () crea una clase nueva y distinta llamada contexto (no simplemente una nueva instancia). Así que evita que Nathaniel tenga cuidado con el contexto compartido.

 g = outer() assert g() == 1 assert g() == 2 assert f() == 5 

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 scope de cierre, por ejemplo,

 x = 3 def outer(): def inner(): print x inner() outer() 

Debería funcionar como se espera (impresión 3). Sin embargo, reemplazar el valor de x no funciona, por ejemplo,

 x = 3 def outer(): def inner(): x = 5 inner() outer() print x 

todavía se imprimirá 3. Según mi entendimiento de PEP-3104, esto es lo que la palabra clave no local debe cubrir. Como se mencionó en el PEP, puede usar una clase para lograr lo mismo (un poco desordenado):

 class Namespace(object): pass ns = Namespace() ns.x = 3 def outer(): def inner(): ns.x = 5 inner() outer() print ns.x 

Hay otra forma de implementar variables no locales en Python 2, en caso de que alguna de las respuestas aquí sea indeseable por cualquier motivo:

 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 

Es redundante usar el nombre de la función en la statement 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.

Aquí hay algo inspirado en una sugerencia que Alois Mahdal hizo en un comentario sobre otra respuesta :

 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) 

Actualizar

Después de recordar esto recientemente, me sorprendió lo bien que era el decorador, cuando me di cuenta de que implementarlo como uno lo haría más genérico y útil (aunque al hacerlo, posiblemente se degrada su legibilidad).

 # 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) 

Tenga en cuenta que ambas versiones funcionan tanto en Python 2 como en 3.

Hay una verruga en las reglas de scope de Python: la asignación hace que una variable sea local para su scope de función de cierre inmediato. Para una variable global, resolvería esto con la palabra clave global .

La solución es introducir un objeto que se comparte entre los dos ámbitos, que contiene variables mutables, pero que a su vez se hace referencia a través de una variable que no está asignada.

 def outer(v): def inner(container = [v]): container[0] += 1 return container[0] return inner 

Una alternativa es algunos ámbitos de piratería:

 def outer(v): def inner(varname = 'v', scope = locals()): scope[varname] += 1 return scope[varname] return inner 

Es posible que pueda descubrir algunos trucos para obtener el nombre del parámetro en el outer , y luego pasarlo como varname, pero sin confiar en el nombre outer le gustaría usar un combinador de Y.

Otra forma de hacerlo (aunque es demasiado verbosa):

 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 

Extendiendo la elegante solución de Martineau a un caso de uso práctico y algo menos elegante, obtengo:

 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) 

Usa una variable global

 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 

Personalmente, no me gustan las variables globales. Pero, mi propuesta se basa en https://stackoverflow.com/a/19877437/1083704 respuesta

 def report(): class Rank: def __init__(self): report.ranks += 1 rank = Rank() report.ranks = 0 report() 

donde el usuario debe declarar un ranks variable global, cada vez que necesite llamar al report . Mi mejora elimina la necesidad de inicializar las variables de función del usuario.