¿Cuál es la diferencia entre globals(), locals() y vars()?
¿Cuál es la diferencia entre globals()
, locals()
y vars()
? ¿Qué devuelven? ¿Son útiles las actualizaciones de los resultados?
Cada uno de estos devuelve un diccionario:
globals()
siempre devuelve el diccionario del espacio de nombres del módulolocals()
siempre devuelve un diccionario del espacio de nombres actualvars()
devuelve un diccionario del espacio de nombres actual (si se llama sin argumento) o el diccionario del argumento.
locals
y vars
le vendría bien alguna explicación más. Si locals()
se llama dentro de una función, actualiza un dict con los valores del espacio de nombres de la variable local actual (más cualquier variable de cierre) a partir de ese momento y lo devuelve. Varias llamadas a locals()
en el mismo marco de pila devuelven el mismo dictado cada vez: está adjunto al objeto del marco de pila como su f_locals
atributo. El contenido del dict se actualiza en cada locals()
llamada y cada f_locals
acceso a atributos, pero solo en dichas llamadas o accesos a atributos. No se actualiza automáticamente cuando se asignan variables y la asignación de entradas en el dict no asignará las variables locales correspondientes:
import inspect
def f():
x = 1
l = locals()
print(l)
locals()
print(l)
x = 2
print(x, l['x'])
l['x'] = 3
print(x, l['x'])
inspect.currentframe().f_locals
print(x, l['x'])
f()
Nos da:
{'x': 1}
{'x': 1, 'l': {...}}
2 1
2 3
2 2
El primero print(l)
sólo muestra una 'x'
entrada, porque la asignación se l
realiza después de la locals()
llamada. El segundo print(l)
, después de locals()
volver a llamar, muestra una l
entrada, aunque no guardamos el valor de retorno. La tercera y cuarta print
muestran que la asignación de variables no se actualiza l
y viceversa, pero después de acceder f_locals
, las variables locales se copian enlocals()
nuevamente.
Dos notas:
- Este comportamiento es específico de CPython: otros Pythons pueden permitir que las actualizaciones regresen automáticamente al espacio de nombres local.
- En CPython 2.x es posible hacer que esto funcione poniendo una
exec "pass"
línea en la función. Esto cambia la función a un modo de ejecución más antiguo y más lento que utiliza ellocals()
dict como representación canónica de variables locales.
Si locals()
se llama fuera de una función, devuelve el diccionario real que es el espacio de nombres actual. Los cambios adicionales en el espacio de nombres se reflejan en el diccionario y los cambios en el diccionario se reflejan en el espacio de nombres:
class Test(object):
a = 'one'
b = 'two'
huh = locals()
c = 'three'
huh['d'] = 'four'
print huh
Nos da:
{
'a': 'one',
'b': 'two',
'c': 'three',
'd': 'four',
'huh': {...},
'__module__': '__main__',
}
Hasta ahora, todo lo que he dicho locals()
también es cierto para vars()
... aquí está la diferencia: vars()
acepta un solo objeto como argumento, y si le das un objeto, devuelve el valor __dict__
de ese objeto. Para un objeto típico, __dict__
es donde se almacena la mayoría de los datos de sus atributos. Esto incluye variables de clase y globales de módulo:
class Test(object):
a = 'one'
b = 'two'
def frobber(self):
print self.c
t = Test()
huh = vars(t)
huh['c'] = 'three'
t.frobber()
lo que nos da:
three
Tenga en cuenta que el de una función __dict__
es su espacio de nombres de atributos, no las variables locales. No tendría sentido que una función __dict__
almacene variables locales, ya que la recursión y el subproceso múltiple significan que puede haber múltiples llamadas a una función al mismo tiempo, cada una con sus propias variables locales:
def f(outer):
if outer:
f(False)
print('Outer call locals:', locals())
print('f.__dict__:', f.__dict__)
else:
print('Inner call locals:', locals())
print('f.__dict__:', f.__dict__)
f.x = 3
f(True)
lo que nos da:
Inner call locals: {'outer': False}
f.__dict__: {'x': 3}
Outer call locals: {'outer': True}
f.__dict__: {'x': 3}
Aquí,f
se llama a sí mismo de forma recursiva, por lo que las llamadas internas y externas se superponen. Cada uno ve sus propias variables locales cuando llama locals()
, pero ambas llamadas ven lo mismo f.__dict__
y f.__dict__
no tienen ninguna variable local.