web-dev-qa-db-fra.com

Pourquoi exec ne fonctionne-t-il pas dans une fonction avec une sous-fonction?

On dirait que vous ne pouvez pas utiliser exec dans une fonction qui a une sous-fonction ...

Quelqu'un sait pourquoi ce code Python ne fonctionne pas? Je reçois une erreur à l'exec dans test2. De plus, je sais que le style de l'exécutif n'est pas bon, mais croyez-moi, je l'utilise pour une raison appropriée. Je ne l'utiliserais pas autrement.

#!/usr/bin/env python
#

def test1():
    exec('print "hi from test1"')

test1()

def test2():
    """Test with a subfunction."""
    exec('print "hi from test2"')
    def subfunction():
        return True

test2()

EDIT: J'ai réduit le bogue à une fonction dans une sous-fonction. Cela n'a rien à voir avec le mot clé de relance.

52
anon

Correct. Vous ne pouvez pas utiliser exec dans une fonction comportant une sous-fonction, sauf si vous spécifiez un contexte. De la docs:

Si exec est utilisé dans une fonction et le fonction contient un bloc imbriqué avec variables libres, le compilateur aura déclenche une SyntaxError sauf si exec spécifie explicitement le local espace de noms pour l'exec. (En d'autres termes, "exec obj" serait illégal, Mais "exec obj in ns" serait légal.)

Il y a une bonne raison à cela que je comprendrais probablement si ce n'était pas dimanche soir… .. Maintenant, question suivante: Pourquoi utilisez-vous exec? C'est très rarement nécessaire. Vous dites que vous avez une bonne raison. Je me sens sceptique à ce sujet. ;) Si vous avez une bonne raison, je vous expliquerai la solution de contournement. :-P

Eh bien, la voici quand même:

def test2():
    """Test with a subfunction."""
    exec 'print "hi from test2"' in globals(), locals()
    def subfunction():
        return True
64
Lennart Regebro

Bien qu'en Python, les variables locales soient stockées dans un dictionnaire locals(), elles ne le sont généralement pas. Au lieu de cela, ils sont principalement stockés sur la pile et accessibles par index. Cela rend la recherche de variable locale plus rapide que si elle devait faire une recherche par dictionnaire à chaque fois. Si vous utilisez la fonction locals(), vous obtenez un nouveau dictionnaire créé à partir de toutes les variables locales. C'est pourquoi l'assignation à locals() ne fonctionne généralement pas.

Il y a quelques exceptions à ce scénario:

Lorsque vous utilisez une variable exec non qualifiée dans une fonction, Python désactive l'optimisation et utilise un dictionnaire réel pour les variables locales. Cela signifie que vous pouvez créer ou mettre à jour des variables à partir de la exec, mais cela signifie également que tous les accès aux variables locales dans cette fonction s'exécuteront plus lentement.

L'autre exception est que lorsque vous imbriquez des fonctions, la fonction interne peut accéder aux variables locales dans l'étendue de la fonction externe. Dans ce cas, la variable est stockée dans un objet 'cellule' au lieu d'être stockée dans la pile. Le niveau supplémentaire d'indirection ralentit l'utilisation des variables étendues, que vous y accédiez depuis la fonction interne ou externe.

Le problème que vous avez rencontré est que ces deux exceptions à la manière dont les variables locales sont normalement stockées sont incompatibles. Vous ne pouvez pas avoir une variable stockée dans un dictionnaire et accessible simultanément via une référence de cellule. Python 2.x résout ce problème en interdisant l'exéc, même dans les cas où vous n'essayez pas d'utiliser des variables étendues.

28
Duncan

C'est un cas plutôt intéressant:

>>> def func():
...     exec('print "hi from func"')
...     def subfunction():
...         return True
... 
  File "<stdin>", line 2
SyntaxError: unqualified exec is not allowed in function 'func' because 
it contains a nested function with free variables

