¿Por qué Python comstack los módulos pero no el script que se está ejecutando?

¿Por qué Python comstack las bibliotecas que se usan en un script, pero no el script que se llama a sí mismo?

Por ejemplo,

Si hay main.py y module.py , y Python se ejecuta haciendo python main.py , habrá un archivo comstackdo module.pyc pero no uno para main. ¿Por qué?

Editar

Añadiendo generosidad. No creo que esto haya sido adecuadamente respondido.

  1. Si la respuesta son permisos de disco potenciales para el directorio de main.py , ¿por qué comstack Python los módulos? Tienen la misma probabilidad (si no es más probable) de aparecer en una ubicación donde el usuario no tiene acceso de escritura. Python podría comstackr main si se puede escribir, o alternativamente en otro directorio.

  2. Si el motivo es que los beneficios serán mínimos, considere la situación en la que el script se usará muchas veces (como en una aplicación CGI).

Related of "¿Por qué Python comstack los módulos pero no el script que se está ejecutando?"

Los archivos se comstackn en la importación. No es una cosa de seguridad. Es simplemente que si lo importas, Python guarda la salida. Ver este post de Fredrik Lundh en Effbot.

 >>>import main # main.pyc is created 

Al ejecutar un script, Python no utilizará el archivo * .pyc. Si tiene alguna otra razón por la que quiere que su script esté precomstackdo, puede usar el módulo compileall .

 python -m compileall . 

Uso compileal

 python -m compileall --help option --help not recognized usage: python compileall.py [-l] [-f] [-q] [-d destdir] [-x regexp] [directory ...] -l: don't recurse down -f: force rebuild even if timestamps are up-to-date -q: quiet operation -d destdir: purported directory name for error messages if no directory arguments, -l sys.path is assumed -x regexp: skip files matching the regular expression regexp the regexp is searched for in the full path of the file 

Respuestas a la pregunta Editar

  1. Si la respuesta son permisos de disco potenciales para el directorio de main.py , ¿por qué comstack Python los módulos?

    Los módulos y scripts son tratados de la misma manera. Importar es lo que activa la salida que se guardará.

  2. Si el motivo es que los beneficios serán mínimos, considere la situación en la que el script se usará muchas veces (como en una aplicación CGI).

    Usar compileall no resuelve esto. Los scripts ejecutados por python no usarán el *.pyc menos que se llame explícitamente. Esto tiene efectos secundarios negativos, bien establecidos por Glenn Maynard en su respuesta .

    El ejemplo dado de una aplicación CGI debería abordarse utilizando una técnica como FastCGI . Si desea eliminar la sobrecarga de comstackr su secuencia de comandos, también puede eliminar la sobrecarga de iniciar Python, por no mencionar la sobrecarga de conexión de la base de datos.

    Se puede usar un script de arranque ligero o incluso python -c "import script" , pero estos tienen un estilo cuestionable.

Glenn Maynard proporcionó algo de inspiración para corregir y mejorar esta respuesta.

Nadie parece querer decir esto, pero estoy bastante seguro de que la respuesta es simple: no hay una razón sólida para este comportamiento.

Todas las razones dadas hasta ahora son esencialmente incorrectas:

  • No hay nada especial en el archivo principal. Se carga como un módulo y se muestra en sys.modules como cualquier otro módulo. Ejecutar un script principal no es más que importarlo con un nombre de módulo de __main__ .
  • No hay ningún problema con el hecho de no poder guardar los archivos .pyc debido a los directorios de solo lectura; Python simplemente lo ignora y sigue adelante.
  • La ventaja de almacenar en caché una secuencia de comandos es la misma que la de almacenar en caché cualquier módulo: no perder el tiempo comstackndo la secuencia de comandos cada vez que se ejecuta. Los documentos reconocen esto explícitamente (“Por lo tanto, el tiempo de inicio de una secuencia de comandos puede reducirse …”).

