Comportement de la fonction exec en Python 2 et Python 3
le code suivant donne une sortie différente en Python2
et Python3
:
from sys import version
print(version)
def execute(a, st):
b = 42
exec("b = {}nprint('b:', b)".format(st))
print(b)
a = 1.
execute(a, "1.E6*a")
Python2
imprime:
2.7.2 (default, Jun 12 2011, 15:08:59) [MSC v.1500 32 bit (Intel)]
('b:', 1000000.0)
1000000.0
Python3
imprime:
3.2.3 (default, Apr 11 2012, 07:15:24) [MSC v.1500 32 bit (Intel)]
b: 1000000.0
42
Pourquoi Python2
liez la variable b
à l'intérieur du execute
fonction aux valeurs de la chaîne de caractères de exec
fonction, tandis que Python3
ne pas le faire? Comment puis-je obtenir le comportement de Python2
<!--5? J'ai déjà essayé de passer des dictionnaires pour les globals et les locaux à exec
fonction Python3
, mais rien n'a fonctionné jusqu'à présent.
- - - EDIT - - -
Après la lecture de Martijns réponse que j'ai analysé ce Python3
. Dans l'exemple suivant je donne l' locals()
dictionay d
exec
, mais d['b']
imprime autre chose que l'impression b
.
from sys import version
print(version)
def execute(a, st):
b = 42
d = locals()
exec("b = {}nprint('b:', b)".format(st), globals(), d)
print(b) # This prints 42
print(d['b']) # This prints 1000000.0
print(id(d) == id(locals())) # This prints True
a = 1.
execute(a, "1.E6*a")
3.2.3 (default, Apr 11 2012, 07:15:24) [MSC v.1500 32 bit (Intel)]
b: 1000000.0
42
1000000.0
True
La comparaison de l'id de l' d
et locals()
montre qu'ils sont le même objet. Mais dans ces conditions b
doit être le même que d['b']
. Ce qui est faux dans mon exemple?
4 réponses
Il y a une grande différence entre exec
en Python 2 et exec()
en Python 3. Vous traitez exec
comme une fonction, mais c'est vraiment un déclaration en Python 2.
en raison de cette différence, vous ne pouvez pas changer les variables locales dans la portée de la fonction en Python 3 en utilisant exec
, même si c'était possible en Python 2. Pas même des variables déclarées précédemment.
locals()
reflète seulement les variables locales dans une direction. La suite de ne jamais ouvré en 2 ou 3:
def foo():
a = 'spam'
locals()['a'] = 'ham'
print(a) # prints 'spam'
en Python 2, en utilisant le exec
l'instruction signifiait que le compilateur savait désactiver les optimisations de portée locale (passant de LOAD_FAST
LOAD_NAME
par exemple, pour rechercher des variables dans les échelles locale et globale). exec()
étant une fonction, cette option n'est plus disponible et les portées de fonction sont maintenant toujours optimisé.
de plus, en Python 2, le exec
déclaration copie explicitement tous variables trouvées dans locals()
retour à la fonction locaux utilisant PyFrame_LocalsToFast
, mais seulement si aucun globals et habitants paramètres fournis.
la solution appropriée est d'utiliser un nouvel espace de noms (un dictionnaire) pour votre exec()
appel:
def execute(a, st):
namespace = {}
exec("b = {}\nprint('b:', b)".format(st), namespace)
print(namespace['b'])
exec()
documentation est très explicite sur cette limitation:
Remarque: par défaut habitants agir comme décrit pour la fonction
locals()
ci-dessous: modifications à la valeur par défaut habitants dictionnaire ne doit pas être tentée. Passer une explicite habitants dictionnaire si vous avez besoin de voir les effets de la code sur les habitants après la fonctionexec()
retourne.
je dirais que c'est un bug de python3.
def u():
exec("a=2")
print(locals()['a'])
u()
imprime "2".
def u():
exec("a=2")
a=2
print(a)
u()
imprime "2".
Mais
def u():
exec("a=2")
print(locals()['a'])
a=2
u()
échoue avec
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in u
KeyError: 'a'
--- modifier --- Autre comportement intéressant:
def u():
a=1
l=locals()
exec("a=2")
print(l)
u()
def u():
a=1
l=locals()
exec("a=2")
locals()
print(l)
u()
sorties
{'l': {...}, 'a': 2}
{'l': {...}, 'a': 1}
Et
def u():
l=locals()
exec("a=2")
print(l)
print(locals())
u()
def u():
l=locals()
exec("a=2")
print(l)
print(locals())
a=1
u()
sorties
{'l': {...}, 'a': 2}
{'l': {...}, 'a': 2}
{'l': {...}, 'a': 2}
{'l': {...}}
Apparemment, l'action de exec
sur les habitants est la suivante:
- si a la variable est définie dans
exec
et cette variable est une variable locale, puisexec
modifie le dictionnaire interne (celle retournée parlocals()
) et ne revient pas à son état d'origine. Un appel àlocals()
met à jour le dictionnaire (comme documenté dans la section 2 de la documentation python), et la valeur définie dansexec
est oublié. La nécessité de l'appellocals()
mettre à jour le dictionnaire n'est pas un bug de python3, car il est documenté, mais il n'est pas intuitif. En outre, le fait que modifications des locaux au sein deexec
ne pas modifier les habitants de la fonction est documentée différence avec python2 (la documentation dit "Pass explicite les habitants dictionnaire si vous avez besoin de voir les effets de la code sur les habitants après la fonction exec() renvoie"), et je préfère le comportement de python2. - Si une variable est définie dans
exec
et cette variable n'existe pas en avant, puisexec
modifie le dictionnaire interne à moins que la variable ne soit définie par la suite. Il semble que il y a un bug dans la façon dontlocals()
met à jour le dictionnaire ; ce bug donne accès à la valeur définie dansexec
en appelantlocals()
aprèsexec
.
je crains de ne pas pouvoir l'expliquer exactement, mais il vient essentiellement du fait que b à l'intérieur de la fonction est locale, et exec()
semble affecter au global B. Vous devrez déclarer que b est global à l'intérieur de la fonction, et à l'intérieur de la déclaration exec.
essaye ceci:
from sys import version
print(version)
def execute1(a, st):
b = 42
exec("b = {}\nprint('b:', b)".format(st))
print(b)
def execute2(a, st):
global b
b = 42
exec("global b; b = {}\nprint('b:', b)".format(st))
print(b)
a = 1.
execute1(a, "1.E6*a")
print()
execute2(a, "1.E6*a")
print()
b = 42
exec("b = {}\nprint('b:', b)".format('1.E6*a'))
print(b)
ce qui me donne
3.3.0 (default, Oct 5 2012, 11:34:49)
[GCC 4.4.5]
b: 1000000.0
42
b: 1000000.0
1000000.0
b: 1000000.0
1000000.0
vous pouvez voir qu'en dehors de la fonction, le b global est automatiquement détecté. L'intérieur de la fonction, vous avez l'impression de l' b local.
Notez que j'aurais pensé qu' exec()
utilise toujours le b global en premier, de sorte que dans execute2()
, vous n'avez pas besoin de le déclarer à l'intérieur de l' exec()
fonction. Mais je trouve que cela ne fonctionne pas (ce qui est la partie que je ne peux pas expliquer exactement).
Pour résumer:
- il n'y a pas de bug dans Python 2 ni dans Python 3
- Le comportement différent de
exec
tiges deexec
étant une instruction en Python 2, alors qu'elle est devenue une fonction en Python 3.
remarque:
Je ne dis rien de nouveau ici. C'est juste un assemblage de la vérité là-bas trouvé dans toutes les autres réponses et commentaires. Tout ce que j'essaie ici c'est d'apporter de la lumière à certains des plus détails obscurs.
la seule différence entre Python 2 et Python 3 est que, en effet,exec
est capable de changer la portée locale de la fonction d'enclosage en Python 2 (parce que c'est une instruction et peut accéder à la portée locale actuelle) et ne peut plus le faire en Python 3 (parce que c'est maintenant une fonction, donc tourne dans sa propre portée locale).
L'irritation, cependant, n'a rien à voir avec l' exec
déclaration, il ne découle d'un comportement spécial détails:
locals()
retourne quelque chose, que je veux appeler "un singleton mutable de portée qui, après l'appel à locals()
, renvoie toujours uniquement à toutes les variables de la portée locale".
Veuillez noter que le comportement de locals()
n'a pas changé entre Python 2 et 3. Donc, ce comportement avec le changement de comment exec
les travaux semblent être erratiques, mais ne l'est pas, car il expose juste quelques détails, qui a toujours été là.
Qu'est-ce qu'un "scope-wise" mutable singleton qui référence les variables dans la portée locale"?
- C'est un
scope-wise singleton
, peu importe combien de fois vous appelezlocals()
dans la même portée, l'objet retourné est toujours le même.- d'Où l'observation, que
id(d) == id(locals())
, parce qued
etlocals()
se référer au même objet, la même singleton, comme il peut y en avoir qu'un (dans un autre champ que vous obtenez un objet différent, mais dans le même champ d'application que vous ne verrez cette simple un.)
- d'Où l'observation, que
- C'est
mutable
, car c'est un objet normal, donc vous pouvez le modifier.locals()
force toutes les entrées de l'objet à renvoyer de nouveau aux variables de la portée locale.- si vous changez quelque chose dans l'objet (via
d
), cela modifie l'objet, comme il est normal mutable objet.
ces changements du singleton ne se propagent pas de nouveau dans la portée locale, parce que toutes les entrées dans l'objet
references to the variables in the local scope
. Donc, si vous modifiez les entrées, cela change l'objet singleton, et non le contenu de l'endroit où "les références pointées avant que vous changiez la référence" (donc vous ne modifiez pas la variable locale).en Python, les chaînes et les nombres ne sont pas modifiables. Cela signifie, Si vous assignez quelque chose à une entrée, vous ne changez pas l'objet où les points d'entrée à, vous introduisez un nouvel objet et assignez une référence à cela à l'entrée. Exemple:
a = 1 d = locals() d['a'] = 300 # d['a']==300 locals() # d['a']==1
en plus de l'optimisation:
- Créer un nouveau Numéro de l'objet(1) - qui est à quelques autres singleton, BTW.
- stocker le pointeur de ce nombre (1) dans
LOCALS['a']
(oùLOCALS
doit être la portée locale interne) - Si existe pas déjà, créez
SINGLETON
objet - mise à jour
SINGLETON
, donc il fait référence à toutes les entrées dansLOCALS
- pointeur de magasin du
SINGLETON
enLOCALS['d']
- Créer un Nombre(300), qui est un singleton, BTW.
- stocker le pointeur vers ce nombre(300) dans
d['a']
- d'où l'
SINGLETON
est mis à jour, aussi. - mais
LOCALS
mise à jour, donc, la variable localea
ouLOCALS['a']
toujours est le Numéro(1) - Maintenant,
locals()
est appelé de nouveau, leSINGLETON
est mis à jour. d
renvoieSINGLETON
, pasLOCALS
,d
changements, trop!
pour en savoir plus sur ce détail surprenant, pourquoi
1
est un singleton, alors que300
n'est pas, reportez-vous à https://stackoverflow.com/a/306353mais n'oubliez pas: les nombres sont immuables, donc si vous essayez de changer un nombre en une autre valeur, vous créez effectivement un autre objet.
Conclusion:
Vous ne pouvez pas ramener le exec
comportement de Python 2 à Python 3 (sauf en changeant votre code), car il n'y a plus de moyen de modifier les variables locales en dehors du flux du programme.
cependant, vous pouvez amener le comportement de Python 3 à Python 2, de sorte que vous pouvez, aujourd'hui, écrire des programmes qui fonctionnent de la même façon, peu importe s'ils fonctionnent avec Python 3 ou Python 2. C'est parce que dans (newer) Python 2 Vous pouvez utiliser exec
avec les arguments de type Fonction aussi bien( en fait, c'est un 2 - ou 3-tuple), avec permet de utilisez la même syntaxe avec la même sémantique que Python 3:
exec "code"
(qui ne fonctionne qu'en Python 2) devient (qui fonctionne pour Python 2 et 3):
exec("code", globals(), locals())
mais attention, cela "code"
ne peut plus modifier la portée locale de cette façon. Voir aussi https://docs.python.org/2/reference/simple_stmts.html#exec
Quelques derniers mots:
Le changement de exec
en Python 3 c'est bien. En raison de optimisation.
en Python 2 vous n'avez pas été en mesure d'optimiser à travers exec
, parce que l'état de toutes les variables locales qui contiennent des contenus immuables peut changer de façon imprévisible. Cela ne peut plus arriver. Maintenant, les règles habituelles des appels de fonction s'appliquent à exec()
comme pour toutes les autres fonctions, trop.