La raison pour laquelle cela ne fonctionne pas est que subfunction contient une variable libre et que, dans Python 2, exec pourrait théoriquement modifier les variables locales de la portée contenant, il serait impossible de décider si la variable doit être liée à la variable globale ou la portée de la fonction parent. Un des vers dans le zen de Python est "Face à l'ambiguïté, refusez la tentation de deviner." et c'est ce que fait Python 2.

Maintenant la question est: quelle est cette variable libre (non liée)? Eh bien, c'est True

En effet, il est reproductible avec None:

>>> def func():
...     exec('print "hi from func"')
...     def subfunction():
...         return None
... 
  File "<stdin>", line 2
SyntaxError: unqualified exec is not allowed in function 'test2' because it contains a nested
function with free variables

Même si None ne peut pas être affecté et qu'il est considéré comme une constante constante dans le bytecode, l'analyseur de buggy pense que c'est une variable non liée ici.

Mais si vous le remplacez par 1 et que cela fonctionne sans problème:

>>> def test2():
...     exec('print "hi from func"')
...     def subfunction():
...         return 1
... 
>>>

Pour éviter cette erreur, spécifiez explicitement les globales et éventuellement les locales qui doivent être utilisées par exec, par exemple:

>>> def test2():
...     exec 'print "hi from test2"' in {}
...     def subfunction():
...         return None
...
>>>

En Python 3, exec est juste une fonction simple et n'est pas gérée spécialement par l'analyseur ou le compilateur de code intermédiaire. En Python 3, exec ne peut pas relier les noms locaux de fonctions, et donc cette syntaxe et cette ambiguïté n'existent pas.


Un cas particulier de compatibilité entre Python 2 et 3 est que, alors que la documentation Python 2.7 indique que

La forme exec(expr, globals) est équivalente à exec expr in globals, tandis que la forme exec(expr, globals, locals) est équivalente à exec expr in globals, locals. La forme Tuple de exec assure la compatibilité avec Python 3, où exec est une fonction plutôt qu'une instruction.

La forme Tuple n'a pas toujours été compatible à 100%, car il y avait un bogue dans le traitement de exec dans les fonctions avec des fonctions imbriquées (numéro 21591) ; Jusqu'à Python 2.7.8, le code suivant peut avoir levé une exception:

def func():
    exec('print "hi from test2"', {})
    def subfunction():
        return None

Ceci a été corrigé dans Python 2.7.9 et ne jette plus.

6
Antti Haapala

Cela fonctionne bien dans Python 3.1.3, après avoir modifié l’instruction print pour utiliser la fonction print.

En Python 2.6, il produit SyntaxError: unqualified exec is not allowed in function 'test2' it contains a nested function with free variables, je ne pense pas que ce soit un bogue.

4
Lie Ryan

L'erreur semble être assez évidente pour moi:

SyntaxError: unqualified exec is not allowed in function 'test2' it contains a nested function with free variables

Voir pep 227 pour plus d’informations: http://www.python.org/dev/peps/pep-0227/

1
Wolph

Les compréhensions dict et list peuvent également être considérées comme des sous-fonctions de Python 2.7.5

Par exemple, cela échoue sur Python 2.7.5, mais fonctionne sur Python 2.7.12:

def func():
    exec('print("a")')
    (str(e) for e in range(10))

avec:

  File "./a.py", line 4
    exec('print("a")')
SyntaxError: unqualified exec is not allowed in function 'func' it contains a nested function with free variables

Probablement, il a été compilé en interne pour une fonction dans le bytecode.

TODO trouver le correctif de fixation. C'était au-delà de mon git log --grep foo.

Analogue pour les compréhensions dict:

def func():
    exec('print("a")', {e:str(e) for e in range(10)})

ce qui est particulièrement mauvais puisqu'il s'agit d'un paramètre commun pour l'argument global.

Également soulevé à: https://github.com/sphinx-doc/sphinx/issues/5417#issuecomment-421731085