Некоторые сведения о Perl 5/Пакеты, библиотеки и модули
← Функции и процедуры | Глава | Объектно-ориентированное программирование в Perl → |
Пакеты, библиотеки и модули | ||
Пакеты
правитьПакет — это пространство имен для некоторой части программы. Каждый фрагмент кода Perl относится к некоторому пакету.
Чтобы объявить пакет, используется конструкция
package <имя-пространства-имен>;
Встроенная функция package
предписывает компилятору использовать для оставшегося кода указанное пространство имен. Область действия этого пространства имен распространяется до следующего пакета, либо до конца:
- процедуры;
- блока;
- строки, переданной функции
eval()
; - исходного файла.
Все идентификаторы, встречающиеся после пакета, автоматически связываются с ним, кроме локальных переменных, созданных с помощью my()
.
Пространство имен можно многократно менять в разных точках программы. По умолчанию предполагается, что основная программа всегда начинается с объявления пакета package main;
. Таким образом, все глобальные переменные неявно находятся в пакете main
.
К переменным пакета main
мы можем обращаться без всяких дополнительных префиксов, но для обращения к объектам других пакетов нужно использовать следующий синтаксис
<идентификатор-типа><пространство-имен>::<имя-объекта>
# Например
$PackageName::var; # Обращение к скаляру в пространстве PackageName
# Для пространства main допустимо указывать пакет явно, но это
# в общем то бесполезно, если это пространство уже действует.
# Следующие три обращения направлены на одну и ту же переменную в пространстве
# main.
$var;
$main::var;
$::var;
С помощью специальной лексемы __PACKAGE__
можно узнать какое пространство имен применяется в некоторой точке программы.
Таблица символов пакета
правитьС каждым пакетом связана отдельная таблица символов. Она представляет собой хеш-массив, имя которого образовано из имени пакета, за которым следуют два двоеточия. Например, таблица символов для пакета main
хранится в хеш-массиве %main::
.
Ключами этого хеш-массива являются идентификаторы переменных, определенных в пакете, а значениями — typeglob
-ссылки, указывающие на гнездо, состоящее из одноимённых переменных разных типов.
Таблица символов может использоваться для создания псевдонимов, так как typeglob
-ссылки обладают следующими свойствами:
*alias = *object;
# alias становится псевдонимом для всех переменных одного гнезда
# таблицы: $object, @object, %object, &object.
# Можно создать псевдоним только для конкретного объекта
*alias = \$object; # Псевдоним для скаляра $object
Функция из следующего примера позволяет распечатать таблицу символов.
#
# Аргументы:
# $1 - имя пространства для печати
# \%2 - ссылка на хеш-массив пространства имен
#
# Пример:
# printNamespace ("main", %main::);
#
sub printNamespace ($\%) {
my ($spaceName, $space) = @_;
my ($key, $item);
print "Symbols in the '$spaceName' namespace:\n";
for $key (sort keys %$space) {
local *glob = $$space{$key};
print " scalar: \$$key = $glob\n" if defined $glob;
if (@glob) {
print " array '\@$key':\n";
for $item (0..$#glob) {
print " \$$key [$item] = $glob[$item]\n";
}
}
if (%glob) {
print " hash '\@$key':\n";
for $item (sort keys %glob) {
print " \$$key {$item} = $glob{$item}\n";
}
}
print " routine '$key()'\n" if defined &glob;
}
}
package test1;
$scalar = 4;
@array = (1, 2, 3);
%hash = (name => "Larry", surname => "Wall");
sub func { return 0 }
package test2;
$scalar = 4;
@array = (1, 2, 3);
%hash = (name => "Larry", surname => "Wall");
sub func { return 0 }
package main;
printNamespace ("test1", %test1::);
printNamespace ("test2", %test2::);
Результат вывода
Symbols in the 'test1' namespace:
array '@array':
$array [0] = 1
$array [1] = 2
$array [2] = 3
routine 'func()'
hash '@hash':
$hash {name} = Larry
$hash {surname} = Wall
scalar: $scalar = 4
Symbols in the 'test2' namespace:
array '@array':
$array [0] = 1
$array [1] = 2
$array [2] = 3
routine 'func()'
hash '@hash':
$hash {name} = Larry
$hash {surname} = Wall
scalar: $scalar = 4
В этом примере мы создали два пакета test1
и test2
, в каждом из которых мы объявили скаляр, массив, хеш и функцию.
В таблицу символов пакета, отличного от main
, входят только идентификаторы, начинающиеся с буквы или символа нижнего подчеркивания. Все остальные идентификаторы относятся к пакету main
. Кроме них к main
также относятся: STDIN
, STDOUT
, STDERR
, ARGV
, ARGVOUT
, ENV
, INC
, SIG
. В этих случаях не обязательно указывать пространство имен из другого пространства.
Конструктор и деструктор пакета
правитьВ некоторых сложных пакетах может потребоваться внутренняя инициализация, например установка констант, некоторые переключения, зависящие от операционной системы, управление порядком компиляции и прочее. Для таких случаев в пакет может быть внедрен конструктор и деструктор пакета.
Конструктор пакета — это процедура, оформленная в специальном блоке BEGIN
, которая выполняется сразу после своего определения до завершения компиляции оставшейся части программы.
Деструктор пакета — это процедура, оформленная в специальном блоке END
, которая выполняется перед выгрузкой пакета.
BEGIN { <операторы> }
END { <операторы> }
Для одного пакета может быть определено несколько блоков BEGIN
и END
, причем блоки END
могут быть определены раньше BEGIN
: это не повлияет на порядок их вызова. Однако, порядок определения блоков-конструкторов и порядок определения блоков-деструкторов будет определять порядок их вызова.
Следующий пример демонстрирует, как вызываются конструкторы и деструкторы пакетов.
BEGIN {
print "Package " . __PACKAGE__ . " are loading ...", "\n";
}
END {
print "Package " . __PACKAGE__ . " are unloading ...", "\n";
}
END {
print __PACKAGE__ . ": additional destructor.", "\n";
}
package test1;
BEGIN {
print "Package " . __PACKAGE__ . " are loading ...", "\n";
}
BEGIN {
print __PACKAGE__ . ": additional consturctor.", "\n";
}
END {
print "Package " . __PACKAGE__ . " are unloading ...", "\n";
}
Результат вывода
Package main are loading ...
Package test1 are loading ...
test1: additional consturctor.
Package test1 are unloading ...
main: additional destructor.
Package main are unloading ...
Автозагрузка
правитьПри попытке обратиться к функции, которая еще не определена в пакете, интерпретатор завершает работу с выдачей сообщения об ошибке. Если в пакете определить функцию со специальным именем AUTOLOAD
, то интерпретатор будет перенаправлять вызов на нее с сохранением передаваемых параметров. Кроме того, внутри этой функции будет определен скаляр $AUTOLOAD
, в котором будет хранится имя вызываемой функции.
Способы применения этой возможности самые различные. Самое простое, так можно отслеживать вызов функции при написании прототипа новой программы или определить, что пакеты подгружаются в программу неправильно.
sub AUTOLOAD {
print "Called '$AUTOLOAD': @_\n";
}
testCall(1, 2 ,4);
undefinedFunc(1, "a", 3.1415, "b");
Результат
Called 'main::testCall': 1 2 4
Called 'main::undefinedFunc': 1 a 3.1415 b
Библиотека
правитьПакеты, как мы выяснили это выше, служат для повторного использования имен, а также для логического объединения операторов. Второй шаг — это группировка процедур и сохранение их в отдельных исходных файлах.
Для реализации этого, в Perl существует два механизма: библиотеки и модули. В этом разделе мы рассмотрим библиотечный подход.
Библиотеки появились в Perl с 4-й версии языка и являются классическим методом. Библиотека — это пакет, областью действия которого является отдельный файл. Другими словами, библиотека Perl — это исходный файл, составленный на языке Perl, первая строка которого вводит пространство имен:
package package_name;
Имя библиотечного файла обычно имеет расширение .pl
.
Основная программа загружает библиотеки при помощи директивы компилятора require
.
Загрузка библиотеки
правитьДиректива
require [<выражение>]
служит для загрузки функций из внешних библиотек во время выполнения. Если <выражение>
отсутствует, то вместо него будет использоваться переменная $_
.
Если <выражение>
является числом, то вызов соответствует требованию, что для выполнения данного сценария необходим интерпретатор perl с номером версии, не меньшим, чем указано. Самую меньшую версию, которую можно указать, это 5.005. Более ранние интерпретаторы будут прерывать исполнение с выдачей сообщения об ошибке.
Чтобы загрузить библиотеку, нужно указать имя исходного файла в выражении. Логику работы этой директивы можно выразить следующей псевдофункцией:
sub require {
my ($filename) = @_;
return 1 if $INC{$filename};
my ($realfilename, $result);
ITER: {
foreach $prefix (@INC) {
$realfilename = "$prefix/$filename";
if (-f $realfilename) {
$result = do $realfilename;
last ITER;
}
}
die "Can't find $filename in \@INC";
}
die $@ if $@;
die "$filename did not return true value" unless $result;
$INC{$filename} = $realfilename;
return $result;
}
Специальный встроенный массив @INC
содержит имена каталогов, в которых следует искать сценарии Perl, подлежащие выполнению в конструкциях:
do <файл>
;require
;use
.
В этот массив помещаются каталоги, переданные интерпретатору опцией -I
, стандартные пути к библиотечным каталогам (которые могут зависеть от операционной системы) и символическое обозначение текущего каталога .
.
Специальный встроенный хеш-массив %INC
содержит по одному элементу для каждой загруженной библиотеки с помощью do
и require
. Ключом является имя файла, в том виде, в котором оно было указано, а значением — полный путь к нему. Хеш-массив %INC
защищает программу от повторной загрузки библиотеки, так как он проверяется перед включением.
Обычно библиотечные файлы имеют расширение .pl
в своем имени. Так как в Perl есть операция конкатенации .
, чтобы не путать интерпретатор, следует всегда передавать имена файлов в кавычках:
require "mylib.pl";
Если файл не имеет расширения, то по умолчанию предполагается, что это модуль, которые имеют расширение .pm
.
Рекомендации по созданию библиотечных файлов
правитьДля создания библиотеки нужно придерживаться следующих правил:
- Создайте каталог для хранения библиотечных файлов и помещайте файлы в него по мере роста библиотеки.
- Типовая струтура библиотечного файла имеет вид:В конце файла следует поместить строку
package myspace; ... # Процедуры, функции и другое ... 1;
1;
, которая нужна для реализации вызоваdo <файл>
: несложно догадаться, что еслиdo
удается распарсить файл целиком, то наружу нужно вернуть ИСТИНУ, которая как раз возвращается единицей. - Библиотеки следует включать в основном директивой
require
. - Пути поиска пользовательских библиотек можно внедрять в программу по-разному:
- Раньше
@INC
обычно редактировался в конструкторе пакета как то так:Сейчас рекомендуется использовать директивуBEGIN { unshift(@INC, <список>) }
lib
(см. ниже). - Можно передавать пути к каталогам через опцию
-I
.
- Раньше
Модуль
правитьМодули являются дальнейшим развитием библиотек и появились начиная с Perl 5. Модуль — это библиотека, оформленная особым образом, которая определяет свои правила включения.
Модуль определяет правила экспорта и импорта. Правила экспорта предоставляют другим модулям права для импорта в них символов текущего модуля. В свою очередь, правила импорта определяют правила включения в текущий модуль символы из других модулей. Таким образом, в такой системе ожидается, что модули могут включать друг друга единственно правильным образом, а все ошибочные действия будут перехватываться как можно раньше.
Для целей управления экспортом каждый модуль должен располагать методом import()
и определить специальные массивы @EXPORT
и @EXPORT_OK
. Вызываюшая программа обращается к методу import()
, чтобы включить в себя модуль. Массив @EXPORT
содержит идентификаторы, экспортируемые по умолчанию. Специальный массив @EXPORT_OK
содержит идентификаторы, которые будут экспортироваться только в том случае, если они указаны явно в списке импорта вызывающей программы.
С появлением модулей появилась и новая директива для их подключения к основной программе — use()
.
Директива use()
правитьДиректива
use <модуль> [<список>]
use <версия>
служит для того же самого, что и require()
, но ориентирована на загрузку модулей. Она автоматически импортирует имена функций и переменных в основное пространство имен текущего пакета. Для этого в импортируемом модуле неявно вызывается метод import()
. Этот метод должен быть определен в экспортирующем модуле, либо он должен его унаследовать у модуля Exporter
. Большинство модулей не имеют своего метода import()
, поэтому они наследуют его из модуля Exporter
.
Логику работы директивы use
можно описать строкой:
BEGIN { require <модуль>; import <модуль> <список>; }
Здесь <модуль>
это слово без суффиксов, не заключенное в кавычки. Если аргументом является число <версия>
, то оно будет обозначать номер версии интерпретатора perl. Если номер версии текущего интерпретатора меньше указанного, то интерпретатор завершит работу с сообщением об ошибке.
Если при импорте модуля указан <список>
, то будут импортироваться символы, указанные в этом списке, иначе будут импортироваться символы из массива @EXPORT
, определенном в самом модуле.
Создание и подключение модуля
правитьДля создания модулей требуется немного больше кода, чем для простых библиотек. Ниже показан пример модуля, который можно использовать как шаблон.
# Файл: TestModule.pm
package TestModule; # Пространство модуля
require Exporter; # Импортируем метод import()
@ISA = qw{ Exporter }; # Для наследования из @ISA
@EXPORT = qw{ test printContent }; # Символы, экспортируемые по умолчанию
@EXPORT_OK = qw{ $scalar @array %hash }; # Символы, экспортируемые по требованию
# Функции модуля
sub test {
my ($element, $counter);
@array = @_;
$scalar = $#array + 1;
foreach $element (@array) {
$hash{"$element"} = ++$counter;
}
}
sub printContent {
foreach $key (sort keys %hash) {
print __PACKAGE__ . "::\$hash {$key} = $hash{$key}\n";
}
print __PACKAGE__ . "::Scalar = $scalar\n";
}
1;
Первые пять строк (начиная со второй) являются стандартными для любого модуля:
package TestModule;
Вводит пространство имен модуляTestModule
, исходный файл которого имеет имяTestModule.pm
.require Exporter;
Мы подключаем библиотекуExporter
, чтобы не писать методimport()
.@ISA = qw{ Exporter };
С каждым пакетом ассоциируется свой массив@ISA
, включающий имена других пакетов, представляющих классы. Если интерпретатор наталкивается на метод, не определенный в текущем пакете, он просматривает массив@ISA
, чтобы найти его среди перечисленных пакетов. В нашем примере достаточно указать только модульExporter
.@EXPORT = qw{ test printContent };
Массив символов, которые импортируются из модуляTestModule
неявно. В данном случае мы всегда неявно импортируем обе функции, которые определены в модуле.@EXPORT_OK = qw{ $scalar @array %hash };
Массив символов, которые мы разрешаем импортировать из модуляTestModule
по требованию списком. В данном примере мы разрешаем импортировать все глобальные переменные модуля. На практике, обычно, модуль имеет свои внутренние переменные, которые он никому не раскрывает.
Представим, что наш тестовый модуль лежит в некотором каталоге. Теперь напишем программу, которая его подключит и вызовет его функции.
# Файл: main.pl
use TestModule qw{ :DEFAULT $scalar };
test one, two, three, four;
printContent;
print __PACKAGE__ . ": TestModule::Scalar = $scalar\n";
Первой строкой use TestModule qw{ :DEFAULT $scalar };
мы подключаем модуль TestModule
. Кроме имени модуля, мы передаем список того, что хотим импортировать в наше приложение. В нашем примере мы передаем спецификацию :DEFAULT
, которая требует импортировать все, что перечислено в массиве @EXPORT
, а также дополнительно мы просим импортировать символ $TestModule::scalar
.
Далее мы вызываем функцию модуля test()
, которая инициализирует внутренние массивы модуля переданными значениями, а затем мы вызываем функцию печати printContent()
, которая печатает содержимое хеш-массива модуля. Благодаря тому что мы импортировали символ $TestModule::scalar
, мы можем им пользоваться в нашем приложении, как будто он существует в пространстве main
.
Вызывать нашу программу мы должны с опцией -I
, так как наш модуль не лежит в стандартном для модулей месте. Таким образом, вызов должен быть таким:
$ perl -I$(pwd) ./main.pl
Результат работы программы представлен ниже
TestModule::$hash {four} = 4
TestModule::$hash {one} = 1
TestModule::$hash {three} = 3
TestModule::$hash {two} = 2
TestModule::Scalar = 4
main: TestModule::Scalar = 4
Директива no()
правитьДиректива
no <имя-модуля> <список>
является противоположной по смыслу директиве use()
. Она отменяет действия, связанные с импортом, вызывая метод unimport <имя-модуля> <список>
.
Базовые модули Perl
правитьВ стандартной поставке Perl включено множество стандартных модулей, некоторые из которых мы перечислим в приложении (см. Некоторые сведения о Perl 5/Приложения#Библиотечные модули Perl). Помимо них, за много лет существования Perl, была создана огромная коллекция модулей CPAN (Comprehensive Perl Archive Network), доступ к которой можно получить через Интернет. Обычно вы должны сначала посмотреть не была ли решена ваша задача в виде некоторого модуля CPAN. Если такой модуль существует, вам следует загрузить его и установить в своей системе. Исходные тексты всех модулей Perl распространяются открыто, поэтому вы всегда сможете познакомиться с тем, как они работают.
Прагма-библиотеки
правитьКомпилятором Perl можно управлять с помощью специальных модулей, объявляющих особые директивы. Данные директивы обычно используются чтобы запретить некоторые конструкции языка Perl (ужесточить проверки, чтобы сделать код более предсказуемым), выводить предупреждения или отладочные сообщения и многое другое. Такие библиотеки мы называем библиотеками директив компилятора или просто прагма-библиотеками (pragmas).
Как и любые другие модули, директивы подключаются с помощью use()
и отключаются с помощью no()
.
Обычно директива применяется ко всему пакету/библиотеке/модулю, либо директивы применяют к отрезку программы. Например, чтобы ускорить вычисления, директивой integer
можно временно запретить вычисления с плавающей точкой, оставив только целочисленные:
print 2 / 3, "\n"; # 0.666666666666667
use integer;
print 2 / 3, "\n"; # 0
no integer;
print 2 / 3, "\n"; # 0.666666666666667
В дистрибутивный комплект Perl входит стандартный набор прагма-библиотек. Некоторые из них мы рассмотрим ниже.
С прагма-библиотеками следует быть осторожными, так как от релиза к релизу они могут менять поведение. Большинство учебников по Perl не успевают за этими изменениями, поэтому сведения из очень старых уже не актуальны для последних релизов языка. Этим мы хотим сказать, что не ленитесь сверяться с документацией к конкретному релизу на perldoc.perl.org.
Директива strict
правитьОчень полезная директива, которая запрещает использование небезопасных конструкций языка. Многие подходы в программировании на Perl, которые в старых релизах интерпретатора считались допустимыми, со временем стали небезопасными из-за возможности возникновения скрытых ошибок. В новых релизах документация к Perl обычно рекомендует новые подходы, не запрещая старые для сохранения обратной совместимости.
Чтобы запретить использование старых методов и не давать искушения программисту их где-либо использовать в новом коде, следует в начале любого исходного файла вводить эту директиву. Директива обычно применяется к файлу или отдельному блоку.
use strict;
# Аналогично следующим трем вызовам:
# Запрещает объявление переменных без явного указания их области видимости
use strict "vars";
# Запрещает символические ссылки
use strict "refs";
# Ужесточает правила для идентификаторов:
# если идентификатор не заключен в фигурные скобки и не стоит
# слева от =>, то нельзя его записывать без кавычек и без идентификатора типа.
# При этом, если он является именем процедуры, то это ошибкой не считается.
use strict "subs";
Ниже приведены антипримеры того, что запрещает директива.
# Примечание: везде, где написано НЕЛЬЗЯ, оператор запретит директива.
use strict 'refs';
$ref = \3.1415;
print $$ref, "\n"; # МОЖНО: разыменовывается жесткая ссылка.
$ref = "foo";
print $$ref, "\n"; # НЕЛЬЗЯ: попытка разыменовать символическую ссылку.
$file = "STDOUT";
print $file "Hi!"; # НЕЛЬЗЯ: без запятой после $file и без директивы, интерпретатор
# попытался бы разрешить символическую ссылку, однако сейчас они
# запрещены директивой, поэтому эта строка не скомпилируется.
# ПРАВИЛЬНО
$file = *STDOUT;
print $file "Hi!";
# Есть одно ИСКЛЮЧЕНИЕ из правил.
$bar = \&{'foo'};
&$bar; # МОЖНО: ссылка на функцию разрешится как символическая.
# Примечание: везде, где написано НЕЛЬЗЯ, оператор запретит директива.
use strict 'vars';
$main::var = 1; # МОЖНО: есть квалификатор пакета
$var1 = 2; # НЕЛЬЗЯ: область видимости не указана явно.
our $var1 = 2; # МОЖНО: our определяет область видимости пакета.
my $lexical = 3; # МОЖНО: my определяет лексическую область видимости.
local $var1 = 4; # МОЖНО: потому что переменная $var1 была к этому моменту определена.
local $var2 = 5; # НЕЛЬЗЯ: переменная $var2 должна быть определена к этому моменту,
# даже несмотря на то, что local определяет видимость.
package MyPackage;
our $var3 = 6; # МОЖНО: our определяет область видимости пакета.
# Примечание: везде, где написано НЕЛЬЗЯ, оператор запретит директива.
use strict 'subs';
sub func { return 0 }
$scalar = string; # НЕЛЬЗЯ: нужны кавычки для литерала.
$scalar = "string"; # ПРАВИЛЬНО
func 3, arg2; # НЕЛЬЗЯ: второй аргумент должен быть закавычен.
func 3, "arg2"; # ПРАВИЛЬНО
$ref = \&func; # МОЖНО: потому что func это имя функции.
До версии интерпретатора 5.36 эту директиву нужно было прописывать всегда явно. Начиная с версии 5.36 достаточно писать следующим образом:
use v5.36; # Директива 'strict' включается неявно.
...
Здесь же отметим, что до недавнего времени (а конкретнее до Perl 5.6.0) при включенной use strict 'vars';
, глобальные переменные нужно было объявлять через директиву vars
, например
use vars qw($frob @mung %seen);
С версии Perl 5.6.0 глобальные переменные пакета следует объявлять исключительно через функцию our
.
Директива lib
правитьДиректива lib
позволяет редактировать массив @INC
из программы. Напомним, что этот массив используется для определения путей, по которым нужно искать модули и библиотеки.
# Добавит список к тому, что уже есть в массиве.
use lib <список-директорий>;
# Очистит @INC целиком
no lib;
Вы можете только добавлять конкретные пути в массив. С помощью директивы удалить можно только сразу все пути. Следует аккуратно удалять пути, так как некоторыми из них могут пользоваться уже загруженные модули.
Вернемся к примеру, где мы писали свой модуль. В основной программе мы могли бы добавить нужные директивы для поиска модуля.
use strict; # Добавим директиву, чтобы ужесточить проверки.
use lib qw{ . }; # Добавим текущую рабочую директорию в @INC.
use TestModule qw{ :DEFAULT $scalar };
test "one", "two", "three", "four";
printContent;
print __PACKAGE__ . ": TestModule::Scalar = $scalar\n";
Директива subs
правитьДанная директива позволяет определить процедуры до их фактического определения. В основном это используется для того, чтобы вызывать их без скобок вокруг аргументов и без идентификатора &
.
use subs <список-имен-процедур>;
Пример
example1 1, 2, 3; # НЕЛЬЗЯ: функция еще к этому моменту не определена.
example1 (1, 2, 3); # ПРАВИЛЬНО: в этой точке скобки обязательны.
use subs qw{ example1 example2 example3 };
example1 1, 2, 3; # МОЖНО: функция как бы определена к этому моменту через директиву.
sub example1 { return 0 }
sub example2 { return 0 }
sub example3 { return 0 }
← Функции и процедуры | Объектно-ориентированное программирование в Perl → |