Respuestas:
Las funciones internas pueden leer variables no locales en 2.x, pero no volver a enlazarlas . Esto es molesto, pero puede 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
def inner(): print d; d = {'y': 1}
. Aquí, print d
lee el exterior d
creando así una variable no local d
en el ámbito interior.
X = 1
simplemente vincula el nombre X
con un objeto en particular (y int
con el valor 1
). X = 1; Y = X
une 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.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 en inner
sí). Esto es muy feo (tenga en cuenta que el atributo solo se puede crear después de inner
que se define la función) 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)
inc()
y dec()
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, en dec()
una línea como inc.value -= 1
.
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
Luego
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). Por lo tanto, evita que @ Nathaniel tenga cuidado con el contexto compartido.
g = outer()
assert g() == 1
assert g() == 2
assert f() == 5
__slots__ = ()
y crear un objeto en lugar de usar la clase, por ejemplo, context.z = 3
generaría un AttributeError
. 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,
x = 3
def outer():
def inner():
print x
inner()
outer()
debería funcionar como se esperaba (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 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):
class Namespace(object): pass
ns = Namespace()
ns.x = 3
def outer():
def inner():
ns.x = 5
inner()
outer()
print ns.x
def ns(): pass
seguida de ns.x = 3
. No es bonito, pero es un poco menos feo para mí.
class Namespace: x = 3
?
ns
es un objeto global, por lo que puede hacer referencia ns.x
a nivel de módulo en la print
declaració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:
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 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.
f = outer()
y luego lo hace g = outer()
, f
el contador de 'se restablecerá. Esto se debe a que ambos comparten una sola outer.y
variable, 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 llamar outer
más de una vez.
outer.y
no 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 nombre outer
en su ámbito adjunto . Y, por tanto, se podría haber utilizado igualmente, por escrito outer.y
, cualquier otro nombre en lugar de outer
, siempre que se sepa que está vinculado a ese ámbito. ¿Es esto correcto?
outer.y
usar el nombre inner.y
(ya que inner
está vinculado dentro de la llamada outer()
, que es exactamente el alcance que queremos), pero poniendo la inicialización inner.y = 0
después de la definición de interno (ya que el objeto debe existir cuando se crea su atributo), pero, por supuesto, antes return inner
?
Aquí hay algo inspirado en una sugerencia que Alois Mahdal hizo en un comentario con respecto a 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 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).
# 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 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 global
palabra 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.
def outer(v):
def inner(container = [v]):
container[0] += 1
return container[0]
return inner
Una alternativa es la piratería de algunos ámbitos:
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 outer
y luego pasarlo como varname, pero sin depender del nombre outer
que le gustaría, debe usar un combinador Y.
nonlocal
. locals()
crea un diccionario de outer()
s de los locales en el momento inner()
se define pero cambiando ese diccionario qué no cambiar el v
en outer()
. Esto ya no funcionará cuando tenga más funciones internas que quieran compartir una variable cerrada. Decir una inc()
y dec()
ese incremento y disminuir un contador compartido.
nonlocal
es una característica de Python 3.
nonlocal
en 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.
nonlocal
palabra clave introducida en Python 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
Al extender la elegante solución de Martineau anterior 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 la respuesta https://stackoverflow.com/a/19877437/1083704
def report():
class Rank:
def __init__(self):
report.ranks += 1
rank = Rank()
report.ranks = 0
report()
donde el usuario necesita declarar una variable global ranks
, cada vez que necesite llamar a report
. Mi mejora elimina la necesidad de inicializar las variables de función del usuario.
inner
, pero no puede asignarla, pero puede modificar sus claves y valores. Esto evita el uso de variables globales.