web-dev-qa-db-fra.com

Comportement de la fonction exec dans Python 2 et Python 3

Le code suivant donne une sortie différente dans Python2 Et dans 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 Lie la variable b à l'intérieur de la fonction execute aux valeurs de la chaîne de la fonction exec, alors que Python3 Ne fonctionne pas fais pas ça? Comment puis-je obtenir le comportement de Python2 Dans Python3? J'ai déjà essayé de passer des dictionnaires pour les globaux et les locaux à la fonction exec dans Python3, Mais rien n'a fonctionné jusqu'à présent.

--- MODIFIER ---

Après avoir lu la réponse de Martijns, j'ai analysé cela avec Python3. Dans l'exemple suivant, je donne la dictionnaire locals() en tant que d à exec, mais d['b'] Imprime autre chose que la simple impression de 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 des identifiants de d et locals() montre qu'ils sont le même objet. Mais dans ces conditions, b devrait être identique à d['b']. Qu'est-ce qui ne va pas dans mon exemple?

36
Holger

Il y a une grande différence entre exec in Python 2 et exec() in Python 3. Vous traitez exec en tant que fonction, mais c'est vraiment une instruction in Python 2.

En raison de cette différence, vous ne pouvez pas modifier les variables locales dans la portée de la fonction dans Python 3 en utilisant exec, même si cela était possible dans Python 2 Pas même des variables précédemment déclarées.

locals() ne reflète que les variables locales dans une direction. Les éléments suivants n'ont jamais fonctionné en 2 ou 3:

def foo():
    a = 'spam'
    locals()['a'] = 'ham'
    print(a)              # prints 'spam'

Dans Python 2, l'utilisation de l'instruction exec signifiait que le compilateur savait désactiver les optimisations de portée locale (en passant de LOAD_FAST À LOAD_NAME Pour exemple, pour rechercher des variables dans les étendues locale et globale.) Avec exec() étant une fonction, cette option n'est plus disponible et les étendues de fonction sont désormais toujours optimisées.

De plus, dans Python 2, l'instruction exec copie explicitement toutes les variables trouvées dans locals() vers les fonctions locales en utilisant PyFrame_LocalsToFast, Mais uniquement si aucun paramètre globals et locals n'a été fourni.

La solution de contournement appropriée consiste à utiliser un nouvel espace de noms (un dictionnaire) pour votre appel exec():

def execute(a, st):
    namespace = {}
    exec("b = {}\nprint('b:', b)".format(st), namespace)
    print(namespace['b'])

La documentation exec() est très explicite sur cette limitation:

Remarque: La valeur par défaut locaux agit comme décrit pour la fonction locals() ci-dessous: modifications de la valeur par défaut locals dictionnaire ne doit pas être tenté. Passez un dictionnaire explicite locals si vous avez besoin de voir les effets du code sur les locaux après le retour de la fonction exec().

41
Martijn Pieters

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'

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

les sorties

{'l': {...}, 'a': 2}
{'l': {...}, 'a': 1}

Et aussi

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

les sorties

{'l': {...}, 'a': 2}
{'l': {...}, 'a': 2}
{'l': {...}, 'a': 2}
{'l': {...}}

Apparemment, l'action de exec sur les locaux est la suivante:

  • Si une variable est définie dans exec et que cette variable était une variable locale, alors exec modifie le dictionnaire interne (celui renvoyé par locals()) et ne le renvoie pas à son état d'origine. Un appel à locals() met à jour le dictionnaire (comme indiqué dans la section 2 de la documentation python), et la valeur définie dans exec est oubliée. Le besoin de appeler locals() pour mettre à jour le dictionnaire n'est pas un bug de python3, car il est documenté, mais il n'est pas intuitif. De plus, le fait que les modifications des locaux dans exec ne changent pas le les sections locales de la fonction sont une différence documentée avec python2 (la documentation dit "Passez un dictionnaire de sections locales explicite si vous devez voir les effets du code sur les sections locales après le retour de la fonction exec ()"), et je préfère le comportement de python2.
  • Si une variable est définie dans exec et que cette variable n'existait pas auparavant, exec modifie le dictionnaire interne, sauf si la variable est définie par la suite. Il semble qu'il y ait un bug dans la façon dont locals() met à jour le dictionnaire; ce bogue donne accès à la valeur définie dans exec en appelant locals() après exec.
5
LRGH

Résumer:

  • Il n'y a pas de bogue dans Python 2 ni dans Python 3
  • Le comportement différent de exec découle du fait que exec est une instruction dans Python 2, alors qu'il est devenu une fonction dans Python 3.

Notez s'il vous plaît:

