не делает никакой привязки имени, как свидетельствует байт-код.

родолжение вопросаИспользование встроенного__import__() в нормальных случаяхЯ провел несколько тестов и наткнулся на удивительные результаты.

Я здесь сравниваю время исполнения классическогоimport заявление и вызов__import__ встроенная функция. Для этого я использую следующий скрипт в интерактивном режиме:

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 < t2 else ">"))

Как и в связанном вопросе, вот сравнение при импортеsysнаряду с некоторыми другими стандартными модулями:

>>> test('sys')
import statement:    0.319865173171288
__import__ function: 0.38428380458522987
t(statement) < t(function)

>>> test('math')
import statement:    0.10262547545597034
__import__ function: 0.16307580163101054
t(statement) < t(function)

>>> test('os')
import statement:    0.10251490255312312
__import__ function: 0.16240755669640627
t(statement) < t(function)

>>> test('threading')
import statement:    0.11349136644972191
__import__ function: 0.1673617034957573
t(statement) < t(function)

Все идет нормально,import быстрее чем__import__(), Это имеет смысл для меня, потому что, как я написал в связанном посте, я считаю логичным, чтоIMPORT_NAME Инструкция оптимизирована по сравнению сCALL_FUNCTIONкогда последний приводит к вызову__import__.

Но когда дело доходит до менее стандартных модулей, результаты меняются:

>>> 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)

В чем причина такой разницы во времени исполнения? Какова реальная причина, почемуimport утверждение быстрее на стандартных модулях? С другой стороны, почему__import__ работать быстрее с другими модулями?

Тесты проводят с Python 3.6

 Right leg12 сент. 2017 г., 14:06
@ cᴏʟᴅsᴘᴇᴇᴅ Я не хотел опускать ключ и влиять на потенциальных ответчиков, но ... Да, я думаю, что это связано.
 coldspeed12 сент. 2017 г., 14:05
Я предполагаю возможность кэширования встроенных модулей где-то заранее.

Ответы на вопрос(3)

Оператор import должен пройти довольно простой путь. Это ведет кIMPORT_NAME какие звонкиimport_name и импортирует данный модуль (если нет переопределения имени__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__с другой стороны, проходит через шаги общего вызова функций, которые все функции выполняют черезCALL_FUNCTION:

dis('__import__(math)')
  1           0 LOAD_NAME                0 (__import__)
              2 LOAD_NAME                1 (math)
              4 CALL_FUNCTION            1
              6 RETURN_VALUE

Конечно, он встроен и так быстрее, чем обычные функции Py, но он все еще медленнее, чемimport заявление сimport_name.

Вот почему разница во времени между ними постоянна. Используя фрагмент @MSeifert (который исправил несправедливые моменты времени :-) и добавив еще одну печать, вы можете увидеть это:

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)

На моей машине между ними есть постоянная разница около 0,17 (с небольшим отклонением, которое обычно ожидается)

* Стоит отметить, что это неименно так эквивалент.__import__ не делает никакой привязки имени, как свидетельствует байт-код.

Решение Вопроса

timeit измеряет общее время выполнения, но первый импорт модуля, черезimport или же__import__, медленнее, чем последующие - потому что это единственный, который фактически выполняет инициализацию модуля. Он должен искать в файловой системе файл (ы) модуля, загружать исходный код модуля (самый медленный) или ранее созданный байт-код (медленный, но немного быстрее, чем парсинг.py файлы) или совместно используемую библиотеку (для расширений C), выполните код инициализации и сохраните объект модуля вsys.modules, Последующие операции импорта позволяют пропустить все это и извлечь объект модуля изsys.modules.

Если вы измените порядок, результаты будут другими:

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)

Лучший способ получить непредвзятые результаты - импортировать их один раз, а затем выполнить синхронизацию:

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)

Так да,import всегда быстрее чем__import__.

 MSeifert12 сент. 2017 г., 14:36
@Rightleg Я не уверен в причине разницы в скорости, но да, байт-код будет быстрее, чем поиск имени__import__ и вызывая функцию. Даже если функция будет делать то же самое, что и байт-код. Но также может быть множество различий между фактической реализацией байт-кода и вызовом функции. Я имею в видуфункция принимает много параметров, которые не нужны оператору
 MSeifert12 сент. 2017 г., 14:32
Нет, это не так. Он только несколько раз оценивает настройку, которая представляет очистку. Однако для импорта: они кэшируются между запусками (см., Например,этот смысл.
 user235711212 сент. 2017 г., 19:56
import также смотрит вверх__import__ имя, потому что он должен проверить, если__import__ был заменен. Если__import__ не был заменен,import тем не менее, идет по быстрому пути.
 Ashwini Chaudhary12 сент. 2017 г., 14:34
@Rightleg После загрузки модуля (локально или глобально) он всегда остается вsys.modulesбудущий импорт просто извлекает его оттуда (если вы не удалите его изsys.modules), следовательно, никакой очистки не происходит. Поиск имени и вызов функции являются причинами, почему__import__ медленный.
 Right leg12 сент. 2017 г., 14:20
я думал чтоtimeit очистил контекст после выполнения ... Похоже, я ошибся. Итак, причина, почемуimport быстрее чем__import__ на самом деле связано сIMPORT_NAME инструкция байт-кода?

sys.modules после первого импорта, так что время ...

В любом случае, мои результаты выглядят так:

#!/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.586import math: 0.163__import__('math'): 0,247import six: 0.157__import__('six'): 0,273import PIL: 0.162__import__('PIL'): 0,265

Ваш ответ на вопрос