Питон: импорт и модули - часть 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__.pya.pylinux2b.py
win32b.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__.pya.py-
__pycache____init__.cpython-32.pyca.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 многое идет "в коробке".
Бесподобно!!! Браво! Всё очень понятно и просто даже для начинающего меня:). Отдельно спасибо за специфический юмор:).
ОтветитьУдалитьКушайте на здоровье.
ОтветитьУдалить