Приглашаем посетить
Лермонтов (lermontov-lit.ru)

12.7. Ведение собственного каталога модулей

Назад
Глава 12 Пакеты, библиотеки и модули
Вперед

12.7. Ведение собственного каталога модулей

Проблема

Вы не хотите включать собственные модули в стандартную библиотеку расширений системного уровня.

Решение

Возможно несколько вариантов: воспользоваться параметром командной строки Perl -I; присвоить значение переменной окружения PERL5LIB; применить директиву use lib (возможно, в сочетании с модулем FindBin).

Комментарий

Массив @INC содержит список каталогов, которые просматриваются при каждс:" компиляции кода из другого файла, библиотеки или модуля командой do, require или use. Содержимое массива легко вывести из командной строки:
% perl -e 'for (@INC) { printf "%d %s\n", $i++, $_ }'
0 /usr/local/perl/lib/i686-linux/5.004
1 /usr/local/perl/lib
2 /usr/local/perl/lib/site_perl/i686-linux
3 /usr/local/perl/lib/site_perl
4 .

Первые два элемента (0 и 1) массива @INC содержат обычные платформенно-за-висимый и платформенно-независимый каталоги, с которыми работают все стандартные библиотеки, модули и директивы. Этих каталогов два, поскольку некоторые модули содержат данные или форматирование, имеющие смысл лишь для конкретной архитектуры. Например, модуль Config содержит информацию, относящуюся лишь к некоторым архитектурам, поэтому он находится в 0 элементе массива. Здесь же хранятся модули, содержащие откомпилированные компоненты на С (например, Socket.so). Однако большинство модулей находится в элементе 1 (независимый от платформы каталог). Следующая пара, элементы 2 и 3, по своим функциям аналогична элементам О и 1, но относится к конкретной системе. Допустим, у вас имеется модуль, который не поставлялся с Perl, - например, модуль, загруженный с CPAN или написанный вами. Когда вы (или, что более вероятно, ваш системный администратор) устанавливаете этот модуль, его компоненты попадают в один из этих каталогов. Эти каталоги следует использовать для любых модулей, удобный доступ к которым должен быть в вашей системе. Последний стандартный элемент, "." (текущий рабочий каталог), используется только в процессе разработки и тестирования программ. Если модули находятся в каталоге, куда вы перешли последней командой chdir, все хорошо. Если в любом другом месте - ничего не получится. Иногда ни один из каталогов, указанных в @1МС, не подходит. Допустим, у вас имеются личные модули или ваша рабочая группа использует свой набор модулей, относящихся только к данному проекту. В этом случае необходимо дополнить поиск по стандартному содержимому @INC. В первом варианте решения используется флаг командной строки -1список_ка-талогов. После флага указывается список из одного или нескольких каталогов, разделенных двоеточиями'. Список вставляется в начало массива @1МС. Этот вариант удобен для простых командных строк и потому может использоваться на уровне отдельных команд (например, при вызове простой однострочной программы из сценария командного интерпретатора). Подобную методику не следует использовать в строках #!. Во-первых, редактировать каждую программу в системе скучно. Во-вторых, в некоторых старых операционных системах имеются ошибки, связанные с ограничением длины этой строки (обычно 32 символа, включая #!). В этом случае очень длинный путь (например, й/opt/languages/Tree/extrabits/perl) приведет к появлению таинственной ошибки "Command not found". Perl пытается заново просканировать строку, но этот механизм недостаточно надежен и полагаться на него не стоит. Нередко самое удачное решение заключается в использовании переменной окружения PERL5LIB, значение которой обычно задается в стартовом сценарии интерпретатора. Если системный администратор задаст переменную в стартовом файле системного уровня, результаты будут доступны для всех пользователей. Предположим, ваши модули хранятся в каталоге -/perllib. Включите одну из следующих строк в стартовый файл командного интерпретатора (в зависимости от того, каким интерпретатором вы пользуетесь):
# Синтаксис для sh, bash, ksh и zsh $
export PERL5LIB=$HOME/perllib # Синтаксис для csh или tcsh
% setenv PERL5LIB '/perllib
Возможно, самое удобное решение с точки зрения пользователя - включение директивы use lib в начало сценария. При этом пользователям программы вообще не придется выполнять специальных действий для ее запуска. Допустим, у нас имеется гипотетический проект Spectre, программы которого используют собственный набор библиотек. Такие программы могут начинаться с команды:
use lib "/projects/spectre/lib";

