Питон: импорт и модули - часть 2.

Питон: импорт и модули - часть 2.

Простой ответ - из sys.path - будет лишь отчасти верным.

Полный список должен содержать:

  • __import__
  • sys.modules
  • sys.path
  • .pth файлы
  • sys.meta_path
  • sys.path_hooks
  • sys.path_import_cache

Давайте рассмотрим все по порядку.

__import__

Любая форма импорта, а их с точки зрения CPython три:

сводится к вызову функции __import__(name:str, globals:dict=, locals:dict=, form_list:list=[], level:int=-1) -> types.ModuleType

  • name - имя модуля. С точками внутри, если нужно.
  • globals - глобальное пространство имен блока, который загружает модуль. О пространствах имен я здесь рассказывать не буду - тема для отдельной большой статьи. Упомяну лишь, что его можно получить через вызов globals()
  • locals - локальное пространство имен, locals() . При импорте не используется.
  • from_list - список имен, которые нужно получить из импортируемого модуля.
  • level - уровень вложенности. Используется для относительного импорта.

Честно говоря, эта функция весьма запутана и ее использование довольно нетривиально. По ней можно отследить развитие механизма импорта в Питоне - модули, имена из модулей, относительный импорт. При разном наборе параметров она дает различный результат. Не люблю.

Описывать подробно все не хочу - читайте стандартную документацию.

Замечу, немного забегая вперед - начиная с Python 3.1 появился замечательный пакет importlib, в котором есть удобная importlib.import_module .

__import__ реализован в Python/import.c - довольно большой файл.

  • блокировки потоков.
  • работа с модулями
  • поддержка пакетов
  • импорт модулей, написанных на Питоне
  • поддержка C Extensions
  • работа с кешем питоновских модулей ( .pyc файлы)
  • встроенные ( builtins ) и замороженные ( frozen ) модули. Последние могут быть интересны для разработчиков систем, в которые Питон вшит внутрь (python embedding)
  • расширения (import hooks)
  • платформозависимый код - на linux все выглядит немного иначе, чем на Windows. А в import.c еще есть код, специфичный для MacOS и OS/2.

Следует упомянуть еще один стандартный модуль - imp . Он содержит набор низкоуровневых функций, необходимых для работы импорта. Я не буду давать его полное описание - но в дальнейшем упомяну ряд интересных функций.

При изучении внутренней реализации следует всегда помнить, что импорт развивался долго и трудно. Нововведения обязаны были поддерживать обратную совместимость с уже имеющимся кодом. Текущая картина представляет результат этого компромисса.

При этом часть заложенных возможностей все еще не реализована - особенно это касается расширений, которые хоть и стали стандартным механизмом начиная с Python 2.3, все еще выглядят как сторонняя надстройка. Когда-нибудь весь импорт будет построен на расширениях (Python 3.3?)

Как бы то ни было, в изложении я буду описывать текущее состояние дел, показывая где нужно "идеальную картинку", к которой все рано или поздно придет.

Я буду в основном говорить о модулях, написанных на Питоне .py файлах, если отдельно не будет упомянуто. C Extensions - отдельная очень интересная тема, о которой можно писать очень долго.

Преамбула закончена. Приступим к детальному рассмотрению.

Блокировка

