Como seguimiento de la pregunta Uso de __import__()
en casos normales , realizo algunas pruebas y encontré resultados sorprendentes.
Aquí estoy comparando el tiempo de ejecución de una statement de import
clásica y una llamada a la __import__
incorporada __import__
. Para este propósito, uso el siguiente script en modo interactivo:
import timeit def test(module): t1 = timeit.timeit("import {}".format(module)) t2 = timeit.timeit("{0} = __import__('{0}')".format(module)) print("import statement: ", t1) print("__import__ function:", t2) print("t(statement) {} t(function)".format("<" if t1 "))
Como en la pregunta vinculada, aquí está la comparación al importar sys
, junto con algunos otros módulos estándar:
>>> test('sys') import statement: 0.319865173171288 __import__ function: 0.38428380458522987 t(statement) >> test('math') import statement: 0.10262547545597034 __import__ function: 0.16307580163101054 t(statement) >> test('os') import statement: 0.10251490255312312 __import__ function: 0.16240755669640627 t(statement) >> test('threading') import statement: 0.11349136644972191 __import__ function: 0.1673617034957573 t(statement) < t(function)
Hasta ahora, bien, la import
es más rápida que __import__()
. Esto tiene sentido para mí, porque como escribí en la publicación vinculada, me parece lógico que la instrucción IMPORT_NAME
esté optimizada en comparación con CALL_FUNCTION
, cuando esta última resulta en una llamada a __import__
.
Pero cuando se trata de módulos menos estándar, los resultados se invierten:
>>> test('numpy') import statement: 0.18907936340054476 __import__ function: 0.15840019037769792 t(statement) > t(function) >>> test('tkinter') import statement: 0.3798560809537861 __import__ function: 0.15899962771786136 t(statement) > t(function) >>> test("pygame") import statement: 0.6624641952621317 __import__ function: 0.16268579177259568 t(statement) > t(function)
¿Cuál es la razón detrás de esta diferencia en los tiempos de ejecución? ¿Cuál es la razón real por la que la statement de import
es más rápida en los módulos estándar? Por otro lado, ¿por qué la función __import__
es más rápida con otros módulos?
Las pruebas conducen con Python 3.6.
timeit
mide el tiempo total de ejecución, pero la primera importación de un módulo, ya sea a través de import
o __import__
, es más lenta que las posteriores, porque es la única que realiza la inicialización del módulo. Tiene que buscar en el sistema de archivos los archivos del módulo, cargar el código fuente del módulo (el más lento) o el bytecode creado anteriormente (lento pero un poco más rápido que analizar los archivos .py
) o la biblioteca compartida (para las extensiones C), ejecutar el código de inicialización, y almacenar el objeto de módulo en sys.modules
. Las importaciones posteriores pueden omitir todo eso y recuperar el objeto de módulo de sys.modules
.
Si invierte el orden los resultados serán diferentes:
import timeit def test(module): t2 = timeit.timeit("{0} = __import__('{0}')".format(module)) t1 = timeit.timeit("import {}".format(module)) print("import statement: ", t1) print("__import__ function:", t2) print("t(statement) {} t(function)".format("<" if t1 < t2 else ">")) test('numpy') import statement: 0.4611093703134608 __import__ function: 1.275512785926014 t(statement) < t(function)
La mejor manera de obtener resultados no sesgados es importarlo una vez y luego hacer los tiempos:
import timeit def test(module): exec("import {}".format(module)) t2 = timeit.timeit("{0} = __import__('{0}')".format(module)) t1 = timeit.timeit("import {}".format(module)) print("import statement: ", t1) print("__import__ function:", t2) print("t(statement) {} t(function)".format("<" if t1 < t2 else ">")) test('numpy') import statement: 0.4826306561727307 __import__ function: 0.9192819125911029 t(statement) < t(function)
Entonces, sí, la import
siempre es más rápida que __import__
.
Recuerde que todos los módulos se almacenan en caché en sys.modules
después de la primera importación, por lo que el tiempo …
De todos modos, mis resultados se ven así:
#!/bin/bash itest() { echo -n "import $1: " python3 -m timeit "import $1" echo -n "__import__('$1'): " python3 -m timeit "__import__('$1')" } itest "sys" itest "math" itest "six" itest "PIL"
import sys
: 0.481 __import__('sys')
: 0.586 import math
: 0.163 __import__('math')
: 0.247 import six
: 0.157 __import__('six')
: 0.273 import PIL
: 0.162 __import__('PIL')
: 0.265
¿Cuál es la razón detrás de esta diferencia en los tiempos de ejecución?
La statement de importación tiene un camino bastante sencillo para pasar. Conduce a IMPORT_NAME
que llama a import_name
e importa el módulo dado (si no se ha __import__
el nombre __import__
):
dis('import math') 1 0 LOAD_CONST 0 (0) 2 LOAD_CONST 1 (None) 4 IMPORT_NAME 0 (math) 6 STORE_NAME 0 (math) 8 LOAD_CONST 1 (None) 10 RETURN_VALUE
__import__
, por otro lado, sigue los pasos de llamada a funciones genéricas que todas las funciones realizan a través de CALL_FUNCTION
:
dis('__import__(math)') 1 0 LOAD_NAME 0 (__import__) 2 LOAD_NAME 1 (math) 4 CALL_FUNCTION 1 6 RETURN_VALUE
Claro, está integrado y es más rápido que las funciones normales de py, pero sigue siendo más lento que la statement de import
con import_name
.
Por eso, la diferencia de tiempo entre ellos es constante. Al usar el fragmento de código @MSeifert (que corrigió los tiempos injustos 🙂 y agregar otra impresión, puede ver esto:
import timeit def test(module): exec("import {}".format(module)) t2 = timeit.timeit("{0} = __import__('{0}')".format(module)) t1 = timeit.timeit("import {}".format(module)) print("import statement: ", t1) print("__import__ function:", t2) print("t(statement) {} t(function)".format("<" if t1 < t2 else ">")) print('Diff: {}'.format(t2-t1)) for m in sys.builtin_module_names: test(m)
En mi máquina, hay una diferencia constante de alrededor de 0,17 entre ellos (con una ligera variación que generalmente se espera)
* Vale la pena señalar que estos no son exactamente equivalentes. __import__
no hace ningún enlace de nombre como lo atestigua el bytecode.