¿Qué es una alternativa a execfile en Python 3?

352

Parece que cancelaron en Python 3 toda la manera fácil de cargar rápidamente un script eliminando execfile()

¿Hay una alternativa obvia que me falta?

RS
fuente
1
reloadestá de vuelta, como imp.reload, desde 3.2.
Dougal
18
Si está utilizando Python interactivamente, considere usar IPython: %run script_namefunciona con todas las versiones de Python.
Michael
1
Desde 3.4 impes importlib (que debe importarse): importlib.reload(mod_name)importa y ejecuta mod_name.
P. Wormer
3
¿Qué hay de malo con el archivo de ejecución ("filename.py")?
Mousomer
1
Gracias @mousomer !! Estaba buscando precisamente la funcionalidad de, runfile()ya que necesitaba ejecutar un script de Python que se ejecute en su propio espacio de nombres (en lugar de ejecutarse en el espacio de nombres de llamada ). Mi solicitud: agregar el directorio del script llamado a la ruta del sistema ( sys.path) mediante el __file__atributo: si usamos execfile()o su equivalente en Python 3 ( exec(open('file.py').read())) del script incluido se ejecuta en el espacio de nombres llamando y por lo tanto __file__resuelve al llamar a nombre de archivo.
mastropi

Respuestas:

389

Según la documentación , en lugar de

execfile("./filename") 

Utilizar

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

Ver:

Pedro Vagner
fuente
54
¿Alguna idea de por qué harían tal cosa? Esto es mucho más detallado que antes. Además, no me funciona en Python3.3. Obtengo "No existe tal archivo o directorio" cuando ejecuto (abrir ('./ some_file'). Read ()). He intentado incluir la extensión '.py' y también excluir el './'
JoeyC
25
Menos trivial, esto no proporciona números de línea cuando se generan excepciones, como lo hizo execfile ().
KDN
35
También necesitarás closeese identificador de archivo. Otra razón por la que no les gusta el cambio de pitón 2.
REBS
3
@Rebs que no es necesario para cerrar el identificador de archivo en ese ejemplo, que se llevará a cabo de forma automática (por lo menos en CPython regular)
tiho
44
@Rebs en los objetos CPython se recolectan como basura tan pronto como su recuento de referencias va a 0, solo las referencias circulares pueden retrasar esto ( stackoverflow.com/questions/9449489/… ). En ese caso, eso debería suceder justo después de que read () regrese. Y los objetos de archivo se cierran al eliminarlos (NB: me doy cuenta de que este enlace dice explícitamente "cerrar siempre los archivos", lo cual es una buena práctica en general)
tiho
219

Se supone que solo debes leer el archivo y ejecutar el código tú mismo. La corriente 2to3 reemplaza

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

como

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

(La llamada de compilación no es estrictamente necesaria, pero asocia el nombre de archivo con el objeto de código, lo que facilita un poco la depuración).

Ver:

Benjamin Peterson
fuente
3
Esto funciona para mi. Sin embargo, noté que has escrito los argumentos locales y globales en el orden incorrecto. En realidad es: exec (objeto [, globales [, locales]]). Por supuesto, si tenía los argumentos invertidos en el original, entonces 2to3 producirá exactamente lo que dijo. :)
Nathan Shively-Sanders
3
Me complació descubrir que, si puede omitir global_vars y local_vars, el reemplazo de python3 aquí también funciona en python2. Aunque execes una declaración en python2, exec(code)funciona porque los padres simplemente se ignoran.
medmunds
2
+1 por usar la compilación. Mi "somefile.py"contenido inspect.getsourcefile(lambda _: None)estaba fallando sin la compilación, porque el inspectmódulo no podía determinar de dónde venía el código.
ArtOfWarfare
16
Eso es ... realmente feo. ¿Alguna idea de por qué se deshicieron de execfile () en 3.x? execfile también facilitó pasar argumentos de línea de comandos.
aneccodeal
3
open("somefile.py")puede ser incorrecto si somefile.pyusa una codificación de caracteres diferente de locale.getpreferredencoding(). tokenize.open()podría ser usado en su lugar.
jfs
73

