Quelle est une alternative à execfile en Python 3?

352

Il semble qu'ils aient annulé dans Python 3 tout le moyen facile de charger rapidement un script en supprimant execfile()

Y a-t-il une alternative évidente qui me manque?

RS
la source
1
reloadest de retour, puisque imp.reload, depuis 3.2.
Dougal
18
Si vous utilisez Python de manière interactive, envisagez d'utiliser IPython: %run script_namefonctionne avec toutes les versions de Python.
Michael
1
Puisque 3.4 impest importlib (qui doit être importé): importlib.reload(mod_name)importe et exécute mod_name.
P. Wormer
3
quel est le problème avec runfile ("filename.py")?
mousomer
1
Merci @mousomer !! Je cherchais précisément la fonctionnalité de runfile()car j'avais besoin d'exécuter un script Python qui s'exécute dans son propre espace de noms (par opposition à l'exécution sur l' espace de noms appelant ). Mon application: ajoutez le répertoire du script appelé au chemin système ( sys.path) en utilisant l' __file__attribut: si nous utilisons execfile()ou son équivalent en Python 3 ( exec(open('file.py').read())) le script inclus est exécuté dans l'espace de noms appelant et se __file__résout ainsi en nom de fichier appelant .
mastropi

Réponses:

389

Selon la documentation , au lieu de

execfile("./filename") 

Utilisation

exec(open("./filename").read())

Voir:

Pedro Vagner
la source
54
Une idée pourquoi ils feraient une telle chose? C'est tellement plus verbeux qu'auparavant. De plus, cela ne fonctionne pas pour moi sur Python3.3. J'obtiens "Aucun fichier ou répertoire" lorsque j'exécute (open ('./ some_file'). Read ()). J'ai essayé d'inclure l'extension '.py' et d'exclure également le './'
JoeyC
25
Plus trivialement, cela ne fournit pas de numéros de ligne lorsque des exceptions sont déclenchées, comme l'a fait execfile ().
KDN
35
Vous devrez closeégalement gérer ce fichier. Une autre raison de ne pas aimer le changement de python 2.
Rebs
3
@Rebs vous n'avez pas besoin de fermer le descripteur de fichier dans cet exemple, cela se fera automatiquement (au moins en CPython normal)
tiho
4
@Rebs dans les objets CPython sont récupérés dès que leur nombre de références passe à 0, seules les références circulaires peuvent retarder cela ( stackoverflow.com/questions/9449489/… ). Dans ce cas, cela devrait se produire juste après le retour de read (). Et les objets fichiers sont fermés lors de la suppression (NB: je me rends compte que ce lien dit explicitement "toujours fermer les fichiers", ce qui est en effet une bonne pratique à suivre en général)
tiho
219

Vous êtes juste censé lire le fichier et exécuter le code vous-même. 2to3 remplacements actuels

execfile("somefile.py", global_vars, local_vars)

comme

with open("somefile.py") as f:
    code = compile(f.read(), "somefile.py", 'exec')
    exec(code, global_vars, local_vars)

(L'appel de compilation n'est pas strictement nécessaire, mais il associe le nom de fichier à l'objet de code, ce qui facilite le débogage.)

Voir:

Benjamin Peterson
la source
3
Cela fonctionne pour moi. Cependant, j'ai remarqué que vous avez écrit les arguments locaux et globaux dans le mauvais ordre. C'est en fait: exec (objet [, globaux [, locaux]]). Bien sûr, si vous aviez les arguments retournés dans l'original, alors 2to3 produira exactement ce que vous avez dit. :)
Nathan Shively-Sanders
3
A été heureux de découvrir que, si vous pouvez omettre global_vars et local_vars, le remplacement de python3 fonctionne également sous python2. Même si execest une instruction en python2, exec(code)fonctionne parce que les parens sont simplement ignorés.
medmunds
2
+1 pour l'utilisation de la compilation. Mon "somefile.py"contenu inspect.getsourcefile(lambda _: None)qui échouait sans la compilation, car le inspectmodule ne pouvait pas déterminer d'où venait le code.
ArtOfWarfare
16
C'est ... vraiment moche. Une idée pourquoi ils se sont débarrassés de execfile () dans 3.x? execfile a également facilité le passage des arguments de ligne de commande.
2015
3
open("somefile.py")peut être incorrect si somefile.pyun codage de caractères différent de locale.getpreferredencoding(). tokenize.open()pourrait être utilisé à la place.
jfs
73

Bien qu'il exec(open("filename").read())soit souvent donné comme alternative à execfile("filename"), il manque des détails importants qui ont été execfilepris en charge.

La fonction suivante pour Python3.x est aussi proche que possible d'avoir le même comportement que l'exécution directe d'un fichier. Cela correspond à la course python /path/to/somefile.py.

def execfile(filepath, globals=None, locals=None):
    if globals is None:
        globals = {}
    globals.update({
        "__file__": filepath,
        "__name__": "__main__",
    })
    with open(filepath, 'rb') as file:
        exec(compile(file.read(), filepath, 'exec'), globals, locals)

# execute the file
execfile("/path/to/somefile.py")

Remarques:

  • Utilise la lecture binaire pour éviter les problèmes d'encodage
  • Garantie de fermeture du fichier (Python3.x vous en avertit)
  • Définit __main__, certains scripts en dépendent pour vérifier s'ils se chargent en tant que module ou non, par exemple.if __name__ == "__main__"
  • Le réglage __file__est plus agréable pour les messages d'exception et certains scripts permettent __file__d'obtenir les chemins des autres fichiers par rapport à eux.
  • Prend des arguments facultatifs globaux et locaux, les modifiant en place comme le execfilefait - afin que vous puissiez accéder à toutes les variables définies en relisant les variables après l'exécution.

  • Contrairement à Python2, execfilecela ne modifie pas l'espace de noms actuel par défaut. Pour cela, vous devez explicitement passer dans globals()& locals().

ideasman42
la source
68

Comme suggéré récemment sur la liste de diffusion python-dev , le module runpy pourrait être une alternative viable. Citant ce message:

https://docs.python.org/3/library/runpy.html#runpy.run_path

import runpy
file_globals = runpy.run_path("file.py")

Il existe de subtiles différences pour execfile:

  • run_pathcrée toujours un nouvel espace de noms. Il exécute le code en tant que module, il n'y a donc pas de différence entre les globaux et les locaux (c'est pourquoi il n'y a qu'un init_globalsargument). Les globaux sont retournés.

    execfileexécuté dans l'espace de noms actuel ou l'espace de noms donné. La sémantique de localset globals, si elle est donnée, était similaire aux sections locales et globales dans une définition de classe.

  • run_path peut non seulement exécuter des fichiers, mais aussi des œufs et des répertoires (reportez-vous à sa documentation pour plus de détails).

