¿Qué pasa cuando importas un paquete?

En aras de la eficiencia, estoy tratando de averiguar cómo funciona Python con su montón de objetos (y el sistema de espacios de nombres, pero es más o menos claro). Básicamente, estoy tratando de entender cuándo se cargan los objetos en el montón, cuántos de ellos hay, cuánto tiempo viven, etc.

Y mi pregunta es cuando trabajo con un paquete e importo algo de él :

from pypackage import pymodule 

¿Qué objetos se cargan en la memoria (en el montón de objetos del intérprete de python)? Y más en general: ¿qué pasa? 🙂

Creo que el ejemplo anterior hace algo como: algún objeto del paquete paquete fue creado en la memoria (que contiene información sobre el paquete pero no demasiado), el módulo pymodule fue cargado en la memoria y su referencia fue creada en el local espacio de nombres. Lo importante aquí es: no se crearon otros módulos del pypackage (u otros objetos) en la memoria , a menos que se indique explícitamente (en el propio módulo, o en algún lugar de los trucos y enlaces de inicialización del paquete, con los que no estoy familiarizado ). Al final, la única gran cosa en la memoria es pymodule (es decir, todos los objetos que se crearon cuando se importó el módulo). ¿Es tan? Agradecería que alguien aclarara un poco este asunto. Tal vez usted podría aconsejar algún artículo útil al respecto? (la documentación cubre cosas más particulares)

He encontrado lo siguiente en la misma pregunta sobre la importación de módulos:

Cuando Python importa un módulo, primero verifica el registro del módulo (sys.modules) para ver si el módulo ya está importado. Si ese es el caso, Python usa el objeto de módulo existente tal como está.

De lo contrario, Python hace algo como esto:

  • Crear un nuevo objeto de módulo vacío (esto es esencialmente un diccionario)
  • Inserte ese objeto de módulo en el diccionario sys.modules
  • Cargue el objeto de código del módulo (si es necesario, compile el módulo primero)
  • Ejecute el objeto de código de módulo en el espacio de nombres del nuevo módulo. Todas las variables asignadas por el código estarán disponibles a través del objeto de módulo.

Y agradecería el mismo tipo de explicación sobre los paquetes.

Por cierto, con los paquetes se agrega un nombre de módulo en los sys.modules extraña:

 >>> import sys >>> from pypacket import pymodule >>> "pymodule" in sys.modules.keys() False >>> "pypacket" in sys.modules.keys() True 

Y también hay una cuestión práctica sobre el mismo asunto.

Cuando construyo un conjunto de herramientas, que podrían usarse en diferentes procesos y progtwigs. Y los pongo en módulos. No tengo más remedio que cargar un módulo completo, incluso cuando lo único que quiero es usar solo una función declarada allí. Como veo, uno puede hacer que este problema sea menos doloroso haciendo pequeños módulos y colocándolos en un paquete (si un paquete no carga todos sus módulos cuando solo importa uno de ellos).

¿Hay una mejor manera de hacer tales bibliotecas en Python? (Con las meras funciones, que no tienen ninguna dependencia dentro de su módulo). ¿Es posible con las extensiones C?

PD perdón por una pregunta tan larga.

Usted tiene algunas preguntas diferentes aquí. . .

Sobre la importación de paquetes

Cuando importa un paquete, la secuencia de pasos es la misma que cuando importa un módulo. La única diferencia es que el código de los paquetes (es decir, el código que crea el “objeto de código del módulo”) es el código de __init__.py del paquete.

Entonces, sí, los submódulos del paquete no se cargan a menos que __init__.py lo haga explícitamente. Si lo hace from package import module , solo se carga el module , a menos que, por supuesto, importe otros módulos del paquete.

sys.modules nombres de módulos cargados desde paquetes

Cuando importa un módulo desde un paquete, el nombre que se agrega a sys.modules es el “nombre calificado” que especifica el nombre del módulo junto con los nombres separados por puntos de los paquetes que lo importó. Por lo tanto, si lo hace from package.subpackage import mod , lo que se agrega a sys.modules es "package.subpackage.mod" .

Importando solo parte de un modulo