Что делать, если точный путь к библиотеке неизвестен? Ведь проект может устанавливаться в произвольный каталог. Конечно, можно написать детально проработанную процедуру установки с динамическим обновлением сценария, но даже в этом случае путь будет жестко фиксироваться на стадии установки. Если позднее файлы переместятся в другой каталог, библиотеки не будут найдены.
Модуль Find Bin легко решает эту проблему. Он пытается вычислить полный путь к каталогу выполняемого сценария и присваивает его важной пакетной переменной $Bin. Обычно он применяется для поиска модулей в одном каталоге с программой или в каталоге lib того же уровня.
Рассмотрим пример для первого случая. Допустим, у вас имеется программа wherever/spectre/my prog, которая ищет свои модули в каталоге /wherever/spectrem, однако вы не хотите жестко фиксировать этот путь:
use FindBin;
use lib $FindBin::Bin;

Второй случай - если ваша программа находится в каталоге /wherever/spectre/ bin/myprog, но ее модули должны находиться в каталоге /wherever/spectre/lib:
use FindBin qw($Bin);
use lib "$Bin/. . /lib" ;

Смотри также: Документация по стандартной директиве use lib и стандартному модулю FindBin. Переменная окружения PERL5LIB описана в perl(1). Переменные окружения рассматриваются в руководстве по синтаксису командного интерпретатора.

12.8. Подготовка модуля к распространению

Проблема

Вы хотите подготовить модуль в стандартном формате распространения, чтобы им можно было легко поделиться с другом. Или, что еще лучше, вы собираетесь загрузить модуль на CPAN и сделать его общедоступным.

Решение

Начните со стандартной утилиты Perl h2xs. Предположим, вы хотите создать модуль Planets или Astronomy::0rbits. Введите следующие команды:
% h2xs -ХА -n Planets % h2xs -ХА -n Astronomy::0rbits

Эти команды создают подкаталоги ./Planets/ и ./Astronomy/Orbits/ соответственно. В каталогах находятся все компоненты, необходимые для начала работы. Флаг -n задает имя создаваемого модуля, -X запрещает создание компонентов XS (внешних подпрограмм), а -А означает, что модуль не будет использовать AutoLoader.

Комментарий

