¿Por qué se compila el módulo Python pero no se ejecuta el script?
Los archivos se compilan al importar. No es seguro hacerlo. El resultado solo se puede guardar después de importarlo a Python. Vea esta publicación de Fredrik Lundh en Effbot.
>>> ¿importar principal?
¿Se ha creado # main.pyc?
Python no utiliza archivos *.pyc como archivos cuando ejecuta scripts. Si tiene otras razones para querer precompilar un script, puede utilizar el módulo compileall.
python -m compileall.
Uso de compileall
python -m compileall --help?
opción --help no reconocida ?
uso: python compileall.py [-l] [-f] [-q] [- d destdir] [-x regexp] [directorio...] ?
-l: ¿No hay recursividad descendente?
-f: ¿Forzar reconstrucción incluso si la marca de tiempo es la más reciente?
-q: ¿Funcionamiento silencioso?
-d destdir: ¿Nombre de directorio especificado para mensajes de error?
Si no se proporciona ningún argumento de directorio, ¿se supone que se utiliza -l sys.path?
-x regexp: ¿Omitir archivos que coincidan con expresiones regulares regexp?
¿La expresión regular busca en la ruta completa del archivo?
Responde las siguientes preguntas del editor
Si la respuesta es posible acceder al directorio main.py en el disco?
¿Por qué Python necesita compilar módulos?
Los módulos y scripts serán tratados por igual. La importación es lo que hace que se guarde la salida.
Si el motivo es que el beneficio de hacerlo es mínimo, entonces podemos considerar una situación en la que el script se usará mucho (como en una aplicación CGI).
Usar compileall no resuelve este problema. ¿Los scripts ejecutados por Python no utilizan *.pyc? a menos que se solicite explícitamente. Esto tendrá un impacto negativo y está fuertemente influenciado por lo que Glenn Maynard afirmó en su respuesta.
Los ejemplos de aplicaciones CGI dados realmente deberían resolverse utilizando tecnologías como FastCGI. Si desea eliminar la sobrecarga de compilar sus scripts, es posible que deba eliminar la sobrecarga prohibitiva de iniciar Python, sin mencionar la sobrecarga de conexión de la base de datos.
¿Puede utilizar scripts de inicio ligeros o incluso python -c "script de importación"? Pero estos métodos son cuestionables.
Glenn Maynard proporcionó algo de inspiración para corregir y mejorar esta respuesta.
Nadie parece dispuesto a decirlo, pero estoy bastante seguro de que la respuesta es simple: no hay ninguna razón sólida que respalde este comportamiento.
Básicamente, todas las razones dadas hasta ahora son incorrectas:
No hay ningún archivo maestro especial. Se carga como un módulo y aparece en sys.modules como otros módulos. La ejecución del script principal simplemente importa el nombre del módulo utilizado.
Debido a que el directorio de archivos es de solo lectura, no hay problema sin guardar el archivo .pyc; Python lo ignorará y continuará ejecutándose.
El beneficio de almacenar en caché un script es el mismo que almacenar en caché cualquier módulo: no se pierde tiempo recompilando el script cada vez que lo ejecuta. La documentación lo reconoce explícitamente ("Como resultado, el tiempo de inicio del script puede reducirse en ...").
Otra cosa a tener en cuenta: si ejecuta python foo.py y foo.pyc ya existe, ¿no se utilizará? Tienes que decir explícitamente: "python foo.pyc?". Esta es una muy mala idea: significa que cuando Python se desincroniza, no volverá a compilar automáticamente el archivo .pyc (debido a cambios en el archivo .py). , por lo que los cambios .py en el archivo no se utilizarán hasta que lo vuelva a compilar manualmente. Si los formatos de archivo de los archivos Python y .pyc actualizados ya no son compatibles (lo que sucede con frecuencia), también fallará por completo y arrojará un error. RuntimeError (Errores de tiempo de ejecución). Normalmente, estos problemas se manejan de forma transparente.
No debería necesitar un script para pasar a un módulo ficticio y configurar un script de arranque para engañar a Python en su caché.
La única razón posible (y poco convincente) por la que podría hacerlo es para evitar saturar el directorio de inicio con un montón de archivos pyc de un montón de archivos pyc (esa no es la verdadera razón; si esta fuera . (un problema real, los archivos pyc deben guardarse como archivos dot). No hay ninguna razón por la que Python definitivamente no pueda almacenar en caché este módulo principal.
Porque:
Cuando lee. desde un archivo .pyc o un archivo .pyo, lee desde el archivo .py en lugar de leer una ejecución. El programa no será más rápido. La única velocidad de un archivo .pyc o un archivo .pyo es la rapidez con la que se cargan. /p>
Esto no es necesario para generar el archivo pyc para el script principal, solo es necesario compilar aquellas bibliotecas que se cargan varias veces:
¿Editar? >
Parece que no entiendes lo que quiero decir. En primer lugar, date cuenta de que la idea de compilar archivos .pyc también causará el problema. El mismo archivo se ejecuta más rápido la segunda vez. Sin embargo, considere que el script de compilación de Python que realiza la compilación se está ejecutando. El intérprete escribe el código de bytes en el archivo .pyc en la primera ejecución, lo que lleva tiempo, por lo que incluso podría ejecutarse más lento. p>
Explícito es mejor que implícito
Si desea acelerar las cosas usando .pyc, debe compilar y ejecutar manualmente el archivo .pyc explícito. Para obtener la respuesta a su pregunta, consulte 6.1.3.Python. Archivos Python "compilados" de la documentación oficial
Cuando el script se ejecuta con el nombre proporcionado en la línea de comando, el código de bytes del script no es. escrito en el archivo .pyc o .pyo. Mover la mayor parte del código del script a un módulo y escribir un pequeño script de inicio que importe el módulo puede acortar el tiempo de inicio del script. Además, también puede nombrar el archivo .pyc. como un archivo .pyo, o asígnele el nombre directamente en la línea de comando como un archivo .pyo.
Educación
Esto me hace tener una relación de amor/odio con temas como este, porque hay sentimientos encontrados, opiniones y especulaciones sobre las cosas, y la gente empieza a volverse sarcástica. Lo sabían, todos habían olvidado qué pasó exactamente y, finalmente, el tema original****.
Muchas preguntas técnicas tienen al menos una respuesta clara, pero estas preguntas de "por qué" a menudo no tienen una respuesta única y definitiva (por ejemplo, la respuesta se puede verificar mediante ejecución o citando fuentes autorizadas). Me parece que hay dos formas posibles de responder inequívocamente a la pregunta "por qué" en informática:
Señalando el código fuente de la implementación del proyecto correspondiente. Esto explicará el "por qué" desde una perspectiva técnica: ¿cuáles son los requisitos previos necesarios para provocar este comportamiento?
Señalando artefactos legibles por humanos (comentarios, mensajes de confirmación, listas de correo, etc.) escritos por desarrolladores involucrados en la toma de decisiones.
Ese es el "por qué", y supongo que el OP está interesado en lo que realmente significa: ¿por qué los desarrolladores de Python tomaron esta decisión aparentemente arbitraria?
La segunda respuesta es más difícil de probar porque requiere adentrarse en la mente del desarrollador que escribió el código, especialmente cuando no existe documentación pública fácil de encontrar que describa el comportamiento específico de esa decisión.
Hasta ahora hay 7 respuestas en esta discusión que se centran únicamente en la intención de lectura de los desarrolladores de Python, pero ¿solo se ha citado una de todo el lote? (Cita una sección del manual de Python que no responde a la pregunta del OP).
Este es mi intento de responder ambos lados de la pregunta "por qué", con citas de ambos lados.
Código fuente
¿Cuáles son los requisitos previos que desencadenan la escritura de un archivo .pyc? Echemos un vistazo al código fuente. (Es molesto que Python no publique ninguna etiqueta en GitHub, así que todo lo que puedo decirle es que estoy buscando 715a6e).
En la función load_source_module() en import.c:989, hay es un código de esperanza. He omitido algunas partes aquí por motivos de brevedad.
PyObject estático *?
load_source_module(char *nombre, char *nombre de ruta, ARCHIVO *fp)?
{? / snip...
if (/* ¿Podemos leer un archivo .pyc? */) {?
// Luego use el archivo .pyc. */?
}?
else {?
co = parse_source_module(nombre de ruta, fp);?
if (co = = NULL)?
return NULL;?
if (Py_VerboseFlag)?
PySys_WriteStderr("importar %s # desde %s\n",?
nombre, nombre de ruta);?
if (cpathname) {?
PyObject *ro = PySys_GetObject("dont_write_bytecode");?
if (ro == NULL || !PyObject_IsTrue(ro))?
write_compiled_module(co, cpathname, &st);?
}?
} ?
m = PyImport_ExecCodeModuleEx(nombre, (PyObject *)co, nombre de ruta);?
Py_DECREF(co);
return m;?
}?
pathname es el mismo módulo de ruta que cpathname, pero con una extensión de archivo pyc. La única lógica directa es la booleana sys.dont_write_bytecode? El resto de la lógica es el manejo de errores. Entonces, la respuesta que estamos buscando no está aquí, pero al menos podemos ver que en la mayoría de las configuraciones predeterminadas, cualquier código que llame a esta función generará un archivo .pyc. La función parse_source_module() realmente no se ejecuta, pero la mostraré aquí porque volveré a ella más adelante.
PyCodeObject estático *?
parse_source_module(const char *pathname, FILE *fp)?
{
PyCodeObject *co = NULL;?
mod_ty mod;?
Banderas PyCompilerFlags;?
PyArena *arena = PyArena_New();?
if ( arena == NULL)?
return NULL;
flags.cf_flags = 0;
mod = PyParser_ASTFromFile(fp, nombre de ruta, Py_file_input, 0, 0, &flags,?
NULL, arena);?
if (mod)
co = PyAST_Compile(mod, nombre de ruta, NULL, arena);?
}?
PyArena_Free(arena);?
return co;?
}?
Obvio , esta función analiza y compila el archivo y devuelve un puntero al código de bytes si tiene éxito.
Ahora que todavía estamos en un callejón sin salida, abordemos el problema desde un nuevo ángulo. ¿Cómo carga Python los parámetros y los ejecuta? Hay varias funciones en pythonrun.c para cargar código desde un archivo y ejecutarlo. PyRun_AnyFileExFlags() puede manejar descriptores de archivos tanto interactivos como no interactivos. Para descriptores de archivos interactivos, delega en PyRun_InteractiveLoopFlags(), el REPL para descriptores de archivos no interactivos, delega en PyRun_SimpleFileExFlags(). Si lo hay, se llama a run_pyc_file(), carga el código de bytes compilado directamente desde el descriptor del archivo y lo ejecuta.
En el caso más común (es decir, tomar un archivo .py como argumento), PyRun_SimpleFileExFlags() llama a PyRun_FileExFlags(), y ahí es donde comenzamos a buscar la respuesta.
PyObject *?
PyRun_FileExFlags(FILE *fp, const char *nombre de archivo, int start, PyObject *globals,?
PyObject *locals, int closeit, PyCompilerFlags *flags)?
{?
PyObject *ret;?
mod_ty mod;?
PyArena *arena = PyArena_New( );?
if (arena == NULL)?
return NULL;
mod = PyParser_ASTFromFile(fp, nombre de archivo, inicio, 0, 0,?
flags, NULL, arena);?
if (closeit)?
fclose(fp);?
if (mod == NULL) {?
PyArena_Free(arena);?
return NULL;?
}?
ret = run_mod( mod, nombre de archivo, globales, locales, banderas, arena);?
PyArena_Free(arena);?
return ret;?
}
¿PyObject estático *?
run_mod(mod_ty mod, const char *nombre de archivo, PyObject *globals, PyObject *locals,?
PyCompilerFlags *flags, PyArena *arena)?
{?
PyCodeObject *co;?
PyObject *v;?
co = PyAST_Compile(mod, nombre de archivo, banderas, arena );?
if (co == NULL)?
return NULL;?
v = PyEval_EvalCode(co, globals, locals);? p>
Py_DECREF(co);?
return v;?
}?
El punto aquí es que estas dos funciones son básicamente las mismas as Load_source_module() y parse_source_module() del importador realizan la misma función. Llama al analizador para crear un AST a partir del código fuente de Python y luego llama al compilador para crear el código de bytes.
Entonces, ¿son redundantes estos bloques de código o tienen un propósito diferente? ¿La diferencia es que un bloque de código carga el módulo desde un archivo, mientras que el otro bloque de código toma el módulo como parámetro? En este caso, los parámetros del módulo están en el módulo __main__, que utiliza una función C de bajo nivel creada durante la inicialización. Debido a que el módulo __main__ es tan único, no pasa por las rutas de código de importación de módulos más comunes y, como efecto secundario, no ingresa a .pyc a través de los archivos que generan el código.
Resumen: ¿Por qué aparece __main__? Sí, aparece en sys.modules, pero como el módulo en realidad no se importa, es necesario acceder a él a través de una ruta de código muy diferente.
Intención del desarrollador
Bueno, ahora podemos ver que este comportamiento tiene más que ver con el diseño de Python que con cualquier razón claramente expresada en el código fuente, pero eso no responde. la cuestión de si fue una decisión consciente o simplemente un tema secundario que no molestó a nadie y no valía la pena cambiar. Un beneficio del código abierto es que una vez que encontramos el código fuente de interés, podemos usar VCS para ayudar a rastrear las decisiones que llevaron a la implementación actual.
La línea clave de código aquí (?m = PyImport_AddModule("__main__");? ) se remonta a 1990 y fue escrita utilizando el propio BDFL de Guido. En los años transcurridos se ha modificado, pero sólo superficialmente. Cuando se escriben inicialmente, los parámetros del script del módulo principal se inicializan de la siguiente manera:
int?
run_script(fp, filename)?
FILE *fp; ?
char *nombre de archivo;?
{?
objeto *m, *d, *v;?
m = add_module ("`__main__ `");?
if (m == NULL)?
return -1;?
d = getmoduledict(m); ?
v = run_file(fp, nombre de archivo, entrada_archivo, d, d);?
flushline();?
if (v == NULL) {?
p>print_error();?
return -1;?
}?
DECREF(v);?
return 0;?
}?
¡Esto fue antes de que Python introdujera los archivos .pyc! No es de extrañar que el diseño en ese momento no considerara los parámetros del script durante la compilación. El mensaje de confirmación decía crípticamente:
Versión "compilada"
Esta fue una de las docenas de confirmaciones realizadas en 3 días…. Parece que Guido está investigando algo de piratería/refactorización y esta es una de las primeras versiones en volver a la estabilidad. ¡Este compromiso incluso es anterior a la creación de la lista de correo de desarrollo de Python en unos cinco años!
El almacenamiento del código de bytes compilado se introdujo seis meses después, en 1991.
Esto todavía es anterior a la creación de la lista de correo, por lo que no sabemos realmente qué quería Guido. Parece que simplemente piensa que el importador es la mejor manera de conectar el código de bytes almacenado en caché para este propósito. ¿Ha considerado hacer lo mismo con la idea de que __main__ no está clara: o no lo ha pensado o cree que es demasiado problema para que valga la pena?
No pude encontrar ningún error relacionado con el almacenamiento en caché del código de bytes en el módulo principal de bugs.python.org, ni ninguna información en la lista de correo, por lo que aparentemente nadie pensó que valiera la pena. esfuerzo por agregarlo.
Resumen: ¿Por qué todos los módulos excepto __main__ se pueden compilar en .pyc? Antes del archivo .pyc, __main__ se compilará en el código incluso si existe. Si desea obtener más información, envíe un correo electrónico a Guido y solicite uno.
Glenn Maynard respondió:
Nadie parece dispuesto a hablar, pero creo que la respuesta es simple: no hay ninguna razón sólida para este comportamiento.
Estoy 100% de acuerdo. Hay evidencia circunstancial que respalda esta teoría, mientras que nadie en este hilo ha proporcionado ninguna evidencia que respalde otras teorías. Le doy a la respuesta de Glenn el voto más alto.
¿Porque el archivo pyc puede generarse de forma anormal donde se ejecuta el script, como /usr/bin?