Comment exécuter une chaîne contenant du code Python en Python?

357

Comment exécuter une chaîne contenant du code Python en Python?

hekevintran
la source
5
Pour être juste, contrairement à l'autre question, celle-ci n'est pas un doublon. Et d'autres ont posé des questions beaucoup plus fondamentales que cela.
DNS
8
La bonne réponse, bien sûr, est presque toujours «non!».
bobince
23
@S. Lott: Vous n'avez pas lu la FAQ récemment? "Il est également parfaitement bien de poser et de répondre à votre propre question de programmation, mais faites comme si vous étiez sur Jeopardy: formulez-le sous la forme d'une question". Ce n'est pas une mauvaise question. +1
Devin Jeanpierre
49
@ S.Lott: Non. Vous n'en avez pas besoin. Si la question n'est pas déjà sur le site, alors c'est un jeu équitable (selon la FAQ, comme déjà indiqué). Répondez à chaque question comme si le PO avait besoin d'aide. Peut-être pas, mais le prochain qui lira leur question le fera probablement. Juste mes 2 cents.
Bill the Lizard
1
À tous ceux qui vous disent de ne pas le faire: j'ai un cas où j'en ai absolument besoin. Il s'agit d'un traitement distribué.
sudo

Réponses:

332

Pour les instructions, utilisez exec(string)(Python 2/3) ou exec string(Python 2):

>>> mycode = 'print "hello world"'
>>> exec(mycode)
Hello world

Lorsque vous avez besoin de la valeur d'une expression, utilisez eval(string):

>>> x = eval("2+2")
>>> x
4

Cependant, la première étape devrait être de vous demander si vous en avez vraiment besoin. L'exécution du code devrait généralement être la position de dernier recours: elle est lente, laide et dangereuse si elle peut contenir du code saisi par l'utilisateur. Vous devriez toujours regarder d'abord les alternatives, telles que les fonctions d'ordre supérieur, pour voir si celles-ci peuvent mieux répondre à vos besoins.

Brian
la source
1
mais qu'en est-il de la portée du code exécuté par 'exec'? est-il imbriqué?
jondinham
21
Un cas courant où quelqu'un veut utiliser 'exec' est quelque chose comme if s=='foo': x.foo = 42 elif s=='bar': x.bar = 42etc, dans lequel il peut ensuite écrire exec ("x.%s = 42" % s). Pour ce cas courant (où vous avez seulement besoin d'accéder à l'attribut d'un objet qui est stocké dans une chaîne), il existe une fonction beaucoup plus rapide, plus propre et plus sûre getattr: il suffit d'écrire getattr(x, s) = 42pour signifier la même chose.
ShreevatsaR
5
Comment exec est-il plus lent que l'interpréteur python?
Cris Stringfellow
6
@ShreevatsaR ne voulez-vous pas dire setattr(x, s, 42)? J'ai essayé getattr(x, 2) = 42et cela a échoué aveccan't assign to function call: <string>, line 1
Tanner Semerad
6
@Tanner: Hmm. Oui, en effet, setattr(x, s, 42)c'est la bonne syntaxe. Surpris, il a fallu si longtemps pour que cette erreur soit détectée. Quoi qu'il en soit, le fait est que getattret setattrsont une alternative au execmoment où tout ce que vous voulez est d'obtenir un membre arbitraire, recherché par chaîne.
ShreevatsaR
68

Dans l'exemple, une chaîne est exécutée en tant que code à l'aide de la fonction exec.

import sys
import StringIO

# create file-like string to capture output
codeOut = StringIO.StringIO()
codeErr = StringIO.StringIO()

code = """
def f(x):
    x = x + 1
    return x

print 'This is my output.'
"""

# capture output and errors
sys.stdout = codeOut
sys.stderr = codeErr

exec code

# restore stdout and stderr
sys.stdout = sys.__stdout__
sys.stderr = sys.__stderr__

print f(4)

s = codeErr.getvalue()

print "error:\n%s\n" % s

s = codeOut.getvalue()

print "output:\n%s" % s

codeOut.close()
codeErr.close()
hekevintran
la source
1
échanger stdout et stderr comme ça me rend très nerveux. il semble que cela puisse causer d'énormes problèmes de sécurité. y a-t-il un moyen de contourner cela?
Narcolapser
1
@Narcolapser, vous devriez être plus soucieux d'utiliser execdu tout (sauf si vous savez que la chaîne de code provient d'une source fiable).
bruno desthuilliers
27

evalet execsont la bonne solution, et ils peuvent être utilisés de manière plus sûre .

Comme expliqué dans le manuel de référence de Python et clairement expliqué dans ce tutoriel, les fonctions evalet execprennent deux paramètres supplémentaires qui permettent à un utilisateur de spécifier les fonctions et variables globales et locales disponibles.

Par exemple:

public_variable = 10

private_variable = 2

def public_function():
    return "public information"

def private_function():
    return "super sensitive information"

# make a list of safe functions
safe_list = ['public_variable', 'public_function']
safe_dict = dict([ (k, locals().get(k, None)) for k in safe_list ])
# add any needed builtins back in
safe_dict['len'] = len

