суббота, 22 мая 2010 г.

Импорт в Питоне

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

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

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

Эта серия статей попытается заполнить образовавшийся пробел.

В изложении я буду опираться на Python Enhancement Proposals и исходный код CPython. Рассматривается только CPython, он же "Питон как таковой". Возможно, Jython, IronPython, PyPy имеют отличия - я серьезно с ними не работал и, по большому счету, эти замечательные штуки меня мало интересуют).

Механизм импорта постоянно (хотя и не очень быстро) развивается, поэтому в необходимых случаях будут приводится номера версий Питона, после которых стало доступно то или иное расширение. Большая часть изложенного подойдет для ветки 2.х - но, конечно, мне трудно удержаться от упоминания всех "вкусностей", которые появились (и продолжают вводится) в py3k.

Отдельно нужно сказать о Python 3.2. На момент публикации эта ветка находится в стадии разработки. Доступна как python 3.2. Нестабильна и все еще может довольно значительно поменяться.

Введение закончено. Приступим к делу.

Определения

  • модуль (module) - базовое понятие языка. Содержит код и глобальные переменные. Исполняется при загрузке. Модули бывают написанные на питоне (.py файлы), C Extensions (.pyd и .so файлы, поддерживающие определенный интерфейс) и встроенные в интерпретатор (технически выполненные как C Extensions). Об определении модуля можно писать долго, но этот объект хорошо знаком даже начинающим программистам. Замнем для ясности.
  • пакет (package) - разновидность модуля, используемая для собирания модулей в иерархическую древовидную структуру. Классические папки с __init__.py внутри.

При

import a.b.c

будут последовательно выполнены

import a
import a.b
import a.b.c

Это нужно знать.

Стандартные атрибуты модуля

Строки вроде

import os
from xml import dom

импортируют модуль (os и xml.dom соответственно) и добавляют os и dom в текущее пространство имен.

os и dom - объекты типа "модуль":

>>> os
<module 'os' from '/usr/lib/python2.6/os.pyc'>

У каждого модуля есть набор специфических атрибутов:

  • __name__: str - полное имя модуля. Путь от начала с точками как разделителями. Например, 'xml.dom' или 'xml.dom.minidom'
  • __doc__: str - описание (так называемый docstring)
  • __file__: str - полный путь к файлу, из которого модуль был создан (загружен). До версии Python 3.2 это путь к .py или .pyc (записанный на диск кеш, результат автоматической компиляции кода модуля, используется для ускорения загрузки) - '/usr/lib/python2.6/xml/dom/__init__.pyc'. Начиная с 3.2 __file__ всегда указывает на исходный .py файл '/home/andrew/projects/py3k/Lib/xml/dom/__init__.py'. Встроенные модули не содержат этот атрибут. Для C Extensions __file__ указывает на имя .so или .pyd файла - '/usr/lib/python2.6/dist-packages/libvirtmod.so'. Впрочем, некоторые библиотеки этим пренебрегают и ведут себя как встроенные - жизнь несовершенна.
  • __path__: [str] - список файловых путей, в которых находится пакет. Существует только для пакетов. Об этом атрибуте я расскажу чуть позже более подробно.
  • __cached__: str - [3.2+] нововведение, появившееся в Python 3.2. Путь к .pyc файлу. Читайте дальше.
  • __package__: str - [2.5+] имя пакета, в котором лежит модуль (пустая строка для модулей верхнего уровня). Появился для поддержки относительного импорта from . import a. Текущая реализация содержит довольно серьезный баг: атрибут остается установленным в None в случаях абсолютного импорта по полному пути. PEP 328 - Imports: Multi-Line and Absolute/Relative.
  • __loader__: Loader - [2.3+] ссылка на объект, который выполнял загрузку данного модуля. Присутствует только для тех модулей, которые были обработаны через механизм расширения импорта. Будет рассмотрен в дальнейших статьях. Можете про него забыть на какое-то время.

Первые три атрибута тривиальны. На оставшиеся стоит обратить внимание.

Пакеты

