Питон: импорт и модули - часть 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
- вы глубоко заблуждаетесь.
Андрей, спасибо за полезные статьи по питону. К сожалению, русскоязычных хороших статей по питону в сети я много не нашел. я только начал изучать питон, прикрутил mod_wsgi. Конечно, язык поражает своей масштабностью и лаконичностью. Хочу написать сайт на питоне, но пока не разобрался, как это сделать через mod_wsgi. Очень много всяких модулей для авторизации и т.п. Может, как-нибудь напишете про wsgi? Зарание спасибо.
ОтветитьУдалитьПро wsgi не напишу.
ОтветитьУдалитьПо ряду причин
Сайт пишите на Django. Он прекрасно работает с wsgi.
ОтветитьУдалитьАндрей могу узнать каким редактором пользуешься?
ОтветитьУдалитьпосле довольно долгих метаний emacs всех уверенно заборол
ОтветитьУдалитьСпасибо за ответ.
ОтветитьУдалитьмои попытки в освоении emacs Вы укрепили еще раз :) Спасибо
Если возможно, можете показать Ваш конфиг emacs.
PS
emacs-ом используются действительно крупные фигуры it такие как Гвидо, Линус, Matsumoto(ruby), Андрей Светлов итд
Не стоит меня ставить в линейку действительно выдающихся людей.
ОтветитьУдалитьКонфиги: мой не дам - там почти ничего нет :)
Хороший конфиг у Сани Соловьева
http://hg.piranha.org.ua/conf/file/518e8f8f4d78/.emacs.d
На http://www.python.su/forum/viewforum.php?id=27 несколько раз поднимали тему. Поиск вам поможет.
На самом деле сложность емакса только одна - нет менюшек и неясно, что он умеет а что нет. Какой модуль нужно поставить, команду - выполнить, а кнопку - нажать.
Со временем это проходит, получается задавать правильные вопросы и google быстро дает ответ.
К тому же в последней версии 23 многое идет "в коробке".
Бесподобно!!! Браво! Всё очень понятно и просто даже для начинающего меня:). Отдельно спасибо за специфический юмор:).
ОтветитьУдалитьКушайте на здоровье.
ОтветитьУдалить