109 votes

Variable locale définie dynamiquement

Comment définir dynamiquement une variable locale en Python (où le nom de la variable est dynamique) ?

2 votes

Vous vous y prenez mal si le nom de la variable est dynamique.

17 votes

J'ai déjà programmé la lecture des noms d'espèces chimiques à partir d'un fichier d'entrée, puis la création d'objets pour ces noms d'espèces à partir de ce que j'ai lu dans le fichier texte. De cette façon, je peux dire H2O.mwt, quelque chose comme ça. Il pourrait y avoir une raison légitime de faire cela, IMHO.

1 votes

Je suis conscient que c'est une mauvaise pratique. Mais dans certaines conditions, cela peut être un sale raccourci. De plus, c'est intéressant, cela montre à quel point le langage peut être dynamique. C'est aussi une bonne chose que le feedback soit ici pour que les lecteurs ne l'utilisent pas facilement, mais ce n'est pas une raison valable pour un vote négatif.

82voto

Duncan Points 25356

Contrairement à d'autres réponses déjà postées, vous ne pouvez pas modifier locals() directement et s'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

Modifier locals() est indéfinie. En dehors d'une fonction, lorsque locals() y globals() sont identiques, cela fonctionnera ; à l'intérieur d'une fonction, cela fonctionnera généralement ne fonctionne 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)

Modifier : L'accès aux variables des espaces de noms qui ne sont pas des fonctions (donc les modules, les définitions de classes, les instances) se fait généralement par consultation de dictionnaires (comme le souligne Sven dans les commentaires, il y a des exceptions, par exemple les classes qui définissent __slots__ ). Les fonctions locales peuvent être optimisées en termes de vitesse car le compilateur connaît (généralement) tous les noms à l'avance, de sorte qu'il n'y a pas de dictionnaire jusqu'à ce que vous appeliez locals() .

Dans l'implémentation C de Python locals() (appelé à l'intérieur d'une fonction) crée un dictionnaire ordinaire initialisé à partir des valeurs courantes des variables locales. Dans chaque fonction, un nombre quelconque d'appels à locals() retournera 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 affectations aux éléments du dictionnaire sont ignorées (j'avais initialement écrit que c'était le cas). Les modifications des clés existantes dans le dictionnaire retourné par la fonction locals() ne durent donc que jusqu'au prochain appel à locals() dans le même périmètre.

Dans IronPython, les choses fonctionnent un peu différemment. Toute fonction qui appelle locals() à l'intérieur, il utilise un dictionnaire pour ses variables locales, donc les affectations aux variables locales modifient le dictionnaire et les affectations au dictionnaire modifient les variables. MAIS c'est seulement si vous appelez explicitement locals() sous ce nom. Si vous liez un nom différent à la locals dans IronPython, alors l'appeler vous donne les variables locales de la portée où le nom a été lié et il n'y a aucun moyen d'accéder aux locales de la fonction à travers elle :

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

Tout cela peut changer à tout moment. La seule chose garantie est que vous ne pouvez pas dépendre des résultats de l'assignation au dictionnaire retourné par locals() .

0 votes

Deux corrections mineures : 1. La valeur de retour de locals() est un dictionnaire standard dans CPython 2.x et 3.x, qui peut être modifié comme d'habitude (mais les changements se propagent à la portée locale). 2. L'accès aux espaces de noms de classes et d'instances n'implique pas toujours la consultation d'un dictionnaire. Il existe plusieurs exceptions, notamment les instances de classes qui définissent __slots__ .

0 votes

Merci @SvenMarnach, j'ai un peu mis à jour ma réponse sur votre deuxième point. Ce que j'ai écrit sur le premier point est suffisamment spécifique pour que je sois sûr d'avoir dû le tester, donc soit je me suis totalement embrouillé, soit cela varie peut-être selon la version de Python. Je vais vérifier à nouveau et mettre à jour la réponse dans un moment.

0 votes

@SvenMarnach, j'ai trouvé la solution : de multiples appels à locals() dans la même fonction renvoient le même dictionnaire et écrasent les clés existantes à chaque fois que vous l'appelez, donc parce que j'ai mélangé plusieurs appels à locals() en imprimant le dictionnaire qu'il retournait à l'origine, il me semblait qu'il ne changeait pas. J'ai expliqué ce piège possible dans la réponse. Merci.

29voto

kindall Points 60645

D'autres ont suggéré d'assigner à locals() . Cela ne fonctionnera pas à l'intérieur d'une fonction, où l'on accède aux locaux en utilisant la balise LOAD_FAST opcode, sauf si vous avez un exec quelque part dans la fonction. Pour prendre en charge cette déclaration, qui pourrait créer de nouvelles variables qui ne sont pas connues au moment de la compilation, Python est alors obligé d'accéder aux variables locales par leur nom dans la fonction, de sorte qu'en écrivant à locals() œuvres. Le site 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 bêtise dans Python 3, et d'autres implémentations (Jython, IronPython, etc.) peuvent 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 utiliser votre propre dictionnaire plutôt que de polluer locals() (et prendre le risque d'écraser une variable dont votre fonction a réellement besoin) ?

7voto

ジョージ Points 535

(Juste une note rapide pour les autres qui googlent)

Ok, donc modifier locals() n'est pas la voie à suivre ( en modifiant globals() est censé fonctionner ). En attendant, exec pourrait être mais c'est terriblement lent, donc, comme pour les expressions régulières, nous pouvons vouloir compile() le premier :

# 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

1 votes

... et pourquoi faire ça si on peut simplement utiliser un dictionnaire ?

1 votes

"Je suis conscient que ce n'est pas une bonne pratique, et que les remarques sont légitimes, mais cela n'en fait pas une mauvaise question, juste une question plus théorique" (q) TS -- il faut beaucoup de temps pour expliquer. et c'est une question différente. disons que cette réponse peut être ignorée dans la plupart des cas) ps. et n'est-il pas bon que si vous déciderez que vous en avez besoin, il sera là ?

3voto

Redsplinter Points 103

J'ai passé les dernières... heures, je suppose, à essayer de contourner le manque de fermetures de fonctions, et j'ai trouvé ceci, 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 et artificiel, mais s'il y a beaucoup de locaux ou si vous êtes encore en train de prototyper, ce modèle devient utile. J'étais surtout amer à propos de tous les magasins de données qui sont répliqués ou déplacés afin de gérer les délégués de rappel, etc.

0voto

Adam Zalcman Points 13198

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X