Написать модуль несложно, если знать, как это делается. Написание "правильного" модуля похоже на заполнение юридического контракта - перед вами множество мест для инициалов, подписей и дат, и все нужно заполнить правильно. Если вы что-нибудь пропустите, контракт не имеет законной силы. Вместо того чтобы нанимать специалиста, можно воспользоваться утилитой h2xs. Она создает "скелет" файла модуля с заполненными данными об авторских правах, а также другие файлы, необходимые для правильной установки и документирования модуля, для включения его в CPAN или распространения среди друзей. Название утилиты h2xs может сбить с толку, поскольку XS представляет собой интерфейс внешних подпрограмм Perl для компоновки с С или C++. Однако утилита h2xs также в высшей степени удобна для подготовки распространяемых модулей, даже если они и не используют интерфейс XS. Давайте рассмотрим один из модулей, созданных утилитой h2xs. Поскольку модуль будет называться Astronomy::0rbits, вместо команды use Orbits пользователь должен вводить use Astronomy: :0rbits. Следовательно, нам потребуется дополнительный подкаталог Astronomy, в котором будет размещаться ката..t л Orbits. Приведем первую и, вероятно, самую важную строку Orbits.pm: package Astronomy::0rbits; Команда определяет пакет (префикс по умолчанию) для всех глобальных \\h . тификаторов (переменных, функций, файловых манипуляторов и т. д.) дашк,1 файла. Следовательно, переменная @ISA в действительности является глобальи переменной Astronomy::0rbits::ISA. Как было сказано во введении, использовать команду package Orbits только потому, что она находится в файле Orbits.pm, будет ошибкой. Команда package модуле должна точно совпадать с формулировкой use или require; это означь присутствие префикса каталога, а также совпадение регистра символов. Болс' того, необходим промежуточный каталог Astronomy. Утилита h2xs позаботш-ся обо всем, включая правило установки в Make-файле. Если вы готовите модуль вручную, помните об этом (см. рецепт 12.1).
Если вы собираетесь использовать автоматическую загрузку (см. рецепт 12.10 уберите флаг -А из вызова h2xs. В результате будет создан фрагмент вида:
require Exporter;
require AutoLoader;
@ISA = qw(exporter autoloader);

Если ваш модуль использует и Perl и С (см. рецепт 12.14), уберите флаг -X из вызова h2xs. Сгенерированный фрагмент выглядит так:
require Exporter;
require DynaLoader;
@ISA = qw(exporter dynaloader);
Далее перечисляются переменные модуля Exporter (см. рецепт 12.1). Если вы пишете объектно-ориентированный модуль (см. главу 13), вероятно, вам вообще не придется использовать Exporter. Подготовка завершена. Переходите к написанию кода своего модуля. Когда модуль будет готов к распространению, преобразуйте модуль в tar-архив для удобства распространения. Для этого используется команда make dist в командном интерпретаторе (имя программы make может зависеть от системы).
%make dist

Команда создает файл с именем вида Astronomy-Orbits- 1.03-tar.Z. Чтобы зарегистрироваться в качестве разработчика CPAN, обратитесь по i,i',H су
htip://www.perl.com/CPAN/modules/04pause.html.

> Смотри также ------------------------------
h2xs(1) документация по стандартным модулям Exporter, AutoLoader, Auto-Split и ExtUtils::MakeMaker. По адресу http://www.perl.com/CPAN можно ' . : я ближайший зеркальный узел и рекомендации, касающиеся предостав-лсчия модулей.

12.9. Ускорение загрузки модуля с помощью SelfLoader

Проблема

Вам хочется быстро загрузить очень большой модуль.

Решение

Воспользуйтесь модулем SelfLoader:
require Exporter:
require SelfLoader;
@ISA = qw(exporter selfloader); # # Прочие инициализации и объявления #
__DATA__
sub abc { .... }
sub def { .... }

Комментарий

При загрузке модуля командой require или use необходимо прочитать содержимое всего файла модуля и откомпилировать его (во внутренние деревья лексического анализа, не в байт-код или машинный код). Для очень больших модулей эта раздражающая задержка совершенно не нужна, если вам нужны всего несколько функций из конкретного файла. Модуль SelfLoader решает эту проблему, откладывая компиляцию каждой подпрограммы до ее фактического вызова. Использовать SelfLoader несложно: достаточно расположить подпрограммы вашего модуля под маркером __ОАТА__, чтобы они были проигнорированы компилятором, обратиться к SelfLoader с помощью requi ге и включить SelfLoader в массив @ISA модуля. Вот и все, что от вас требуется. При загрузке модуля SelfLoader создает заглушки для функций, перечисленных в секции __DATA__. При первом вызове функции заглушка компилирует настоящую функцию и вызывает ее.
В модулях, использующих SelfLoader (или AutoLoader - см. рецепт 12.10), действует одно важное ограничение. Функции, загружаемые SelfLoader или AutoLoader, не имеют доступа к лексическим переменным файла, в чьем блоке __DATA__ они находятся, поскольку они компилируются функцией eval в импортированном блоке AUTOLOAD. Следовательно, динамически сгенерированные функции компилируются в области действия AUTOLOAD модуля SelfLoader или AutoLoader.
Как скажется применение SelfLoader на быстродействии программы - положительно или отрицательно? Ответ на этот вопрос зависит от количества функ-ичй в модуле, от их размера и от того, вызываются ли они на протяжении всего жизненного цикла программы или нет. Модуль SelfLoader не следует применять на стадии разработки и тестирова-,;|!ч модулей. Достаточно закомментировать строку __DATA__, и функции станут ггдны во время компиляции.

Смотри также -------------
Документация по стандартному модулю SelfLoader; рецепт 12.10.

12.10. Ускорение загрузки модуля с помощью Autoloader

Проблема

Вы хотите воспользоваться модулем AutoLoader.

Решение

Простейшее решение - воспользоваться утилитой h2xs для создания каталога и всех необходимых файлов. Предположим, у вас имеется каталог -/perllib, содержащий ваши личные библиотечные модули.
% h2xs -Xn Sample
% cd Sample
% perl Makefile.PL LIB=~/perllib
% (edit Sample.pm)
% make install

Комментарий

Модуль AutoLoader, как и SelfLoader, предназначен для ускорения работы программы. Он также генерирует функции-заглушки, которые заменяются настоящими функциями при первом вызове. Но вместо того чтобы искать все функции в одном файле под маркером __DATA__, AutoLoader ищет определение каждой функции в отдельном файле. Например, если модуль Sample.pm содержит две функции, too и bar, то AutoLoader будет искать их в файлах Sample/auto/foo.al и Sample/ auto/bar.al соответственно. Модули, загружающие функции с помощью AutoLoader, работают быстрее тех, что используют SelfLoader, но за это приходится расплачиваться созданием дополнительных файлов, местом на диске и повышенной сложностью. Процесс подготовки выглядит сложно. Вероятно, сделать это вручную действительно непросто. К счастью, h2xs оказывает громадную помощь. Помимо создания каталога с шаблонами Sample.pm и других необходимых файлов, утилита также генерирует Make-файл, который использует модуль AutoSplit для разделения функций модуля по маленьким файлам, по одной функции на файл. Прни-ло make install устанавливает их так, чтобы они находились автоматически. Все, что от вас нужно, - разместить функции модуля после строки __END__ (вместо строки -_DATA__ в SelfLoader), которая, как вы убедитесь, генерируется автоматически, Как и в случае с SelfLoader, разработку и тестирование модуля лучше осуществлять без AutoLoader. Достаточно закомментировать строку __END__, пока МОДУЛ!) не придет к окончательному виду.
При работе с AutoLoader действуют те же ограничения видимости файловых лексических переменных, что и для SelfLoader, поэтому использование файловых лексических переменных для хранения закрытой информации состояния не подойдет. Если вопрос хранения состояния становится настолько важным и труднореализуемым, подумайте о том, чтобы написать объектный модуль вместо традиционного.