Импорт модуля изменяет глобальные переменные (в первую очередь sys.modules . Чтобы избежать возможные накладки, получающиеся при параллельной загрузке модулей из разных потоков, используется блокировка.

В модуле imp для этого существуют три функции:

  • imp.acquire_lock() - взять блокировку
  • imp.release_lock() - отдать ее
  • imp.lock_held() - проверить, взята ли?

Это выглядит так: первым делом __import__ берет блокировку, затем загружает модуль.

Модулей может быть несколько: помните - import a.b.c превращается в

Т.е. при загрузке модуля из пакета сначала загружаются модули верхних уровней. При этом каждый модуль может содержать другие импорты, которые будут выполнены.

Строго говоря, этот процесс выглядит так:

  • создать модуль
  • создать код для этого модуля (преобразовать питоновский текст в байт-код)
  • выполнить этот байт-код в глобальном пространстве имен модуля. Подробности - в следующих статьях этой серии.

После загрузки модуля блокировка снимается.

Мне никогда не приходилось работать с функциями блокировки напрямую. Импорт сам делает все, что нужно, но знать о блокировках необходимо.

Обычно модули импортируются как first level statemets в начале вашего файла.

Но инструкцию импорта можно писать и внутри функции.

Это делается для отложенной загрузки. Например, для того чтобы разорвать циклическую зависимость модулей. Или, как в случае с twisted, работать с реактором только после того, как был выбран его тип (select, poll, epoll и т.д.)

И все выглядит прекрасно, если ваши функции с import statement внутри работают в одном потоке - лучше всего в главном.

В случае многопоточной программы, интенсивно использующей свои блокировки, можно все подвесить - так называемый dead lock. Я встречал такую ситуацию в моей практике пару раз. Мультипоточность не является темой данной серии статей. Если вы пишите такую программу, то должны знать о взаимных блокировках. Пожалуйста, учитывайте при разработке еще и блокировку импорта.

Памятка: импорт уже загруженного модуля быстрый. Но не мгновенный - Питон возьмет блокировку проделает несколько операций, прежде чем вернуть уже загруженный модуль.

sys.module s

Словарь уже загруженных в Питон модулей.

Давайте глянем на него подробней.

Конечно, я пожалел читателя и нещадно порезал ту бесконечность, которую питон должен загрузить перед началом своей работы.

Итак, что мы видим.

  • builtins - встроенные модули, у которых отсутствует имя файла:
    • __builtin__
    • обязательный __main__ (это ваш файл, с которым вы запустили python)
    • sys - много вкусного
    • zipimport - для загрузки модулей, хранящихся в zip архивах
    • _ctypes , указывающий на _ctypes.so

    Импорт складывает загруженные модули в sys.modules .

    Еще раз подчеркну: если модуль уже там лежит - он быстро возвращается (но блокировка все равно берется).

    Импорт: абсолютный, относительный и непонятный

    Технически есть два вида: абсолютный и относительный.

      При абсолютном следует указывать имя модуля начиная с самого верха: import a.b.c

    Потом появился относительный (2.5+):

    Наверное, уже все успели с ним познакомится. На самом деле очень удобно: точка означает папку, в которой лежит импортирующий модуль. Две точки подряд - прыжок на уровень выше.

    Именно для этого появился последний параметр level в __import__ : он показывает, на сколько уровней вверх нужно заглянуть, чтобы загрузить name .

    К сожалению, и тут не все гладко. В старых (до 2.5) питонах относительный импортов не было. Поэтому при import os питон сначала пытался загрузить os.py в той папке, где находился вызывающий модуль. Если файла не нашлось (а чаще всего так и бывает), то питон будет искать модуль по абсолютному пути. А чтобы не обращаться к файловой системе опять (время дорого) - в sys.modules вставится заглушка:

    Обратите внимание: encodings.__builtin__ и encodings.codecs указывают на None . Это значит, что питон будет при следующей попытке искать __builtin__ и codecs по абсолютному пути.

    Добавлю, что начиная с 2.7+ "компромиссный" способ невозможен. Пишите либо полный путь, либо указывайте его явно с точки. И это замечательно!

    sys.path:[str]

    Начиная разговор о том, где Питон находит новые модули, невозможно пропустить sys.path . Все с него начинается и часто им же и заканчивается.

    sys.path представляет собой список файловых путей, в которых лежат модули.

    Как видим, сюда попадает прежде всего сам питон, установленные библиотеки и мои собственные проекты.

    Поиск модуля ведется с начала списка, и не случайно первой стоит точка (текущая папка). Модуль из текущей папки загрузится первым, перекрыв остальные.

    Поэтому не пытайтесь создавать свои модули с именами pickle или urllib - они перекроют стандартные и вы получите странную ошибку при импорте.

    sys.path можно изменять из питоновского кода, чтобы подключить ваши модули и пакеты.

    Крайне не советую это делать - лучше писать distutils скрипт setup.py , который установит вашу чудесную библиотеку в питон.

    Конечно, меня сразу же поправят - делать distutils неудобно. Согласен, используйте distribute , setuptools , paver , enstaller - что вам больше по душе.

    По этому поводу написано немало статей, а мы все же рассматриваем сейчас немного другой вопрос. Последние два года Тарик Зиаде интенсивно занимается переписыванием distutils с целью учесть все недостатки и создать по настоящему замечательную штуку. Удачи ему.

    Как бы то ни было, нужно понимать способ, которым наполняется sys.path .

    В первую очередь питон добавляет текущую папку и стандартную библиотеку (папка Lib, если смотреть на питоновские исходники).

    Затем следует импорт site.py .

    site.py

    Предназначен для настройки Питона. Большая часть файла занимается добавлением путей в sys.path . Не поленитесь, откройте его в текстовом редакторе и рассмотрите. Это не больно.

    Чтобы узнать, где он лежит - сделайте

    На первый взгляд содержимое представляет дикую мешанину из различных способов расширения. На второй взгляд - тоже. Что поделать - цена обратной совместимости и отражение развития представлений об импорте.

    При этом поставщики различных дистрибутивов могут немного подкручивать его содержимое. Особенно этим славятся Debian и Ubuntu. Использую - но плАчу, как тот ёжик.

    Позвольте мне остановится на "минимальном стандартном наборе", а все многочисленные тонкости изучайте сами.

    Итак, это в первую очередь site-packages - обычно папка внутри стандартной библиотеки питона. Сюда устанавливаются сторонние библиотеки, которые не поставляются вместе с питоном.

    Начиная с Python 2.6 поддерживаются еще и локальные пользовательские папки:

    /.local/lib/python2.6/site-packages или %APPDATA%/Python/Python26/site-packages для Windows.

    Для детального изучения читайте PEP 370: Per-user site-packages Directory и внимательно изучайте ваш site.py . Дело в том, что для новых версий схема может быть иной -

    /.local/lib/python.3.1/site-packages . Различия, впрочем, невелики.

    Более интересны так называемые .pth файлы, которые могут содержаться в site-packages .

    Дело в том, что сторонние пакеты могут иметь разную структуру.

    • dpkt-1.6
      • AUTHORS
      • CHANGES
      • README
      • dpkt
        • __init__.py
        • dpkt.py
        • dhcp.py
        • example-1.py
        • test-perf.py

        Для import dpkt нужна папка dpkt-1.6, в которой уже есть пакет dpkt с __init__.py внутри. Поддерживать два дерева каталогов "для разработки" и "для питона" неудобно.

        Поэтому можно положить в site-packages файл dpkt.pth , содержащий путь к папке, внутри которой будет питоновский пакет dpkt .

        site.py пройдется по всем .pth файлам и обработает их.

        Обработка в данном случае заключается в следующем:

        • все строки, начинающиеся с # - комментарии
        • строка, начинающаяся с import должна быть исполнена. После точки с запятой, отделяющих новую команду - можно писать любой код. Грязный хак, облегчающий жизнь в некоторых ситуациях
        • все прочие строки добавляются в sys.path

        Обратите внимание - путь может указывать куда угодно, в том числе и на вашу папку, в которой вы держите рабочие проекты.

        Подчеркну, еще раз, что создавать самому .pth файлы - моветон.

        Делайте правильные setup.py , используйте distribute , регистрируйте разрабатываемые вами библиотеки через python setup.py develop . Еще лучше применяйте при этом virtualenv .

        Я рассказал о .pth файлах только в рамках общего обзора импорта модулей.

        Последним шагом site.py делает import sitecustomize . sitecustomize.py обычно кладут в ту же папку, где расположен запускаемый питоновский скрипт. Это позволяет настроить интерпретатор перед запуском кода этого скрипта (подкрутить тот же sys.path к примеру).

        Никогда так не делайте. При правильной организации проекта такой трюк не нужен. Зато я видел много проблем у тех, кто пытался использовать sitecustomize . Не хочу подробно на них останавливаться - и так много негативных посылов в этой части. Будут просьбы - расскажу все очень подробно на предметном материале.

        Импорт и главный модуль.

        Не могу обойти вниманием __main__.py . Так называется модуль, который вы непосредственно запускаете через python <script.py> .

        Также в конце этого модуля считается правилом хорошего тона писать

        чтобы вызвать функцию main только тогда, когда файл используется как скрипт.

        На самом деле, конечно, можете писать и вызывать что угодно.

        Смысл этого блока в том, чтобы делать вызов main() только тогда, когда мы запускаем скрипт непосредственно (из командной строки, кликая по нему мышкой и т.д.)

        Обычно это ведет к разбору аргументов командной строки и отработке программы (выводу на консоль, созданию окошек GUI и прочее).

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

        Есть несколько способов запустить скрипт:

        • указать его явно в командной строке. Тривиально.
        • написать python -m unittest . (2.4+) - в данном случае запустить юниттесты для нашей папки, в которой лежат тестовые сценарии.

        Последний механизм подправляли в 2.5 и 2.6:

        Наиболее интересен последний PEP. Дело в том, что 2.5 стал поддерживать относительные пути импорта (которые начинаются с точки). Но __main__ - модуль верхнего уровня. "Выше" быть ничего не может а "рядом" лежат модули из стандартной библиотеки.

        Поэтому в 2.6 ввели атрибут модуля __package__ :

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

        Последняя малоизвестная часть относится к импорту из zip архивов PEP 273: Import Modules from Zip Archives

        Если вы положите файл с именем __main__.py в такой архив, то можно запустить его через python <achive.zip> .

        Я еще раз призываю строить разработку основываясь на библиотеках, пакетах и модулях, а не на файлах, папках и архивах. Разница довольно тонкая, но очень существенная.

        Тем не менее могут быть случаи, системному администратору удобно использовать именно этот подход:

        • его "скриптик" вырос и не помещается в один .py файл.
        • тем не менее он еще не дорос до "большой библиотеки" со всем полагающимся оформлением.

        Заключение

        За рамками статьи остается PEP 382: Namespace Packages и много интересных особенностей, относящихся к sys.path .

        К сожалению эта тема настолько обширна и запутана, что я просто не в силах рассказать обо всем сразу.

        Следующая статья из серии будет посвящена беглому обзору того, как Питон обрабатывает расширения импорта (знаменитый PEP 302).

        И только потом я смогу перейти (наконец-то. ) к собственно разговору о том, как писать import hooks и зачем они могут быть нужны "простому программисту".