Por lo general, no es una gran preocupación tener que importar el módulo completo en lugar de una sola función. Usted dice que es “doloroso” pero en la práctica casi nunca lo es.

Si, como usted dice, las funciones no tienen dependencias externas, entonces solo son Python puro y cargarlas no llevará mucho tiempo. Por lo general, si la importación de un módulo lleva mucho tiempo, es porque carga otros módulos, lo que significa que tiene dependencias externas y que tiene que cargar todo.

Si su módulo tiene operaciones costosas que ocurren en la importación de módulos (es decir, son códigos de nivel de módulo global y no están dentro de una función), pero no son esenciales para el uso de todas las funciones en el módulo, entonces, si lo desea, puede rediseña tu módulo para diferir esa carga hasta más tarde. Es decir, si tu módulo hace algo como:

 def simpleFunction(): pass # open files, read huge amounts of data, do slow stuff here 

puedes cambiarlo a

 def simpleFunction(): pass def loadData(): # open files, read huge amounts of data, do slow stuff here 

y luego diga a las personas “llame a someModule.loadData() cuando desee cargar los datos”. O, como sugirió, podría colocar las partes caras del módulo en su propio módulo separado dentro de un paquete.

Nunca he encontrado que el hecho de importar un módulo causara un impacto significativo en el rendimiento a menos que el módulo ya fuera lo suficientemente grande como para que se pueda dividir razonablemente en módulos más pequeños. Hacer toneladas de pequeños módulos que contienen una función es improbable que obtenga algo, excepto los dolores de cabeza por el mantenimiento de tener que realizar un seguimiento de todos esos archivos. ¿Tiene realmente una situación específica en la que esto hace una diferencia para usted?

Además, con respecto a su último punto, que yo sepa, la misma estrategia de carga de todo o nada se aplica a los módulos de extensión C como a los módulos de Python puros. Obviamente, al igual que con los módulos de Python, puede dividir las cosas en módulos de extensión más pequeños, pero no puede hacerlo from someExtensionModule import someFunction sin from someExtensionModule import someFunction también el rest del código que se empaquetó como parte de ese módulo de extensión.

La secuencia aproximada de pasos que se produce cuando se importa un módulo es la siguiente:

  1. Python intenta localizar el módulo en sys.modules y no hace nada más si se encuentra. Los paquetes están codificados por su nombre completo, por lo que mientras falta sys.modules en sys.modules , pypacket.pymodule estará allí (y se puede obtener como sys.modules["pypacket.pymodule"] .

  2. Python localiza el archivo que implementa el módulo. Si el módulo es parte del paquete, según lo determinado por la syntax xy , buscará directorios llamados x que contengan tanto un __init__.py como un y.py (o otros subpaquetes). El archivo ubicado más abajo será un archivo .py , un archivo .pyd o un archivo .pyd / .pyd . Si no se encuentra ningún archivo que se ajuste al módulo, se ImportError un ImportError .

  3. Se crea un objeto de módulo vacío y el código en el módulo se ejecuta con el __dict__ del módulo como espacio de nombres de ejecución. 1

  4. El objeto de módulo se coloca en sys.modules y se inyecta en el espacio de nombres del importador.

El paso 3 es el punto en el que “los objetos se cargan en la memoria”: los objetos en cuestión son el objeto del módulo y el contenido del espacio de nombres contenido en su __dict__ . Normalmente, este dictado contiene funciones y clases de nivel superior creadas como un efecto secundario de ejecutar todas las declaraciones de def , class y otras declaraciones de nivel superior que normalmente se incluyen en cada módulo.

Tenga en cuenta que lo anterior solo describe la implementación predeterminada de import . Hay varias formas en que uno puede personalizar el comportamiento de importación, por ejemplo, anulando el __import__ incorporado o implementando __import__ importación .


1 Si el archivo del módulo es un archivo fuente .py , primero se comstackrá en la memoria y se ejecutarán los objetos de código resultantes de la comstackción. Si es un .pyc , los objetos de código se obtendrán deserializando el contenido del archivo . Si el módulo es una biblioteca compartida .pyd o .pyd , se cargará utilizando la función de carga de bibliotecas compartidas del sistema operativo y se invocará la función init C para inicializar el módulo.