>>> eval("public_variable+2", {"__builtins__" : None }, safe_dict)
12

>>> eval("private_variable+2", {"__builtins__" : None }, safe_dict)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 1, in <module>
NameError: name 'private_variable' is not defined

>>> exec("print \"'%s' has %i characters\" % (public_function(), len(public_function()))", {"__builtins__" : None}, safe_dict)
'public information' has 18 characters

>>> exec("print \"'%s' has %i characters\" % (private_function(), len(private_function()))", {"__builtins__" : None}, safe_dict)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<string>", line 1, in <module>
NameError: name 'private_function' is not defined

En substance, vous définissez l'espace de noms dans lequel le code sera exécuté.

alan
la source
9
Il n'est pas possible de rendre eval sûr: Eval est vraiment dangereux . Si vous prenez le code de moi et que vous l'évaluez, je peux créer un défaut de votre programme Python. Jeu terminé.
Ned Batchelder
3
@ v.oddou Je répondais à la déclaration d'alan, "eval et exec .. peuvent être utilisés de manière sûre." C'est faux. Si quelqu'un disait: «bash peut être utilisé de manière sûre», ce serait également faux. Bash est dangereux. C'est un danger nécessaire, mais dangereux néanmoins. Prétendre que l'eval peut être sécurisé est tout simplement faux.
Ned Batchelder
1
@NedBatchelder oui en effet. et le lien vers lequel vous pointez est un bon matériel. avec le pouvoir vient la responsabilité, il s'agit donc simplement d'être conscient du pouvoir potentiel de l'eval. et si nous décidons que puissance = danger.
v.oddou
3
@NedBatchelder De nombreux morceaux de code écrits en Python peuvent également être dangereux, mais pourquoi supposez-vous que evalou execsont destinés à être utilisés comme exec(input("Type what you want"))? Il existe de nombreux cas où un programme peut écrire une procédure ou une fonction à la suite d'un calcul; les fonctions résultantes seront aussi sûres et aussi rapides (une fois évaluées) que toute autre partie d'un bon programme bien écrit. Un programme dangereux contenant execn'est pas plus dangereux qu'un programme dangereux faisant lui-même les dégâts car execil ne donne aucun nouveau privilège au programme.
Thomas Baruchel
1
@ThomasBaruchel encore une fois, mon point est de contrer l'idée que eval ou exec peuvent être sécurisés. En particulier, cette réponse affirme que le contrôle des globaux et des locaux permettra de les utiliser en toute sécurité. C'est faux. Chaque fois que vous utilisez exec et eval, vous devez savoir précisément quel code est exécuté. Si vous ne le faites pas, alors vous êtes ouvert aux opérations dangereuses.
Ned Batchelder
21

N'oubliez pas que la version 3 execest une fonction!
utilisez donc toujours exec(mystring)au lieu de exec mystring.

bheks
la source
11

eval()est juste pour les expressions, tandis que eval('x+1')fonctionne, eval('x=1')ne fonctionnera pas par exemple. Dans ce cas, il vaut mieux l'utiliser exec, ou encore mieux: essayez de trouver une meilleure solution :)

LGB
la source
11

Éviter exec eteval

Utilisation de execeteval en Python est très mal vu.

Il existe de meilleures alternatives

De la réponse du haut (accent sur le mien):

Pour les instructions, utilisez exec .

Lorsque vous avez besoin de la valeur d'une expression, utilisez eval .

Cependant, la première étape devrait être de vous demander si vous en avez vraiment besoin. L'exécution du code devrait généralement être la position de dernier recours : elle est lente, laide et dangereuse si elle peut contenir du code saisi par l'utilisateur. Vous devriez toujours regarder d' abord les alternatives, telles que les fonctions d'ordre supérieur , pour voir si celles-ci peuvent mieux répondre à vos besoins.

Des alternatives à exec / eval?

définir et obtenir des valeurs de variables avec les noms dans les chaînes

[tandis que eval ] fonctionnerait, il n'est généralement pas conseillé d'utiliser des noms de variables ayant une signification pour le programme lui-même.

Au lieu de cela, mieux vaut utiliser un dict.

Ce n'est pas idiomatique

De http://lucumr.pocoo.org/2011/2/1/exec-in-python/ (accent sur le mien)

Python n'est pas PHP

N'essayez pas de contourner les idiomes Python car un autre langage le fait différemment. Les espaces de noms sont en Python pour une raison et juste parce qu'il vous donne l'outil, execcela ne signifie pas que vous devez utiliser cet outil.

C'est dangereux

Sur http://nedbatchelder.com/blog/201206/eval_really_is_dangerous.html (c'est moi qui souligne)

Donc eval n'est pas sûr, même si vous supprimez tous les globaux et les builtins!

Le problème avec toutes ces tentatives pour protéger eval () est qu'il s'agit de listes noires . Ils suppriment explicitement les choses qui pourraient être dangereuses. C'est une bataille perdue car s'il n'y a qu'un seul élément à gauche de la liste, vous pouvez attaquer le système .