Otro problema a tener en cuenta: si ejecuta python foo.py y foo.pyc existen, no se utilizará . Tienes que decir explícitamente python foo.pyc . Esa es una muy mala idea: significa que Python no volverá a comstackr automáticamente el archivo .pyc cuando esté desincronizado (debido al cambio del archivo .py), por lo que los cambios en el archivo .py no se utilizarán hasta que lo vuelva a comstackr manualmente. . También se producirá un error absoluto con un RuntimeError si actualiza Python y el formato de archivo .pyc ya no es compatible, lo que sucede con regularidad. Normalmente, todo esto se maneja de forma transparente.

No debería necesitar mover un script a un módulo ficticio y configurar un script de arranque para engañar a Python para que lo almacene en caché. Esa es una solución pirata.

La única razón posible (y muy poco convincente) que puedo crear es evitar que su directorio de inicio se llene de un montón de archivos .pyc. (Esto no es una razón real; si esa era una preocupación real, entonces los archivos .pyc deben guardarse como archivos de puntos). No hay razón para no tener la opción de hacerlo.

Python definitivamente debería poder almacenar en caché el módulo principal.

Pedagogía

Amo y odio preguntas como esta en SO, porque hay una mezcla compleja de emoción, opinión y adivinanzas educadas, y la gente empieza a ponerse furiosa, y de alguna manera todos pierden la pista de los hechos reales y finalmente pierden la pista de la pregunta original. .

Muchas preguntas técnicas sobre SO tienen al menos una respuesta definitiva (por ejemplo, una respuesta que puede verificarse mediante la ejecución o una respuesta que cita una fuente autorizada), pero estas preguntas de “por qué” a menudo no tienen una respuesta única y definitiva. En mi opinión, hay 2 formas posibles de responder definitivamente una pregunta de “por qué” en ciencias de la computación:

  1. Al señalar el código fuente que implementa el elemento de preocupación. Esto explica “por qué” en un sentido técnico: ¿qué condiciones previas son necesarias para evocar este comportamiento?
  2. Al señalar los artefactos legibles para el ser humano (comentarios, mensajes de confirmación, listas de correo electrónico, etc.) escritos por los desarrolladores involucrados para tomar esa decisión. Este es el verdadero sentido del “por qué” que supongo que el OP está interesado en: ¿por qué los desarrolladores de Python tomaron esta decisión aparentemente arbitraria?

El segundo tipo de respuesta es más difícil de corroborar, ya que requiere recordar a los desarrolladores que escribieron el código, especialmente si no hay documentación pública fácil de encontrar que explique una decisión en particular.

Hasta la fecha, este hilo tiene 7 respuestas que se centran únicamente en leer la intención de los desarrolladores de Python y, sin embargo, solo hay una cita en todo el lote. (Y cita una sección del manual de Python que no responde a la pregunta del OP).

Aquí está mi bash de responder a ambos lados de la pregunta del “por qué” junto con las citas.

Código fuente

¿Cuáles son las condiciones previas que activan la comstackción de un .pyc? Veamos el código fuente . (Molestamente, el Python en GitHub no tiene ninguna etiqueta de lanzamiento, así que solo les diré que estoy viendo el 715a6e ).

