Perl: динамическая загрузка модулей, наследование объектов и общие вспомогательные файлы.

Короче говоря, я пытаюсь смоделировать топологию сети, используя объекты для каждого экземпляра в сети. Кроме того, у меня есть класс менеджера верхнего уровня, ответственный за управление этими объектами и выполнение проверок целостности. Файловая структура выглядит следующим образом (я пропустил большую часть объектных файлов, так как все они имеют примерно одинаковую структуру):

Manager.pm
Constants.pm
Classes/
  +- Machine.pm
  +- Node.pm
  +- Object.pm
  +- Switch.pm

Проработав несколько лет в ООП, я фанат повторного использования кода и т. д., поэтому я настроил наследование между этими объектами, дерево наследования (в этом примере) выглядит так:

Switch  -+-> Node -+-> Object
Machine -+

Все эти объекты структурированы следующим образом:

package Switch;
use parent qw(Node);

sub buildFromXML {
  ...
}
sub new {
  ...
}

# additonal methods

Теперь самое интересное:

Вопрос 1

Как я могу обеспечить правильную загрузку всех этих объектов без статического ввода имен? Основная проблема заключается в следующем: если я просто require "$_" foreach glob("./Classes/*");, я получаю много ошибок "Новая подпрограмма, переопределенная в". Я также поигрался с use parent qw(-norequire Object), Module::Find и некоторыми другими модификациями @INC в различных комбинациях, короче говоря: не сработало. В настоящее время я статически импортирую все используемые классы, они автоматически импортируют свои родительские классы.
Итак, в основном, что я спрашиваю: каков (perl-)правильный способ сделать это?
И дополнительно: это было бы очень полезно иметь возможность создавать более сложную структуру папок (поскольку объектов будет довольно много) и при этом иметь наследование + "автозагрузку"

Вопрос 2 – РЕШЕНО

Как я могу «поделиться своим импортом»? Я использую несколько библиотек (моя собственная, содержащая некоторые вспомогательные функции, LibXML, Scalar::Util и т. д.), и я хочу поделиться ими со своими объектами. (Причина этого в том, что мне может понадобиться добавить еще одну общую библиотеку ко всем объектам, и высока вероятность того, что будет значительно больше 100 объектов - неинтересно редактировать их все вручную, и делать это с помощью регулярного выражения/скрипта теоретически будет работать, но это не похоже на самое чистое доступное решение)
Что я пробовал:

  • импортировать все в Manager.pm -> Работает внутри пакета Manager - выдает такие ошибки, как "неопределенная подпрограмма &Switch::trace вызывается"
  • Создайте файл include.pl и do/require/use внутри каждого объекта - выдает те же ошибки.
  • Еще кое-что, к сожалению, не помню

include.pl в основном будет выглядеть так:

use lib_perl;
use Scalar::Util qw(blessed);
use XML::LibXML;
use Data::Dumper;
use Error::TryCatch;
...

Я снова спрашиваю: как правильно это сделать? Использую ли я правильный подход и просто терплю неудачу при выполнении, или мне следует полностью изменить свою структуру?
Не так важно, почему мой текущий код не работает так хорошо, предоставляя правильный, чистый подход к этим проблемам. хватило бы пока :)

РЕДАКТИРОВАТЬ: полностью забыл версию Perl -_- Примечание: я не могу обновить Perl, так как мне нужны библиотеки, которые застряли с 5.8:/

C:\> perl -version
This is perl, v5.8.8 built for MSWin32-x86-multi-thread
(with 50 registered patches, see perl -V for more detail)

Copyright 1987-2006, Larry Wall

Binary build 820 [274739] provided by ActiveState http://www.ActiveState.com
Built Jan 23 2007 15:57:46

person incaseoftrouble    schedule 14.09.2012    source источник
comment
Я просто проходил мимо (нет времени пробираться через эту кучу, извините) и заметил, что у вас package Switch;, а вы на 5.8. В 5.8 был основной модуль под названием Switch (злополучный оператор case Дамиана), так что это плохая идея.   -  person cdarke    schedule 14.09.2012
comment
Внутри я использую SwitchDescription в качестве имени пакета, я выбрал короткое имя только для удобочитаемости, так что это не должно быть проблемой. Но все равно спасибо :)   -  person incaseoftrouble    schedule 14.09.2012


Ответы (3)


Это всего лишь частичный ответ на вопрос 2, разделяющий импорт.

Загрузка модуля (через use) делает две вещи:

  1. Компиляция модуля и установка содержимого в иерархию пространства имен (которая является общей). См. perldoc -f require.
  2. Вызов подпрограммы import для каждого загруженного модуля. Это загружает некоторые подпрограммы или константы и т. д. в пространство имен вызывающего объекта. Это процесс, который класс Exporter в значительной степени скрывает от глаз. Эта часть важна для использования сабвуферов и т. д. без их полного имени, например. max вместо List::Util::max. См. perldoc -f use.

Давайте рассмотрим следующие три модуля: A, B и User.

{
   package A;
   use List::Util qw(max);
   # can use List::Util::max
   # can use max
}
{
   package User;
   # can use List::Util::max -> it is already loaded
   # cannot use max, this name is not defined in this namespace
}

Пакет B определяет подпрограмму load, которая загружает предопределенный список модулей и подпрограмм в пространство имен вызывающих объектов:

{
   package B;
   sub load {
     my $package = (caller())[0]; # caller is a built-in, fetches package name

     eval qq{package $package;} . <<'FINIS' ;
       use List::Util qw(max);
       # add further modules here to load
       # you can place arbitrarily complex code in this eval string
       # to execute it in all modules that call this sub.
       # (e.g. testing and registering)
       # However, this is orthogonal to OOP.
FINIS

     if ($@) {
       # Do error handling
     }
   }
}