Alors, eval peut-il être sécurisé? Dur à dire. À ce stade, ma meilleure supposition est que vous ne pouvez pas faire de mal si vous ne pouvez pas utiliser de soulignements doubles, alors peut-être que si vous excluez une chaîne avec des soulignements doubles, vous êtes en sécurité. Peut être...

C'est difficile à lire et à comprendre

De http://stupidpythonideas.blogspot.it/2013/05/why-evalexec-is-bad.html (c'est moi qui souligne):

Premièrement, execil est plus difficile pour les êtres humains de lire votre code . Afin de comprendre ce qui se passe, je n'ai pas seulement à lire votre code, je dois lire votre code, comprendre quelle chaîne il va générer, puis lire ce code virtuel. Donc, si vous travaillez en équipe, que vous publiez un logiciel open source ou que vous demandez de l'aide quelque part comme StackOverflow, il est plus difficile pour les autres de vous aider. Et s'il y a une chance que vous déboguiez ou développiez ce code dans 6 mois, vous vous compliquez la tâche directement.

Caridorc
la source
"ma meilleure supposition est que vous ne pouvez pas faire de mal si vous ne pouvez pas utiliser de doubles soulignés" - Vous pouvez construire une chaîne contenant des doubles soulignés, puis appeler valider cette chaîne.
Stanley Bak
de bons conseils, à moins que vous n'écriviez un générateur de code, un gestionnaire de travaux ou similaire ... ce que la plupart des gens font probablement ici.
Erik Aronesty
Je dois importer un chemin relatif à partir d'un fichier de configuration ( cfg.yaml):, reldir : ../my/dir/ et reldir = cfg[reldir]. Cependant, comme ce code python doit fonctionner à la fois sur Windows et Linux, j'en ai besoin pour m'adapter aux différents diviseurs de chemin des systèmes d'exploitation; soit \\ ou /. J'utilise donc reldir : os.path.join('..','my','dir')dans le fichier de configuration. Mais cela ne fait reldirqu'être cette chaîne littérale, non évaluée, donc je ne peux pas ouvrir un fichier dans reldir. Avez-vous une suggestion?
Marie. P.
9

Vous effectuez l'exécution de code à l'aide de exec, comme avec la session IDLE suivante:

>>> kw = {}
>>> exec( "ret = 4" ) in kw
>>> kw['ret']

4
gus
la source
1
Cela ne fonctionne pas en python normal. Du moins pas en python 3.
Thomas Ahle
6

Comme les autres l'ont mentionné, c'est "exec" ..

mais, si votre code contient des variables, vous pouvez utiliser "global" pour y accéder, également pour empêcher le compilateur de générer l'erreur suivante:

NameError: le nom 'p_variable' n'est pas défini

exec('p_variable = [1,2,3,4]')
global p_variable
print(p_variable)
Ghanem
la source
5

Utilisez eval .

Pablo Santa Cruz
la source
7
Eval () n'exécute pas d'instructions.
RickyA
4

Il convient de mentionner que ce execfrère existe aussi bien appeléexecfile si vous voulez appeler un fichier python. C'est parfois bon si vous travaillez dans un package tiers qui contient de terribles IDE et que vous souhaitez coder en dehors de leur package.

Exemple:

execfile('/path/to/source.py)'

ou:

exec(open("/path/to/source.py").read())

user1767754
la source
3

Découvrez eval :

x = 1
print eval('x+1')
->2
ryeguy
la source
10
Eval () n'exécute pas d'instructions.
RickyA
1

J'ai essayé pas mal de choses, mais la seule chose qui fonctionnait était la suivante:

temp_dict = {}
exec("temp_dict['val'] = 10") 
print(temp_dict['val'])

production:

10

paul-shuvo
la source
0

La solution la plus logique serait d'utiliser la fonction intégrée eval () . Une autre solution consiste à écrire cette chaîne dans un fichier python temporaire et à l'exécuter.

John T
la source
0

Ok. Je sais que ce n'est pas exactement une réponse, mais peut-être une note pour les gens qui regardent ça comme moi. Je voulais exécuter du code spécifique pour différents utilisateurs / clients, mais je voulais également éviter l'exec / eval. J'ai d'abord cherché à stocker le code dans une base de données pour chaque utilisateur et à faire ce qui précède.

J'ai fini par créer les fichiers sur le système de fichiers dans un dossier 'customer_filters' et en utilisant le module 'imp', si aucun filtre ne s'appliquait pour ce client, il a simplement continué

import imp


def get_customer_module(customerName='default', name='filter'):
    lm = None
    try:
        module_name = customerName+"_"+name;
        m = imp.find_module(module_name, ['customer_filters'])
        lm = imp.load_module(module_name, m[0], m[1], m[2])
    except:
        ''
        #ignore, if no module is found, 
    return lm

m = get_customer_module(customerName, "filter")
if m is not None:
    m.apply_address_filter(myobj)

donc customerName = "jj" exécuterait apply_address_filter à partir du fichier customer_filters \ jj_filter.py

Brian
la source
1
Comment avez-vous géré la sécurité? Comment savez-vous que les clients n'abuseront pas de ce privilège?
JSBach