Jonas Schäfer
la source
1
Pour une raison quelconque, il affiche à l'écran un grand nombre d'informations qu'il n'a pas été demandé d'imprimer (« builtins », etc. dans Anaconda Python 3). Existe-t-il un moyen de désactiver cette option pour que seules les informations que je génère avec print () soient visualisées?
John Donn, du
Est-il également possible d'obtenir toutes les variables dans l'espace de travail actuel au lieu de les stocker toutes file_globals? Cela éviterait d'avoir à taper le file_globals['...']pour chaque variable.
Adriaan
1
"En outre, les fonctions et classes définies par le code exécuté ne sont pas garanties de fonctionner correctement après le retour d'une fonction runpy." À noter, selon votre cas d'utilisation
nodakai
@Adriaan Exécutez "globals (). Update (file_globals)". Personnellement, je préfère cette solution car je peux éventuellement détecter des erreurs avant de décider de mettre à jour l'espace de travail actuel.
Ron Kaminsky
@nodakai Merci pour l'info, j'ai raté ça. Je n'ai encore jamais eu de problème de ce genre, je me demande ce qui risque de déclencher cela.
Ron Kaminsky
21

Celui-ci est meilleur, car il prend les globaux et les locaux de l'appelant:

import sys
def execfile(filename, globals=None, locals=None):
    if globals is None:
        globals = sys._getframe(1).f_globals
    if locals is None:
        locals = sys._getframe(1).f_locals
    with open(filename, "r") as fh:
        exec(fh.read()+"\n", globals, locals)
Noam
la source
En fait, celui-ci est le plus proche de py2 execfile. Cela a même fonctionné pour moi lors de l'utilisation de pytests où d'autres solutions publiées ci-dessus ont échoué. THX! :)
Boriel
17

Vous pouvez écrire votre propre fonction:

def xfile(afile, globalz=None, localz=None):
    with open(afile, "r") as fh:
        exec(fh.read(), globalz, localz)

Si vous aviez vraiment besoin de ...

Evan Fosmark
la source
1
-1: l'exécutable ne fonctionne pas de cette façon. Le code ne s'exécute dans aucune version de python.
nosklo
6
-1: Les valeurs des paramètres par défaut sont évaluées au moment de la définition de la fonction, ce qui fait les deux globalset localspointe vers l'espace de noms global du module contenant la définition de execfile()plutôt que vers l'espace de noms global et local de l'appelant. L'approche correcte consiste à utiliser Nonecomme valeur par défaut et à déterminer les paramètres globaux et locaux de l'appelant via les capacités d'introspection du inspectmodule.
Sven Marnach
12

Si le script que vous souhaitez charger se trouve dans le même répertoire que celui que vous exécutez, peut-être que "import" fera le travail?

Si vous devez importer dynamiquement du code, la fonction intégrée __ import__ et le module imp valent la peine d'être examinés .

>>> import sys
>>> sys.path = ['/path/to/script'] + sys.path
>>> __import__('test')
<module 'test' from '/path/to/script/test.pyc'>
>>> __import__('test').run()
'Hello world!'

test.py:

def run():
        return "Hello world!"