Hay un código prometedor en import.c:989 en la función load_source_module() . He cortado algunos bits aquí por brevedad.

 static PyObject * load_source_module(char *name, char *pathname, FILE *fp) { // snip... if (/* Can we read a .pyc file? */) { /* Then use the .pyc file. */ } else { co = parse_source_module(pathname, fp); if (co == NULL) return NULL; if (Py_VerboseFlag) PySys_WriteStderr("import %s # from %s\n", name, pathname); if (cpathname) { PyObject *ro = PySys_GetObject("dont_write_bytecode"); if (ro == NULL || !PyObject_IsTrue(ro)) write_compiled_module(co, cpathname, &st); } } m = PyImport_ExecCodeModuleEx(name, (PyObject *)co, pathname); Py_DECREF(co); return m; } 

pathname es la ruta al módulo y cpathname es la misma ruta pero con una extensión .pyc. La única lógica directa es el sys.dont_write_bytecode booleano. El rest de la lógica es simplemente el manejo de errores. Entonces, la respuesta que buscamos no está aquí, pero al menos podemos ver que cualquier código que lo llame resultará en un archivo .pyc en la mayoría de las configuraciones predeterminadas. La función parse_source_module() no tiene relevancia real para el flujo de ejecución, pero la mostraré aquí porque volveré a ella más adelante.

 static PyCodeObject * parse_source_module(const char *pathname, FILE *fp) { PyCodeObject *co = NULL; mod_ty mod; PyCompilerFlags flags; PyArena *arena = PyArena_New(); if (arena == NULL) return NULL; flags.cf_flags = 0; mod = PyParser_ASTFromFile(fp, pathname, Py_file_input, 0, 0, &flags, NULL, arena); if (mod) { co = PyAST_Compile(mod, pathname, NULL, arena); } PyArena_Free(arena); return co; } 

El aspecto sobresaliente aquí es que la función analiza y comstack un archivo y devuelve un puntero al código de byte (si tiene éxito).

Ahora todavía estamos en un callejón sin salida, así que abordemos esto desde un nuevo ángulo. ¿Cómo Python carga su argumento y lo ejecuta? En pythonrun.c hay algunas funciones para cargar código desde un archivo y ejecutarlo. PyRun_AnyFileExFlags() puede manejar descriptores de archivos interactivos y no interactivos. Para los descriptores de archivos interactivos, delega a PyRun_InteractiveLoopFlags() (este es el REPL) y para los descriptores de archivos no interactivos, delega a PyRun_SimpleFileExFlags() . PyRun_SimpleFileExFlags() comprueba si el nombre de archivo termina en .pyc . Si lo hace, entonces llama a run_pyc_file() que carga directamente el código de bytes comstackdo desde un descriptor de archivo y luego lo ejecuta.

En el caso más común (es decir, el archivo .py como argumento), PyRun_SimpleFileExFlags() llama a PyRun_FileExFlags() . Aquí es donde empezamos a encontrar nuestra respuesta.

 PyObject * PyRun_FileExFlags(FILE *fp, const char *filename, 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, filename, start, 0, 0, flags, NULL, arena); if (closeit) fclose(fp); if (mod == NULL) { PyArena_Free(arena); return NULL; } ret = run_mod(mod, filename, globals, locals, flags, arena); PyArena_Free(arena); return ret; } static PyObject * run_mod(mod_ty mod, const char *filename, PyObject *globals, PyObject *locals, PyCompilerFlags *flags, PyArena *arena) { PyCodeObject *co; PyObject *v; co = PyAST_Compile(mod, filename, flags, arena); if (co == NULL) return NULL; v = PyEval_EvalCode(co, globals, locals); Py_DECREF(co); return v; } 

El punto saliente aquí es que estas dos funciones realizan básicamente el mismo propósito que load_source_module() y parse_source_module() . Llama al analizador para crear un AST desde el código fuente de Python y luego llama al comstackdor para crear un código de bytes.

Entonces, ¿estos bloques de código son redundantes o sirven para propósitos diferentes? La diferencia es que un bloque carga un módulo de un archivo, mientras que el otro bloque toma un módulo como argumento . Ese argumento del módulo es, en este caso, el módulo __main__ , que se creó anteriormente en el proceso de inicialización utilizando una función C de bajo nivel. El módulo __main__ no pasa por la mayoría de las rutas de código de importación de módulos normales porque es muy único y, como efecto secundario, no pasa por el código que produce los archivos .pyc .

Para resumir: la razón por la que el módulo __main__ no está comstackdo en .pyc es que no está “importado”. Sí, aparece en sys.modules, pero llega a través de una ruta de código muy diferente a la que toman las importaciones de módulos reales.

Intención del desarrollador

Bien, ahora podemos ver que el 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 a la pregunta de si esta es una decisión intencional o solo un efecto secundario. Eso no molesta a nadie lo suficiente como para que valga la pena cambiar. Una de las ventajas del código abierto es que una vez que encontramos el código fuente que nos interesa, podemos usar el VCS para ayudar a rastrear las decisiones que llevaron a la implementación actual.

Una de las líneas de código fundamentales aquí ( m = PyImport_AddModule("__main__"); ) se remonta a 1990 y fue escrita por el propio BDFL, Guido. Se ha modificado en los años intermedios, pero las modificaciones son superficiales. Cuando se escribió por primera vez, el módulo principal para un argumento de script se inicializó así:

 int run_script(fp, filename) FILE *fp; char *filename; { object *m, *d, *v; m = add_module("`__main__`"); if (m == NULL) return -1; d = getmoduledict(m); v = run_file(fp, filename, file_input, d, d); flushline(); if (v == NULL) { print_error(); return -1; } DECREF(v); return 0; } 

