web-dev-qa-db-fra.com

Définir dynamiquement la variable locale

Comment définir dynamiquement une variable locale en Python?

(où le nom de la variable est dynamique)

UPDATE: Je suis conscient que ce n'est pas une bonne pratique, et les remarques sont légitimes, mais cela n'en fait pas une mauvaise question, mais une question plus théorique - je ne vois pas pourquoi cela justifie les votes négatifs.

88
Jonathan

Contrairement aux autres réponses déjà publiées, vous ne pouvez pas modifier locals() directement et vous attendre à ce que cela fonctionne.

>>> 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

La modification de locals() n’est pas définie. En dehors d'une fonction, lorsque locals() et globals() sont identiques, cela fonctionnera; dans une fonction, généralement ne fonctionnera pas.

Utilisez un dictionnaire ou définissez un attribut sur un objet:

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

ou si vous préférez, utilisez une classe:

class C: pass

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

Éditer : L’accès aux variables dans les espaces de noms qui ne sont pas des fonctions (modules, définitions de classe, instances) se fait généralement par recherche dans le dictionnaire (comme le souligne Sven dans les commentaires, il définir __slots__). Les fonctions locales peuvent être optimisées pour la vitesse, car le compilateur connaît (généralement) tous les noms à l'avance. Il n'y a donc pas de dictionnaire tant que vous n'avez pas appelé locals().

Dans l'implémentation C de Python, locals() (appelé depuis une fonction) crée un dictionnaire ordinaire initialisé à partir des valeurs actuelles des variables locales. Dans chaque fonction, un nombre quelconque d'appels à locals() renverra le même dictionnaire, mais chaque appel à locals() le mettra à jour avec les valeurs actuelles des variables locales. Cela peut donner l’impression que les assignations aux éléments du dictionnaire sont ignorées (j’avais écrit à l’origine que c’était le cas). Les modifications apportées aux clés existantes dans le dictionnaire sont renvoyées à partir de locals() et ne durent donc que jusqu'au prochain appel à locals() dans la même étendue.

Dans IronPython, les choses fonctionnent un peu différemment. Toute fonction qui appelle locals() utilise un dictionnaire pour ses variables locales. Les assignations aux variables locales changent le dictionnaire et les assignations au dictionnaire changent les variablesMAISuniquement si vous appelez explicitement locals() sous ce nom. Si vous liez un nom différent à la fonction locals dans IronPython, son appel vous donnera les variables locales pour l'étendue où le nom était lié et il n'y a aucun moyen d'accéder aux variables locales de la fonction via celle-ci:

>>> 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}
>>>

Cela pourrait tout changer à tout moment. La seule chose garantie est que vous ne pouvez pas dépendre des résultats de l'affectation au dictionnaire renvoyé par locals().

67
Duncan

D'autres ont suggéré d'attribuer à locals(). Cela ne fonctionnera pas dans une fonction, où les sections locales sont accessibles à l'aide de l'opcode LOAD_FAST, sauf si vous avez une instruction exec quelque part dans la fonction. Pour prendre en charge cette instruction, qui pourrait créer de nouvelles variables inconnues au moment de la compilation, Python est alors obligé d'accéder aux variables locales par leur nom dans la fonction. L'écriture dans locals() fonctionne donc. La exec peut être en dehors du chemin de code qui est exécuté.

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

func("answer")
>>> 42

Remarque: cela ne fonctionne que dans Python 2.x. Ils ont supprimé cette folie de Python 3 et d’autres implémentations (Jython, IronPython, etc.) pourraient ne pas le supporter non plus.

C'est une mauvaise idée, cependant. Comment allez-vous accéder aux variables si vous ne connaissez pas leur nom? Par locals()[xxx] probablement. Alors, pourquoi ne pas simplement utiliser votre propre dictionnaire plutôt que de polluer locals() (et risquer ainsi d’écraser une variable dont votre fonction a réellement besoin)?

23
kindall

(Juste une note rapide pour les autres googlin ')

Ok, donc modifier locals()n'est pas la solution (lors de la modification de globals()est supposé fonctionner ). Entre-temps, execpourrait être, mais c'est péniblement lent. Par conséquent, comme pour les expressions régulières, il peut être utile de compile() d'abord:

# 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
5
ジョージ

Je pense que j'ai passé ... les dernières heures à essayer de corriger le manque de fonctions fermées, et j'ai proposé ceci, ce qui pourrait aider:

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()

...

C'est un exemple assez lourd/artificiel, mais s'il y a beaucoup d'habitants ou si vous êtes encore en train de prototyper, ce modèle devient utile. La plupart du temps, j'étais simplement amer à propos de tous les magasins de données répliqués ou déplacés afin de gérer les délégués de rappel, etc.

3
Redsplinter

Vous pouvez modifier locals() directement:

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

</ s> Mais une meilleure solution consisterait à créer un dict contenant tous les noms de variables dynamiques sous forme de clés de dictionnaire:

d = {}
for some in thing:
    d[some] = 'whatever'
2
poke

Disons que nous avons le dictionnaire ci-dessous:

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']} 

Je veux créer de nouveaux dictionnaires comme ci-dessous:

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

un oneliner:

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

serait envoyé à:

[{'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']}]

Mais pour déclarer précisément des variables, nous pourrions aller avec:

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

Comme on peut le voir, les 3 premières variables Dict:

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

Comme mentionné par d'autres, si vous voulez le mettre dans une fonction, vous devez l'ajouter à la globals():

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

Vous pouvez utiliser un dictionnaire local et placer toutes les liaisons dynamiques en tant qu'éléments dans le dictionnaire. Puis, connaissant le nom d’une telle "variable dynamique", vous pouvez utiliser le nom comme clé pour obtenir sa valeur.

0
Adam Zalcman