Je ne dis rien de nouveau ici. Ceci est juste une Assemblée de la vérité trouvée dans toutes les autres réponses et commentaires. Tout ce que j'essaie ici, c'est de mettre en lumière certains des détails les plus 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 englobante) dans Python 2 (car il s'agit d'une instruction et peut accéder à la portée locale actuelle) et ne peut plus le faire dans Python 3 (car il s'agit désormais d'une fonction , s'exécute donc dans sa propre portée locale).

L'irritation, cependant, n'a rien à voir avec l'instruction exec, elle ne provient que d'un détail de comportement spécial:

locals() renvoie quelque chose, que je veux appeler "un singleton mutable au niveau de la portée qui, après l'appel à locals(), ne fait toujours référence qu'à 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 ainsi que le changement de fonctionnement de exec ressemble à être erratique, mais ce n'est pas le cas, car cela expose simplement certains détails, qui ont toujours été là.

Que signifie "un singleton mutable de portée qui fait référence à des variables de portée locale"?

  • Il s'agit d'un scope-wise singleton, Car quelle que soit la fréquence à laquelle vous appelez locals() dans la même étendue, l'objet renvoyé est toujours le même.
    • D'où l'observation, que id(d) == id(locals()), parce que d et locals() font référence au même objet, au même singleton, car il ne peut y en avoir qu'un (dans une portée différente vous obtenir un objet différent, mais dans la même portée, vous ne voyez que celui-ci).
  • C'est mutable, car c'est un objet normal, vous pouvez donc le modifier.
    • locals() force toutes les entrées de l'objet à référencer à nouveau les variables dans la portée locale.
    • Si vous modifiez quelque chose dans l'objet (via d), cela modifie l'objet, car il s'agit d'un objet mutable normal.
  • Ces modifications du singleton ne se propagent pas dans la portée locale, car toutes les entrées de l'objet sont references to the variables in the local scope. Donc, si vous modifiez des entrées, cela modifie l'objet singleton, et non le contenu de l'endroit où "les références pointées avant de modifier 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 que si vous affectez quelque chose à une entrée, vous ne modifiez pas l'objet vers lequel pointe l'entrée, vous introduisez un nouvel objet et attribuez une référence à celui-ci à l'entrée. Exemple:

      a = 1
      d = locals()
      d['a'] = 300
      # d['a']==300
      locals()
      # d['a']==1
      

    Outre l'optimisation, cela permet:

    • Créez un nouvel objet Number (1) - qui est un autre singleton, BTW.
    • stocker le pointeur vers ce numéro (1) dans LOCALS['a']
      (où LOCALS doit être la portée locale interne)
    • S'il n'existe pas déjà, créez un objet SINGLETON
    • met à jour SINGLETON, donc il fait référence à toutes les entrées dans LOCALS
    • stocker le pointeur de SINGLETON dans LOCALS['d']
    • Créer un nombre (300), qui est pas un singleton, BTW.
    • stocker le pointeur vers ces numéros (300) dans d['a']
    • par conséquent, le SINGLETON est également mis à jour.
    • mais LOCALS n'est pas pas mis à jour, donc la variable locale a ou LOCALS['a'] est toujours Number ( 1)
    • Maintenant, locals() est de nouveau appelée, SINGLETON est mis à jour.
    • Comme d fait référence à SINGLETON, et non LOCALS, d change aussi!

Pour en savoir plus sur ce détail surprenant, pourquoi 1 Est un singleton alors que 300 Ne l'est pas, voir https://stackoverflow.com/a/30635

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 comportement exec de Python 2 à Python 3 (sauf en changeant votre code), car il n'y a aucun moyen de modifier les variables locales en dehors du flux du programme.

Cependant, vous pouvez apporter le comportement de Python 3 à Python 2, de sorte que vous pouvez aujourd'hui écrire des programmes qui s'exécutent de la même manière, qu'ils exécuter avec Python 3 ou Python 2. C'est parce que dans (plus récent) Python 2 vous pouvez utiliser exec avec également des arguments de fonction (en fait, c'est un 2 ou 3-Tuple), avec permet d'utiliser la même syntaxe avec la même sémantique connue de Python 3:

exec "code"

(qui ne fonctionne que dans Python 2) devient (qui fonctionne pour Python 2 et 3):

exec("code", globals(), locals())

Mais attention, "code" Ne peut plus altérer la portée de l'enveloppe locale de cette façon. Voir aussi https://docs.python.org/2/reference/simple_stmts.html#exec

Quelques derniers mots:

Le changement de exec dans Python 3 est bon. En raison de l'optimisation.

Dans Python 2, vous n'avez pas pu optimiser sur exec, car l'état de toutes les variables locales qui contenaient un contenu immuable pouvait changer de façon imprévisible. Cela ne peut plus se produire. Maintenant, les règles habituelles des invocations de fonctions s'appliquent aussi à exec() comme à toutes les autres fonctions.

3
Tino

Je crains de ne pas pouvoir l'expliquer exactement, mais cela vient essentiellement du fait que b à l'intérieur de la fonction est local, et que exec() semble être assigné au b global. Vous devrez déclarer b comme global à l'intérieur de la fonction, et à l'intérieur de l'instruction exec.

Essaye ça:

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 capté. Dans la fonction, vous imprimez le local b.

Notez que j'aurais pensé que exec() utilise toujours le b global en premier, de sorte que dans execute2(), vous n'avez pas besoin de le déclarer dans la fonction exec() . Mais je trouve que cela ne fonctionne pas (c'est la partie que je ne peux pas expliquer exactement).

1
user707650