Establecer dinámicamente la variable local [duplicar]


Esta pregunta ya tiene una respuesta aquí:

¿Cómo se establece dinámicamente la variable local en Python?

(donde el nombre de la variable es dinámico)

ACTUALIZACIÓN: Soy consciente de que esto no es una buena práctica, y los comentarios son legítimos, pero esto no que sea una mala pregunta, solo una más teórica - no veo por qué esto justifica votos negativos.

Author: martineau, 2011-11-06

7 answers

Contrariamente a otras respuestas ya publicadas, no se puede modificar locals() directamente y esperar que funcione.

>>> def foo():
    lcl = locals()
    lcl['xyz'] = 42
    print(xyz)


>>> foo()

Traceback (most recent call last):
  File "<pyshell#6>", line 1, in <module>
    foo()
  File "<pyshell#5>", line 4, in foo
    print(xyz)
NameError: global name 'xyz' is not defined

Modificar locals() no está definido. Fuera de una función cuando locals() y globals() son lo mismo funcionará; dentro de una función normalmente no funcionará.

Use un diccionario, o establezca un atributo en un objeto:

d = {}
d['xyz'] = 42
print(d['xyz'])

O si lo prefiere, use una clase:

class C: pass

obj = C()
setattr(obj, 'xyz', 42)
print(obj.xyz)

Editar: Acceso a variables en espacios de nombres que no son funciones módulos, definiciones de clases, instancias) se realizan normalmente mediante búsquedas en el diccionario (como Sven señala en los comentarios hay excepciones, por ejemplo clases que definen __slots__). Los locales de funciones se pueden optimizar para la velocidad porque el compilador (generalmente) conoce todos los nombres de antemano, por lo que no hay un diccionario hasta que llame a locals().

En la implementación en C de Python locals() (llamada desde dentro de una función) crea un diccionario ordinario inicializado a partir de los valores actuales del local variable. Dentro de cada función cualquier número de llamadas a locals() devolverá el mismo diccionario, pero cada llamada a locals() lo actualizará con los valores actuales de las variables locales. Esto puede dar la impresión de que la asignación a los elementos del diccionario se ignoran (originalmente escribí que este era el caso). Por lo tanto, las modificaciones a las claves existentes dentro del diccionario devueltas desde locals() solo duran hasta la siguiente llamada a locals() en el mismo ámbito.

En IronPython las cosas funcionan un poco diferente. Cualquier función que llame a locals() dentro de ella usa un diccionario para sus variables locales, por lo que las asignaciones a variables locales cambian el diccionario y las asignaciones al diccionario cambian las variables PERO eso es solo si llama explícitamente locals() bajo ese nombre. Si enlaza un nombre diferente a la función locals en IronPython, llamarla le da las variables locales para el ámbito donde se enlazó el nombre y no hay forma de acceder a los locales de la función a través de it:

>>> def foo():
...     abc = 123
...     lcl = zzz()
...     lcl['abc'] = 456
...     deF = 789
...     print(abc)
...     print(zzz())
...     print(lcl)
...
>>> zzz =locals
>>> foo()
123
{'__doc__': None, '__builtins__': <module '__builtin__' (built-in)>, 'zzz': <built-in function locals>, 'foo': <function foo at 0x000000000000002B>, '__name__': '__main__', 'abc': 456}
{'__doc__': None, '__builtins__': <module '__builtin__' (built-in)>, 'zzz': <built-in function locals>, 'foo': <function foo at 0x000000000000002B>, '__name__': '__main__', 'abc': 456}
>>>

Todo esto podría cambiar en cualquier momento. Lo único garantizado es que no se puede depender de los resultados de asignación al diccionario devueltos por locals().

 65
Author: Duncan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-09-13 19:31:09

Otros han sugerido asignar a locals(). Esto no funcionará dentro de una función, donde se accede a los locales usando el opcode LOAD_FAST, a menos que tenga una instrucción exec en algún lugar de la función. Para soportar esta instrucción, que podría crear nuevas variables que no se conocen en tiempo de compilación, Python se ve obligado a acceder a las variables locales por nombre dentro de la función, por lo que escribir en locals() funciona. El exec puede estar fuera de la ruta de código que se ejecuta.

def func(varname):
    locals()[varname] = 42
    return answer           # only works if we passed in "answer" for varname
    exec ""                 # never executed

func("answer")
>>> 42

Nota: Esto solo funciona en Python 2.x. Acabaron con esta tontería en Python 3, y otras implementaciones(Jython, IronPython, etc.) no puede apoyarlo tampoco.