Si bien a exec(open("filename").read())menudo se ofrece como una alternativa execfile("filename"), se pierden detalles importantes que execfilerespaldan.

La siguiente función para Python3.x es lo más cercana posible a tener el mismo comportamiento que ejecutar un archivo directamente. Eso coincide con correr 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")

Notas:

  • Utiliza lectura binaria para evitar problemas de codificación
  • Garantizado para cerrar el archivo (Python3.x advierte sobre esto)
  • Define __main__, algunos scripts dependen de esto para verificar si se están cargando como un módulo o no, por ejemplo.if __name__ == "__main__"
  • La configuración __file__es mejor para los mensajes de excepción y algunas secuencias de comandos se utilizan __file__para obtener las rutas de otros archivos en relación con ellos.
  • Toma argumentos opcionales globales y locales, modificándolos en el lugar como lo execfilehace, para que pueda acceder a cualquier variable definida al leer las variables después de la ejecución.

  • A diferencia de Python2, execfileesto no modifica el espacio de nombres actual de forma predeterminada. Para eso tienes que pasar explícitamente globals()& locals().

ideasman42
fuente
68

Como se sugirió recientemente en la lista de correo python-dev , el módulo runpy podría ser una alternativa viable. Citando de ese mensaje:

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

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

Hay diferencias sutiles para execfile:

  • run_pathsiempre crea un nuevo espacio de nombres. Ejecuta el código como un módulo, por lo que no hay diferencia entre globales y locales (razón por la cual solo hay un init_globalsargumento). Los globales son devueltos.

    execfileejecutado en el espacio de nombres actual o en el espacio de nombres dado. La semántica de localsy globals, si se da, eran similares a los locales y globales dentro de una definición de clase.

  • run_path no solo puede ejecutar archivos, sino también huevos y directorios (consulte su documentación para más detalles).

Jonas Schäfer
fuente
1
Por alguna razón, envía a la pantalla mucha información que no se le pidió que imprimiera (' incorporados ', etc. en Anaconda Python 3). ¿Hay alguna forma de desactivar esto para que solo se visualice la información que imprimí con print ()?
John Donn
¿También es posible obtener todas las variables en el espacio de trabajo actual en lugar de almacenarlas todas file_globals? Esto ahorraría tener que escribir el file_globals['...']para cada variable.
Adriaan
1
"Además, no se garantiza que las funciones y clases definidas por el código ejecutado funcionen correctamente después de que una función runpy haya regresado". Vale la pena señalar, dependiendo de su caso de uso
nodakai
@Adriaan Ejecute "globals (). Update (file_globals)". Personalmente, me gusta más esta solución porque posiblemente pueda detectar errores antes de decidir actualizar el espacio de trabajo actual.
Ron Kaminsky
@nodakai Gracias por la información, me perdí eso. Nunca tuve un problema como ese todavía, me pregunto qué es probable que lo desate.
Ron Kaminsky
21

Este es mejor, ya que toma los globales y locales de la persona que llama:

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
fuente
En realidad, este es el más cercano a py2 execfile. Incluso funcionó para mi cuando uso pytests donde otras soluciones publicadas anteriormente fallaron. ¡Gracias! :)
Boriel
17

Podrías escribir tu propia función:

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

Si realmente necesitaras ...

Evan Fosmark
fuente
1
-1: el estado ejecutivo no funciona de esta manera. El código no se ejecuta en ninguna versión de python.
nosklo
66
-1: Los valores de los parámetros por defecto se evalúan en tiempo de definición de función, por lo tanto globals, y localsapuntan al espacio de nombres global fo el módulo que contiene la definición del execfile()lugar de espacio de nombres global y local de la persona que llama. El enfoque correcto es usar Nonecomo valor predeterminado y determinar los globales y locales de la persona que llama a través de las capacidades de introspección del inspectmódulo.
Sven Marnach
12

Si el script que desea cargar está en el mismo directorio que el que ejecuta, ¿tal vez "import" hará el trabajo?

