¿Cuál es la diferencia entre globals(), locals() y vars()?

Resuelto Ethan Furman asked hace 13 años • 1 respuestas

¿Cuál es la diferencia entre globals(), locals()y vars()? ¿Qué devuelven? ¿Son útiles las actualizaciones de los resultados?

Ethan Furman avatar Nov 01 '11 23:11 Ethan Furman
Aceptado

Cada uno de estos devuelve un diccionario:

  • globals() siempre devuelve el diccionario del espacio de nombres del módulo
  • locals() siempre devuelve un diccionario del espacio de nombres actual
  • vars()devuelve un diccionario del espacio de nombres actual (si se llama sin argumento) o el diccionario del argumento.

localsy varsle 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_localsatributo. El contenido del dict se actualiza en cada locals()llamada y cada f_localsacceso 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 lrealiza después de la locals()llamada. El segundo print(l), después de locals()volver a llamar, muestra una lentrada, aunque no guardamos el valor de retorno. La tercera y cuarta printmuestran que la asignación de variables no se actualiza ly viceversa, pero después de acceder f_locals, las variables locales se copian enlocals() nuevamente.

Dos notas:

  1. Este comportamiento es específico de CPython: otros Pythons pueden permitir que las actualizaciones regresen automáticamente al espacio de nombres local.
  2. 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 el locals()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.

Ethan Furman avatar Nov 01 '2011 16:11 Ethan Furman