Esta es una mala idea, sin embargo. ¿Cómo accederás a las variables si no sabes su nombre? Por locals()[xxx], probablemente. Entonces, ¿por qué no usar su propio diccionario en lugar de contaminar locals() (y arriesgarse a sobrescribir una variable que su función realmente necesita)?

 23
Author: kindall,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-11-18 15:45:33

(Solo una nota rápida para otros googlin')

Ok, modificando locals() no es el camino a seguir (mientras se modifica globals() se supone que funciona ). Mientras tanto, exec podría ser, pero es dolorosamente lento, así que, como con las expresiones regulares, podemos querer compile() primero:

# var0 = 0; var1 = 1; var2 = 2
code_text = '\n'.join( "var%d = %d" % (n, n) for n in xrange(3) )

filename = ''
code_chunk = compile( code_text, filename, 'exec' )

# now later we can use exec:
exec code_chunk # executes in the current context
 4
Author: ジョージ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:47:19

He pasado el último... un par de horas, supongo, tratando de hackear la falta de cierres de funciones, y se me ocurrió esto, lo que podría ayudar:

common_data = ...stuff...
def process(record):
    ...logic...

def op():
    for fing in op.func_dict: # Key line number 1
        exec(fing + " = op.func_dict[fing]") # Key line number 2

    while common_data.still_recieving:
        with common_data._lock:
            if common_data.record_available:
                process(common_data.oldest_record)
        time.sleep(1.0)

op.func_dict.update(locals()) # Key line number 3
threading.Thread(target = op).start()

...

Es un ejemplo bastante pesado / artificial, pero si hay muchos locales o todavía estás en el proceso de prototipado, este patrón se vuelve útil. En su mayoría, estaba amargado por que todos los almacenes de datos se replicaran o movieran para manejar delegados de devolución de llamada, etc.

 3
Author: Redsplinter,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-10-13 03:19:16

Puedes modificar locals() directamente:

locals()['foo'] = 'bar'

Pero una mejor manera sería tener algún dict que contenga todos los nombres de variables dinámicas como claves de diccionario:

d = {}
for some in thing:
    d[some] = 'whatever'
 2
Author: poke,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-11-06 18:27:09

Puede usar un diccionario local y poner todos los enlaces dinámicos como elementos en el diccionario. Luego, conociendo el nombre de una "variable dinámica", puede usar el nombre como clave para obtener su valor.

 0
Author: Adam Zalcman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-11-06 19:10:59

Digamos que tenemos el siguiente diccionario:

DictionaryA = {'No Rating': ['Hobbit', 'Movie C', 'Movie G'],
               'Forget It': ['Avenger', 'Movie B'], 
               'Must See': ['Children of Men', 'Skyfall', 'Movie F'], 
               '3': ['X-Men', 'Movie D'],
               '2': ['Captain America', 'Movie E'], 
               '4': ['Transformers', 'Movie A']} 

Quiero crear nuevos diccionarios de la siguiente manera:

NewDictionary1 = {'No Rating': ['Hobbit', 'Movie C', 'Movie G']} 
NewDictionary2 = {'Forget It': ['Avenger', 'Movie B']} 
NewDictionary3 = {'Must See': ['Children of Men', 'Skyfall', 'Movie F']}

Una sola frase:

dics = [{k:v} for k,v in DictionaryA.iteritems()]

Sería enviado a:

[{'Must See': ['Children of Men', 'Skyfall', 'Movie F']}, {'Forget It': ['Avenger', 'Movie B']}, {'No Rating': ['Hobbit', 'Movie C', 'Movie G']}, {'3': ['X-Men', 'Movie D']}, {'2': ['Captain America', 'Movie E']}, {'4': ['Transformers', 'Movie A']}]

Pero para declarar con precisión las variables podríamos ir con:

>>> i=0
>>> lcl = locals()
>>> for key,val in DictionaryA.iteritems():
        lcl["Dict" + str(i)] = {key:val}
        i += 1

Como se puede ver las primeras 3 Dict variables:

>>> Dict0
{'Must See': ['Children of Men', 'Skyfall', 'Movie F']}
>>> Dict1
{'Forget It': ['Avenger', 'Movie B']}
>>> Dict2
{'No Rating': ['Hobbit', 'Movie C', 'Movie G']}

Como han mencionado otros, si quieres ponerlo en una función debes añadirlo a globals():

>>> glb = globals()
>>> for key,val in DictionaryA.iteritems():
        glb["Dict" + str(i)] = {key:val}
        i += 1
 0
Author: 0x90,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-06-07 13:45:43