> Смотри также
Документация по стандартному модулю SelfLoader; h2xs(1); рецепт 12.9.

12.11. Переопределение встроенных функций

Проблема

Вы хотите заменить стандартную функцию собственной версией.

Решение

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

Комментарий

Многие (хотя и не все) встроенные функции Perl могут переопределяться. К этому шагу следует относиться серьезно, но в принципе это возможно. Например, необходимость в переопределении может возникнуть при работе на платформе, которая не поддерживает эмулируемой функции. Также переопределение используется для создания интерфейсных оболочек для встроенных функций. Не все зарезервированные слова одинаковы. Те, что возвращают отрицательное число в функции С keyword () файла token.c исходной поставки Perl, могут переопределяться. В версии 5.004 не допускалось переопределение следующих ключевых слов:chop,defined,delete,do,dump, each,else,elsif, eval, exists, for, foreach,format,glob,goto,grep,if,keys,last,local,m,map,my,next,no, package,pop,pos,print, printf,prototype,push,q,qq, qw, qx, redo, return, s, scalar, shift, sort,splice,split, study, sub,tie,tied,tr,undef,unless,unshift,untie, until, use, while и у. Стандартный модуль Perl Cwd переопределяет функцию chdir. Также переопределение встречается во многих модулях с функциями, возвращающими списки: File::stat, Net::hostent, Net::netent, Net::protoent, Net::servent, Time::gmtime, Time::localtime, Time::tm, User::grent и User::pwent. Эти модули содержат переопределения встроенных функций (например, stat или getpwnam), которые возвращают объект с возможностью доступа по имени - например, getpwnam( "daemon ")->dir. Для этого они переопределяют исходные, списковые версии этих функций.
Переопределение осуществляется импортированием функции из другого пакета. Импортирование действует только в импортирующем пакете, а не во всех возможных пакетах. Простого переобъявления недостаточно, функцию необходимо импортировать. Это защищает от случайного переопределения встроенных функций.
Предположим, вы решили заменить встроенную функцию time, которая возвращает целое количество секунд, другой, возвращающей вещественное число. Для этого можно создать модуль FineTime с необязательным экспортированием функции time: package FineTime;
use strict;
require Exporter;
use vars qw(@ISA @EXPORT_OK);
@ISA = qw(exporter);
@EXPORT_OK = qw(time);
sub time() {.....}

Затем пользователь, желающий использовать усовершенствованную версию time, пишет что-то вроде:
use FineTime qw(time);
$start = time();
1 while print time() - $start, "\n";

Предполагается, что в вашей системе есть функция, соответствующая приведенной выше спецификации. Некоторые решения, которые могут работать в вашей системе, рассматриваются в рецепте 12.14.
Переопределение методов и операторов рассматривается в главе 13.

> Смотри также -------------------------------
Раздел "Overriding Built-in Functions" perlsub(1)


Назад
Вперед