Si vous utilisez Python 3.1 ou une version ultérieure, vous devriez également jeter un œil à importlib .

ascobol
la source
C'était la bonne réponse pour moi. Ce blog explique bien importlib dev.to/0xcrypto/dynamic-importing-stuff-in-python--1805
Nick Brady
9

Voici ce que j'avais ( fileest déjà affecté au chemin d'accès au fichier avec le code source dans les deux exemples):

execfile(file)

Voici ce que je l'ai remplacé:

exec(compile(open(file).read(), file, 'exec'))

Ma partie préférée: la deuxième version fonctionne très bien en Python 2 et 3, ce qui signifie qu'il n'est pas nécessaire d'ajouter une logique dépendante de la version.

ArtOfWarfare
la source
5

Notez que le modèle ci-dessus échouera si vous utilisez des déclarations d'encodage PEP-263 qui ne sont pas ascii ou utf-8. Vous devez trouver l'encodage des données et les encoder correctement avant de les transmettre à exec ().

class python3Execfile(object):
    def _get_file_encoding(self, filename):
        with open(filename, 'rb') as fp:
            try:
                return tokenize.detect_encoding(fp.readline)[0]
            except SyntaxError:
                return "utf-8"

    def my_execfile(filename):
        globals['__file__'] = filename
        with open(filename, 'r', encoding=self._get_file_encoding(filename)) as fp:
            contents = fp.read()
        if not contents.endswith("\n"):
            # http://bugs.python.org/issue10204
            contents += "\n"
        exec(contents, globals, globals)
Eric
la source
3
Qu'est-ce que "le schéma ci-dessus"? Veuillez utiliser des liens lorsque vous vous référez à d'autres publications sur StackOverflow. Les termes de positionnement relatif comme "ci-dessus" ne fonctionnent pas, car il existe 3 façons différentes de trier les réponses (par votes, par date ou par activité) et la plus courante (par votes) est volatile. Au fil du temps, votre message et les messages autour du vôtre se retrouveront avec des scores différents, ce qui signifie qu'ils seront réorganisés et que de telles comparaisons seront moins utiles.
ArtOfWarfare
Très bon point. Et étant donné que j'ai écrit cette réponse il y a près de six mois, je suppose que par "modèle ci-dessus", je voulais dire stackoverflow.com/a/2849077/165082 (sur lequel vous devez malheureusement cliquer pour résoudre), ou mieux encore la réponse de Noam:
Eric
2
Généralement, lorsque je veux faire référence à d'autres réponses à la même question dans ma réponse, je tape "Réponse de Noam" (par exemple) et je lie le texte à la réponse à laquelle je fais référence, juste au cas où la réponse se dissocierait de la utilisateur à l'avenir, IE, car l'utilisateur change de nom de compte ou la publication devient un wiki commun car trop de modifications ont été apportées sur celui-ci.
ArtOfWarfare
Comment obtenir l'URL d'une "réponse" spécifique dans un message, à l'exclusion du nom de l'affiche de la réponse?
DevPlayer
Affichez la source et obtenez l'ID. Par exemple, votre question serait stackoverflow.com/questions/436198/… . Je suis tout pour une meilleure méthode, mais je ne vois rien quand je passe près du commentaire
Eric
4

De plus, bien qu'il ne s'agisse pas d'une solution Python pure, si vous utilisez IPython (comme vous le devriez probablement de toute façon), vous pouvez faire:

%run /path/to/filename.py

Ce qui est tout aussi simple.

RS
la source
1

Je suis juste un débutant ici, alors c'est peut-être de la chance si je trouve ça:

Après avoir essayé d'exécuter un script à partir de l'invite d'interpréteur >>> avec la commande

    execfile('filename.py')

pour lequel j'ai obtenu un "NameError: le nom 'execfile' n'est pas défini" J'ai essayé un très basique

    import filename

cela a bien fonctionné :-)

J'espère que cela peut être utile et merci à tous pour les excellents conseils, exemples et tous ces morceaux de code magistralement commentés qui sont une grande inspiration pour les nouveaux arrivants!

J'utilise Ubuntu 16.014 LTS x64. Python 3.5.2 (par défaut, 17 novembre 2016, 17:05:23) [GCC 5.4.0 20160609] sur linux

Claude
la source
0

Pour moi, l'approche la plus propre est d'utiliser importlibet d'importer le fichier en tant que module par chemin, comme ceci:

from importlib import util

def load_file(name, path):
    spec = util.spec_from_file_location(name, path)
    module = util.module_from_spec(spec)
    spec.loader.exec_module(module)
    return module

Exemple d'utilisation

Ayons un fichier foo.py:

print('i got imported')
def hello():
    print('hello from foo')

Maintenant, importez-le et utilisez-le comme un module normal:

>>> foo = load_file('foo', './foo.py')
i got imported
>>> foo.hello()
hello from foo

Je préfère cette technique aux approches directes comme exec(open(...))parce qu'elle n'encombre pas vos espaces de noms ou ne gâche pas inutilement $PATH.

Arminius
la source