Внутри строки eval мы временно переключаемся на пакет callers, а затем загружаем указанный модуль. Это означает, что код пакета User теперь выглядит так:

{
   package User;
   B::load();
   # can use List::Util::max
   # can use max
}

Однако вы должны убедиться, что сабвуфер load уже загружен. use B если сомневаетесь. Возможно, лучше всего выполнить B::load() на этапе BEGIN, прежде чем остальная часть модуля будет скомпилирована:

{
  package User;
  BEGIN {use B; B::load()}
  # ...
}

эквивалентно

{
  package User;
  use B;
  use List::Util qw(max);
  # ...
}

ТИМТОВТДИ. Хотя я нахожу код evaling довольно запутанным и опасным, именно его я бы и использовал в этом сценарии (а не файлы doing, которые похожи, но имеют другие побочные эффекты). Вручную возиться с typeglobs в пространстве имен пакетов — это ад по сравнению с ним, а копирование и вставка списка имен модулей — это все равно, что вернуться в те дни, когда не было даже препроцессора C.


Изменить: Import::Into

… это модуль CPAN, обеспечивающий эту функциональность через интересный интерфейс метода. Используя этот модуль, мы бы переопределили наш пакет B следующим образом:

{
  package B;
  use List::Util;   # you have to 'use' or 'require' this first, before using 'load'.
  use Import::Into; # has to be installed from CPAN first
  sub load {
    my $package = caller;
    List::Util->import::into($package, qw(max));
    # should work too: strict->import::into($package);
    # ...
  }
}

Этот модуль скрывает всю грязную работу (evaling) от просмотра и выполняет гимнастику разрешения вызовов методов, чтобы разрешить импорт прагм в другие пространства имен.

person amon    schedule 14.09.2012
comment
Спасибо за ответ! Я тоже думал о чем-то подобном, но, как вы сказали, evaling это довольно беспорядочно, и я бы предпочел использовать его только в качестве крайней меры. Кстати, что, возможно, стоит отметить в вашем первом фрагменте: если package User имеет директиву use A, вы должны иметь возможность вызывать A::max внутри пакета User. Этот ход мыслей очень помог мне понять принципы use и require. - person incaseoftrouble; 14.09.2012
comment
@Megge Проблема в том, что функция package не принимает произвольное выражение, поэтому пространство имен должно быть жестко запрограммировано. Таким образом, без evaling трудно (невозможно?) переключиться в пространство имен, указанное во время выполнения. importing происходит только в текущем пакете :( И да, A::max доступен, но его использование кажется неправильным. - person amon; 14.09.2012
comment
Дело в том, что у меня сложилось впечатление, что в новом языке, таком как perl, должен быть наполовину приличный подход. Может есть решение/модуль, использующий совсем другой подход. Я просто подожду, если кто-нибудь придумает решение на выходных, если нет, я воспользуюсь вашим. - person incaseoftrouble; 14.09.2012
comment
@Megge На самом деле для этого есть модуль: Import::Into. Удивительно, но он тоже использует eval и просто предоставляет синтаксический сахар, используя какой-то действительно странный код… (Perl — новый язык? Родился в 1987 году — всего на 4 года моложе C++ — переродился в 1994 году как Perl 5, он более длинная история, чем у Python или Java) - person amon; 14.09.2012
comment
Что хорошо в этом сахаре, так это то, что он (должен) работать, и у меня на одну проблему меньше :) Действительно ли странное средство не следует использовать или вы бы порекомендовали его? (Новый в том смысле, что это не что-то вроде BASIC, Fortran и т. д., и в нем есть ООП. Perl кажется настолько интуитивным в отношении многих аспектов, что действительно кажется странным, что у такой общей задачи нет надежного решения. (С другой стороны Я впервые столкнулся с Perl всего 4 недели назад, так что интуитивное чувство может скоро угаснуть ;)) - person incaseoftrouble; 14.09.2012
comment
Funky означает: я не понял код с первого взгляда. Но теперь, когда я это делаю, это абсолютно красиво, и у него есть несколько преимуществ по сравнению с моим подходом. Через минуту я добавлю краткий раздел об использовании в свой пост. - person amon; 14.09.2012
comment
Большое спасибо! Думаю, ответ на вопрос №2 дан. Думаю, если я отмечу ваш ответ как решение, весь вопрос будет помечен как решенный, поэтому я приму ваш ответ, когда все будет решено. - person incaseoftrouble; 15.09.2012

Дополнение к Import::Into Solution

Я нашел сценарий, который, кажется, требует eval() из решения Import::Into. В этом сценарии mod User эффективно используется из пакета B. Это может быть обычным сценарием для людей, использующих Import::Into.

Особенности:

  • Я создал модуль uses_exporter с отдельными сабвуферами для импорта разных групп модулей, например. load_generic() и load_list_utils().

  • Использование в load_list_utils() относится к общедоступным модам, таким как List::MoreUtils, И к моему собственному модулю, list_utils_again. Этот локальный модуль также вызывает load_list_utils(). Вызов завершится ошибкой, если load_list_utils() использует list_utils_again.

  • Мое решение состояло в том, чтобы использовать list_utils_again в eval, который не выполняется, когда $target eq 'list_utils_again'

person cesces    schedule 01.12.2013

Правильный идиоматический способ Perl сделать это не всегда загружать кучу модулей независимо от того, используются они или нет; это значит, что в каждом файле должны быть use те модули, которые ему прямо (а не косвенно) нужны.

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

person ysth    schedule 01.12.2013