Требуются модули Perl с использованием псевдонима - PullRequest
5 голосов
/ 25 апреля 2019

*** Ниже приведена справочная информация, чтобы помочь объяснить, что я пробовал до сих пор Если вы предпочитаете сначала прочитать основной вопрос, перейдите к нижней части. ***


Начиная с

Мой Baz модуль вызывает ряд других модулей, все похожие, каждый из которых находится на один уровень ниже в пространстве имен. Интересующие нас здесь составляют роль Thing. В дополнение к отдельным операторам require в списке констант ALL_THINGS перечислены соответствующие модули Thing для последующего использования. Мой оригинальный код выглядит так:

package Foo::Bar::Baz;

use constant ALL_THINGS => qw{ Foo::Bar::Baz::ThingA Foo::Bar::Baz::ThingB ... };

require Foo::Bar::Baz::ThingA;
require Foo::Bar::Baz::ThingB;
[...]

Устранение избыточности

Как я уже говорил, модулей Thing довольно много, и я все еще добавляю больше. Каждый раз, когда я создаю новый класс Thing, я должен добавить новый оператор require, а также добавить тот же идентичный текст в список ALL_THINGS. Чтобы избежать этого дублирования, я хотел заменить отдельные строки require циклом, повторяющимся по ALL_THINGS. Я добавил это, которое прекрасно работает само по себе:

foreach my $module (ALL_THINGS) {
    eval "require $module";
}

Однако, похоже, это решение не совсем подходит для моих следующих изменений.


Улучшение читаемости

Полное имя модуля для каждого Thing длинное и громоздкое. Я хотел бы присвоить псевдониму имя пакета, чтобы его было легче набирать / читать. Я посмотрел на Package::Alias, но, похоже, их будет use, которых я бы хотел избежать, если это возможно. Лучшее решение, к которому я пришел, это шаблон, предложенный в этом вопросе :

BEGIN { *Things:: = *Foo::Bar::Baz:: ; }

Это также работает, в том смысле, что позволяет мне использовать Thing::ThingA->classMethod. Однако неудивительно, что он не работает в вышеуказанном цикле require, так как require Thing::ThingA ищет @INC для Thing/ThingA.pm, а не Foo/Bar/Baz/ThingA.pm.


Главный вопрос: собрать их вместе

Я хотел бы сократить длинные имена пакетов (например, Foo::Bar::Baz::ThingA) в моем списке ALL_THINGS до Things::ThingA, но все же иметь возможность использовать этот же список для построения моих операторов require в цикле ,

  • Есть ли другой способ использовать псевдоним Foo::Bar::Baz:: как Things::, чтобы я мог require Things::ThingA?
  • Или, если я правильно делаю часть псевдонима, есть ли способ разыменовать Things::ThingA на Foo::Bar::Baz::ThingA в (или раньше?) В eval, чтобы require нашел правильный пакет?
  • Существует ли какой-либо другой общепринятый метод связывания пакетов на разных уровнях одного и того же пространства имен, чтобы устранить необходимость во всем этом?

Бонусные вопросы (связанные с eval "require $x"):

  • В perldoc для константы говорится, что списки констант на самом деле не только для чтения. Создает ли это проблему безопасности при использовании eval?
  • Если это так, есть ли более безопасный способ сделать это без необходимости загружать дополнительные модули?
  • Будучи несколько новичком в Perl, есть ли еще какие-то тонкие различия, которые я мог бы пропустить между этим подходом и моим предыдущим (отдельные операторы require для каждого модуля)?

Примечание: я принял ответ Дейва Шерохмана, так как он наиболее полно отвечает на вопрос, который я задал. Тем не менее, я в конечном итоге реализовал решение, основанное на ответе лордадмиры.

Ответы [ 5 ]

3 голосов
/ 25 апреля 2019

Насколько черный тебе нравится твоя магия?

Мы все знаем, что для require модулей Perl просматривает @INC, чтобы найти файл, который он хочет загрузить. Один из малоизвестных (и даже менее используемых) аспектов этого процесса заключается в том, что @INC не ограничивается только путями файловой системы. Вы также можете поместить туда coderefs, что позволит вам захватить процесс загрузки модуля и подчинить его вашему желанию.

Для описанного вами варианта использования что-то вроде следующего (непроверенного) должно сработать:

BEGIN { unshift @INC, \&require_things }

sub require_things {
  my (undef, $filename) = @_;

  # Don't go into an infinite loop when you hit a non-Thing:: module!
  return unless $filename =~ /^Thing::/;

  $filename =~ s/^Thing::/Foo::Bar::Baz::/;
  require $filename;  
}

По сути, это первая запись в @INC, которая просматривает имя запрашиваемого модуля и, если он начинается с Thing::, вместо этого загружает соответствующий модуль Foo::Bar::Baz::. Просто и эффективно, но действительно легко спутать с будущими программистами по обслуживанию (включая вас самих!), Поэтому используйте с осторожностью.


В качестве альтернативного подхода у вас также есть возможность указать имя модуля в модуле, которое не соответствует физическому пути к файлу - они обычно одинаковы по соглашению, чтобы упростить жизнь при чтении и поддержание кода, но нет никаких технических требований для их соответствия. Если файл ./lib/Foo/Bar/Baz/Xyzzy.pm содержит

package Thing::Xyzzy;

sub frob { ... };

тогда вы бы использовали его, выполнив

require Foo::Bar::Baz::Xyzzy;
Thing::Xyzzy::frob();

и Perl будет совершенно доволен этим (даже если ваши коллеги могут и не быть).


Наконец, если вы хотите избавиться от ALL_THINGS, взгляните на Module :: Pluggable . Вы даете ему пространство имен, затем он находит все доступные модули в этом пространстве имен и выдает вам их список. Он также может быть установлен на require каждый модуль, как он найден:

use Module::Pluggable require => 1, search_path => ['Foo::Bar::Baz'];
my @plugins = plugins;

@plugins теперь содержит список всех модулей Foo::Bar::Baz::*, и эти модули уже загружены с require. Или вы можете просто позвонить plugins, не присваивая результат переменной, если вам нужна только загрузка модулей и не нужен их список.

3 голосов
/ 25 апреля 2019

Есть ли другой способ для псевдонима Foo :: Bar :: Baz :: as Things :: такой, что мне может потребоваться Things :: ThingA?

Да. Для этого есть два требования:

  1. Создайте псевдоним пакета, как вы уже сделали.

    BEGIN { *Things:: = *Foo::Bar::Baz:: }
    
  2. Создайте символическую ссылку на mylibs/Things из каталога mylibs/Foo/Bar/Baz (где mylibs - путь к вашим Perl-модулям)

    (Сделайте ссылку из файла Foo/Bar/Baz.pm на Things.pm тоже, если хотите)

Как только вы это сделаете и вызовете eval "require Things::Quux" или eval "use Things::Quux", Perl загрузит файл в mylibs/Things/Quux.pm, что совпадает с файлом mylibs/Foo/Bar/Baz/Quux.pm. В этом файле есть оператор package Foo::Bar::Baz::Quux, но поскольку этот пакет уже связан с пространством имен Things::Quux, все его подпрограммы и переменные пакета будут доступны в любом из них.

Существует ли какой-либо другой общепринятый метод связывания пакетов на разных уровнях одного и того же пространства имен, чтобы устранить необходимость во всем этом?

Непонятно, какова ваша объектная модель, но если *::Thing1, *::Thing2 и т. Д. Являются реализациями некоторого общего базового класса, вы можете рассмотреть фабричный метод в базовом классе.

package Foo::Bar::Baz;
sub newThing {
    my ($class, $implementation, @options) = @_;
    eval "use $class\::$implementation; 1"
        or die "No $implementation subclass yet";
    no strict 'refs';
    my $obj = "$class\::$implementation"->new(@options);
    return $obj;
}

Теперь Foo::Bar::Baz::Thing7 (который может иметь или не иметь псевдоним Things::Thing7) будет загружаться только в том случае, если это необходимо, скажем, при вызове типа

my $obj7 = Foo::Bar::Baz->newThing("Thing7",foo => 42);
print ref($obj7);   # probably  Foo::Bar::Baz::Thing7
2 голосов
/ 26 апреля 2019

Взаимодействие с typeglobs похоже на ядерное излишество. Module :: Runtime - это стандартный способ загрузки модулей во время выполнения на основе данных конфигурации. На данный момент все может быть обычными переменными. Использование константы здесь бесполезно.

Вот мое предложение от нашего чата IRC.

package Foo::Bar::Baz;

use strict;
use Module::Runtime "require_module";
use List::Util "uniq";

my $prefix = "Things::LetterThings";
my %prop_module_map = (
   foo => [ qw{ThingC ThingF ThingY} ],
   bar => [ qw{ThingL ThingB} ],
   baz => [ qw{ThingA ThingB ThingC ThingE ThingG ThingH ThingZ} ],
   # or
   # ALL => [ qw{ThingA .. ThingZ} ],
);
my @all_modules = uniq map { @$_ } values %prop_module_map;

sub load_modules {
  my $self = shift;

  # map module list if matching property found, otherwise use ALL_MODULES
  my $modules = $prop_module_map{$self->prop} ?
     $prop_module_map{$self->prop} :
     \@all_modules;

  #only do the map operation on the list we actually need to use
  my @modules = map { join "::", $prefix, $_  } @$modules;

  foreach my $module (@modules) {
    require_module($module);
  }
}

1;
__END__
0 голосов
/ 18 июня 2019

ИМЯ и ПАКЕТ существуют в основном для того, чтобы вы могли узнать, из какого пакета и имени пришла ссылка.Кроме этого есть способ вернуть имя переменной, а не значение переменной, например, для отладки.

printf "%s\n", __PACKAGE__; my $y = \*ydp; pp *$y{PACKAGE}, *$y{NAME};
W
("W", "ydp")
0 голосов
/ 25 апреля 2019

Я нашел решение, основанное на примере в perlmod , которое, кажется, работает, хотя я все еще пытаюсь обернуть его вокруг.Я публикую его с надеждой, что кто-то сможет улучшить его или хотя бы объяснить или предоставить обратную связь.

foreach my $package (ALL_THINGS) {
    no strict "refs";
    $package = *{$package}{PACKAGE}."::".*{$package}{NAME};
    eval "require $package";
}

Редактировать: После перехода по ссылке наperlref, я нашел эту рекламу в разделе (7):

* foo {NAME} и * foo {PACKAGE} являются исключением в том, чтоони возвращают строки, а не ссылки.Они возвращают пакет и имя самого typeglob, а не тот, который был ему назначен.Таким образом, после * foo = * Foo :: bar , * foo станет "* Foo :: bar" при использовании в качестве строки, но * foo {PACKAGE} и * foo {NAME} будут продолжать производить "main" и "foo" соответственно.

Исходя из этого, имеет смыслэто *Things{PACKAGE} всегда будет преобразовываться в Foo::Bar::Baz, так как это пакет, в котором мы работаем, и, следовательно, пакет, к которому принадлежит typeglob.В моем коде выше $package разрешается до Things::ThingA, а не Things, поэтому мы получаем *Things::ThingA{PACKAGE}.Точно так же вторая часть становится *Things::ThingA{NAME}.Я мог бы рискнуть несколькими догадками относительно того, почему это может работать, но правда в том, что я не уверен.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...