Si necesita importar código dinámicamente, vale la pena mirar la función incorporada __ import__ y el módulo imp .

>>> 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 está utilizando Python 3.1 o posterior, también debería echar un vistazo a importlib .

ascobol
fuente
Esta fue la respuesta correcta para mí. Este blog hace un buen trabajo explicando importlib dev.to/0xcrypto/dynamic-importing-stuff-in-python--1805
Nick Brady
9

Esto es lo que tenía ( fileya está asignado a la ruta del archivo con el código fuente en ambos ejemplos):

execfile(file)

Esto es con lo que lo reemplacé:

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

Mi parte favorita: la segunda versión funciona bien tanto en Python 2 como en 3, lo que significa que no es necesario agregar lógica dependiente de la versión.

ArtOfWarfare
fuente
5

Tenga en cuenta que el patrón anterior fallará si está utilizando declaraciones de codificación PEP-263 que no son ascii o utf-8. Debe encontrar la codificación de los datos y codificarla correctamente antes de entregarla a 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
fuente
3
¿Qué es "el patrón anterior"? Utilice enlaces cuando haga referencia a otras publicaciones en StackOverflow. Los términos de posicionamiento relativo como "lo anterior" no funcionan, ya que hay 3 formas diferentes de ordenar las respuestas (por votos, por fecha o por actividad) y la más común (por votos) es volátil. Con el tiempo, su publicación y las publicaciones alrededor de la suya terminarán con puntuaciones diferentes, lo que significa que se reorganizarán y tales comparaciones serán menos útiles.
ArtOfWarfare
Muy buen punto. Y dado que escribí esta respuesta hace casi seis meses, supongo que por "patrón anterior" quise decir stackoverflow.com/a/2849077/165082 (que desafortunadamente tienes que hacer clic para resolver), o mejor aún la respuesta de Noam:
Eric
2
En general, cuando quiero referirme a otras respuestas a la misma pregunta de mi respuesta, escribo "Noam's Answer" (por ejemplo) y vinculo el texto a la respuesta a la que me refiero, por si la respuesta se disocia del usuario en el futuro, IE, porque el usuario cambia el nombre de su cuenta o la publicación se convierte en un wiki común porque se han realizado demasiadas modificaciones en él.
ArtOfWarfare
¿Cómo se obtiene la URL de una "respuesta" específica en una publicación, excluyendo el nombre del cartel de la respuesta?
DevPlayer
Ver la fuente y obtener la identificación. Por ejemplo, su pregunta sería stackoverflow.com/questions/436198/… . Estoy a favor de un método mejor, pero no veo nada cuando me acerco al comentario
Eric
4

Además, aunque no es una solución pura de Python, si está utilizando IPython (como probablemente debería hacerlo de todos modos), puede hacer lo siguiente:

%run /path/to/filename.py

Lo cual es igualmente fácil.

RS
fuente
1

Solo soy un novato aquí, así que tal vez sea pura suerte si encuentro esto:

Después de intentar ejecutar un script desde el indicador del intérprete >>> con el comando

    execfile('filename.py')

para lo cual obtuve un "NameError: el nombre 'execfile' no está definido" Intenté un método muy básico

    import filename

funcionó bien :-)

¡Espero que esto pueda ser útil y gracias a todos por las excelentes sugerencias, ejemplos y todas esas piezas de código magistralmente comentadas que son una gran inspiración para los recién llegados!

Yo uso Ubuntu 16.014 LTS x64. Python 3.5.2 (predeterminado, 17 de noviembre de 2016, 17:05:23) [GCC 5.4.0 20160609] en Linux

Claude
fuente
0

Para mí, el enfoque más limpio es usar importlibe importar el archivo como un módulo por ruta, así:

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

Ejemplo de uso

Tengamos un archivo foo.py:

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

Ahora solo importe y use como un módulo normal:

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

Prefiero esta técnica sobre los enfoques directos, como exec(open(...))porque no abarrota sus espacios de nombres o se mete innecesariamente con ellos $PATH.

Arminio
fuente