Поддержка пакетов и питоне довольно интересная. Рассмотрим нетривиальный пример.

Допустим, мы должны поддерживать разные операционные системы.

При этом у нас есть код (скажем, ctypes вызовы) которые имеют смысл только для Windows, а в linux нужно писать совсем иначе.

Классический подход громоздит много if и elif (думаю именно потому, что авторы не всегда владеют Питоном в полной мере).

Существует и более элегантное решение:

  • package
    • __init__.py
    • a.py
    • linux2
      • b.py
    • win32
      • b.py

Мы кладем "общий" код непосредственно в package, а платформозависимый разносим по вложенным (технически они могут находится где угодно) папкам. Обратите внимание - linux2 и win32 не содержат __init.py__ и не являются вложенными пакетами.

А в __init__.py пишем что-то вроде:

import sys
from os.path import join, dirname
__path__.append(join(dirname(__file__), sys.platform))

В результате package.__path__ будет выглядеть как ['.../package', '.../package/linux2'], а windows specific модули в него не попадут и не смогут импортироваться при всем желании. Модульный полиморфизм в действии, долой if/else - частый признак плохого дизайна.

Просто делайте

from package import b

и получите то версию, работающую у вас. Другие - не сможете увидеть.

Кеширование модулей

Загрузка модуля из исходного .py файла - довольно накладное занятие. Нужно построить по питоновскому тексту исполняемый code block, создать модуль и выполнить код в его пространстве имен. Синтаксический анализ трудоемок, поэтому CPython использует кеширование. Когда программа просит загрузить модуль a, то питон смотрит на файлы a.py и a.pyc. Последний содержит уже скомпилированный код.

Если a.pyc существует и он не старее (смотрим дату модификации файлов) чем a.py - радостно используем этот кеш. Иначе делаем все медленно и печально, заодно создавая или перезаписывая a.pyc для последующего использования.

Теперь о том, что поменялось.

С самых бородатых времен оба файла лежали рядом. Это создавало определенные неудобства. Дело в том, что на компьютере можно установить несколько разных версий Питона, а .pyc файл один. Каждая версия добавляет/изменяет свой байткод, поэтому .pyc файлы оказываются несовместимыми.

Решают этот вопрос по разному. Обычно новые модули устанавливают в pythonx.x/site-packages. Для другой версии питона будет другой путь, и никто не подерется.

Все хорошо, но стороннюю библиотеку придется установить несколько раз - для каждой версии Питона заново.

Как по мне - это лучший выход, но ребята из Debian/Ubuntu пошли своим путем. Описывать его не буду, кто знает - поймет. Остальным это не нужно. Скажу лишь, что их способ взрывает мозг.

К счастью, вышел новый PEP 3147 - PYC Repository Directories, который реализован в Python 3.2.

Теперь все будет выглядеть так:

  • package
    • __init__.py
    • a.py
    • __pycache__

      • __init__.cpython-32.pyc
      • a.cpython-32.pyc

Внутри каждого пакета появляется папка __pycache__ для складывания в нее .pyc файлов.

Эту папку не прячут, начиная имя с точки и не устанавливают hidden attribute.

Кеш-файлы имеют имена вида <name>.<tag>.pyc. С именем все ясно. tag закодирован так, чтобы содержать версию питона - cpython-32: CPython версии 3.2. Оставлено место под другие разновидности Питона. Например, Unladen Swallow имеет несовместимый байткод - поэтому он сможет использовать свой уникальный префикс.

>>> dom.__cached__
'/home/andrew/projects/py3k/Lib/xml/dom/__pycache__/__init__.cpython-32.pyc'

Еще раз повторюсь - с выходом 3.2 __file__ указывает на .py файл, а не на .pyc.

Ремарка для педантов - читайте PEP 3174 для включения только .pyc в дистрибутив. Не люблю...

Резюме

Вот и все. Мы подробно прошлись по атрибутам модуля - какие они были и есть, что добавилось.

В следующей части я расскажу, где Питон находит модули и пакеты.

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