Esto existía incluso antes de que los archivos .pyc se introdujeran en Python. No es de extrañar que el diseño en ese momento no tuviera en cuenta la comstackción para los argumentos de script. El mensaje de confirmación dice enigmáticamente:

Versión “comstackndo”

Esta fue una de las varias docenas de confirmaciones durante un período de 3 días … parece que Guido estaba profundamente involucrado en algunas operaciones de piratería y refactorización y esta fue la primera versión que volvió a ser estable. ¡Este compromiso incluso precede a la creación de la lista de correo Python-Dev por aproximadamente cinco años!

Guardar el código de bytes comstackdo se introdujo 6 meses después, en 1991 .

Esto aún es anterior a la lista de servicios, por lo que no tenemos una idea real de lo que Guido estaba pensando. Parece que él simplemente pensó que el importador era el mejor lugar para conectarse con el propósito de almacenar códigos de bytes. No está claro si consideró la idea de hacer lo mismo para __main__ : o no se le ocurrió, o pensó que era más problema de lo que valía.

No puedo encontrar ningún error en bugs.python.org relacionado con el almacenamiento en caché de los códigos de byte para el módulo principal, ni puedo encontrar ningún mensaje en la lista de correo al respecto, por lo que aparentemente nadie más cree que vale la pena intentar agregar eso.

Para resumir: la razón por la que todos los módulos se comstackn en __main__ excepto __main__ es que es una peculiaridad de la historia. El diseño y la implementación de cómo funciona __main__ se __main__ en el código antes de que existieran los archivos .pyc . Si desea saber más que eso, deberá enviar un correo electrónico a Guido y preguntar.

La respuesta de Glenn Maynard dice:

Nadie parece querer decir esto, pero estoy bastante seguro de que la respuesta es simple: no hay una razón sólida para este comportamiento.

Estoy de acuerdo 100%. Hay evidencia circunstancial para apoyar esta teoría y nadie más en este hilo ha proporcionado una sola muestra de evidencia para apoyar cualquier otra teoría. Yo subestimé la respuesta de Glenn.

Desde

Un progtwig no se ejecuta más rápido cuando se lee desde un archivo .pyc o .pyo que cuando se lee desde un archivo .py; lo único que es más rápido con los archivos .pyc o .pyo es la velocidad con la que se cargan.

Eso no es necesario para generar el archivo .pyc para el script principal. Solo se deben comstackr las bibliotecas que pueden cargarse muchas veces.

Editado :

Parece que no entendiste mi punto. Primero, conocer la idea completa de comstackr en un archivo .pyc es hacer que el mismo archivo se ejecute más rápido por segunda vez. Sin embargo, considere si Python compiló el script que se está ejecutando. El intérprete escribirá el código de .pyc en un archivo .pyc en la primera ejecución, esto lleva tiempo. Así que incluso se ejecutará un poco más lento. Se podría argumentar que se ejecutará más rápido después. Bueno, es solo una elección. Además, como this dice:

Explícito es mejor que implícito.

Si uno quiere una aceleración utilizando el archivo .pyc , debe comstackrlo manualmente y ejecutar el archivo .pyc explícitamente.

Para responder a su pregunta, refiérase a 6.1.3. Archivos “comstackdos” de Python en el documento oficial de Python.

Cuando se ejecuta una secuencia de comandos dando su nombre en la línea de comandos, el código de bytes de la secuencia de comandos nunca se escribe en un archivo .pyc o .pyo. Por lo tanto, el tiempo de inicio de una secuencia de comandos puede reducirse moviendo la mayor parte de su código a un módulo y teniendo una pequeña secuencia de comandos de arranque que importe ese módulo. También es posible nombrar un archivo .pyc o .pyo directamente en la línea de comandos.

Debido a que la secuencia de comandos que se está ejecutando puede estar en un lugar donde no sea apropiado generar archivos .pyc, como /usr/bin .