python closures python-2.x python-nonlocal

palabra clave no local en Python 2.x



closures python-2.x (10)

Aquí hay algo inspirado por una sugerencia que hizo Alois Mahdal en un comment sobre otra answer :

class Nonlocals(object): """ Helper class to implement nonlocal names in Python 2.x """ def __init__(self, **kwargs): self.__dict__.update(kwargs) def outer(): nonlocals = Nonlocals(y=0) def inner(): nonlocals.y += 1 return nonlocals.y return inner f = outer() print(f(), f(), f()) # -> (1 2 3)

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?


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,

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

debería funcionar como se espera (impresión 3). Sin embargo, anular 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 comprensión de PEP-3104 esto es lo que la palabra clave no local debe cubrir. Como se menciona en el PEP, puede usar una clase para lograr lo mismo (algo desordenado):

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


En lugar de un diccionario, hay menos desorden en una clase no local . Modificando el example @ 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 instancia nueva). Por lo tanto, evita que @ Nathaniel tenga cuidado con el contexto compartido.

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


Extendiendo la elegante solución de Martineau a un práctico y algo menos elegante estuche de uso que 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)


Hay otra forma de implementar variables no locales en Python 2, en caso de que alguna de las respuestas aquí no sea deseable por alguna razón:

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 utilizar el nombre de la función en la declaración de asignación de la variable, pero me parece más sencillo y más 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.


Hay una verruga en las reglas de alcance de python: la asignación hace que una variable sea local para su alcance de función que encierra inmediatamente. Para una variable global, usted 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 se hace referencia a él mismo 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 hacks de alcances:

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

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


La siguiente solución está inspirada en la respuesta de Elias Zamaria , pero contrariamente a esa respuesta maneja correctamente múltiples llamadas de la función externa. La "variable" inner.y es local a la llamada actual de outer . Solo que no es una variable, ya que está prohibido, sino un atributo de objeto (el objeto es la función inner misma). Esto es muy feo (tenga en cuenta que el atributo solo se puede crear después de definir inner función inner ) pero parece 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)


Las funciones internas pueden leer variables no locales en 2.x, pero no las vuelven a vincular . Esto es molesto, pero puedes evitarlo. Simplemente crea un diccionario y almacena tus 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


Otra forma de hacerlo (aunque es demasiado detallado):

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


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://.com/a/19877437/1083704 respuesta

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

donde el usuario necesita declarar ranks variables globales, cada vez que necesita llamar al report . Mi mejora elimina la necesidad de inicializar las variables de función del usuario.