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 dexec, 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?

30
demandé sur Eric Leschinski 2013-02-26 13:57:18

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_FASTLOAD_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 fonction exec() retourne.

37
répondu Martijn Pieters 2017-12-12 13:45:12

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, puis exec modifie le dictionnaire interne (celle retournée par locals()) 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 dans exec est oublié. La nécessité de l'appel locals() 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 de exec 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, puis exec 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 dont locals() met à jour le dictionnaire ; ce bug donne accès à la valeur définie dans exec en appelant locals() après exec.
5
répondu LRGH 2015-05-20 10:14:15

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

1
répondu 2013-02-26 10:56:33

Pour résumer:

  • il n'y a pas de bug dans Python 2 ni dans Python 3
  • Le comportement différent de exec tiges de exec é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 appelez locals() dans la même portée, l'objet retourné est toujours le même.
    • d'Où l'observation, que id(d) == id(locals()), parce que d et locals() 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.)
  • 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 dans LOCALS
    • pointeur de magasin du SINGLETON en LOCALS['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 locale a ou LOCALS['a'] toujours est le Numéro(1)
    • Maintenant, locals() est appelé de nouveau, le SINGLETON est mis à jour.
    • d renvoie SINGLETON, pas LOCALS,d changements, trop!

pour en savoir plus sur ce détail surprenant, pourquoi 1 est un singleton, alors que 300 n'est pas, reportez-vous à https://stackoverflow.com/a/306353

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

1
répondu Tino 2017-11-23 18:12:58