我知道这个问题的具体实例之前已经得到回答:
Perl Monks 也有很好的答案:
但我想要一种强大的方法来向 Perl 应用程序添加功能,即:
为了说明这一点,以下是一些可以从良好解决方案中受益的示例:
一组从不同应用程序移动数据的脚本。例如,将数据从 OpenCart 移动到 Prestashop,其中数据模型中的每个实体都有一个处理输入或输出的特定“附加组件”;然后中间数据模型负责数据的转换。这可用于在任何方向甚至同一电子商务的不同版本之间移动数据。
需要在不同位置呈现不同类型 HTML 的 Web 应用程序。每个“模块”都知道如何处理特定信息并接受参数来执行此操作。一个模块输出 HTML,另一个模块输出文档列表,另一个模块输出文档,另一个模块输出横幅,等等。
以下是我使用过并且有效的一些示例。
在运行时加载函数并输出可能的编译错误:
eval `cat $file_with_function`;
if( $@ ) {
print STDERR $@, "\n";
die "Errors at file $file_with_function\n";
}
Run Code Online (Sandbox Code Playgroud)
或者使用更强大的File::Slurp
:
eval read_file("$file_with_function", binmode => ':utf8');
Run Code Online (Sandbox Code Playgroud)
检查某个函数是否已定义:
if( !defined &myfunction ) {
die "myfunction is not defined\n";
}
Run Code Online (Sandbox Code Playgroud)
可以从那里调用该函数。这对于一种功能来说没问题,但对于很多功能来说就不行了。
如果将函数放入模块中:
require $file_with_function; # needs the ".pm" extension, i.e. addon/func.pm
$name_of_module->import(); # need to know the module name, i.e. Addon::Func
$name_of_module->myfunction(...);
Run Code Online (Sandbox Code Playgroud)
其中require
可以将其保护在内部eval
,然后$@
像以前一样使用。
使用模块::加载:
load $name_of_module;
Run Code Online (Sandbox Code Playgroud)
后面跟着 和import
的用法相同。安全性不应成为问题,因为可以假设动态代码来自受信任的地方。还有更好的方法吗?哪种方式被认为是好的做法?
如果它有帮助,我将在Dancer框架内使用该解决方案(以及其他地方,但不限于此)。
编辑:鉴于评论,我添加了一些更多信息。我想到的所有案例都有一个共同点:
鉴于评论和缺乏回应,我做了一些研究来回答我自己的问题。欢迎评论或其他答案!
\n\n我所说的动态代码是指在运行时评估的代码。一般来说,我认为最好编译应用程序,以便在开始执行之前进行 Perl 编译器可以提供的所有错误检查。添加到use strict
和use warnings
,您可以通过这种方式发现许多常见错误。那么为什么要使用动态代码呢?我认为的原因如下:
鉴于 Perl 提供的可能性,添加动态代码的解决方案有两种:使用eval
和使用require
. 还有一些模块可以帮助您以更简单或更易于维护的方式完成任务。
方式是采用在运行时编译一段Perl代码的eval
形式。该表达式可以是一个字符串,但我建议将代码放入一个文件中,并将其他类似的文件分组到一个方便的位置。然后,如果可能的话使用File::Slurp:eval EXPR
eval read_file("$file_with_code", binmode => \':utf8\');\nif( $@ ) {\n die "$file_with_code: error $@\\n";\n}\nif( !defined &myfunction ) {\n die "myfunction is not defined at $file_with_code\\n";\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n指定字符集以read_file
确保正确解释文件。检查编译是否正确以及我们期望的函数是否已定义也很好。所以在 中$file_with_code
,我们将有:
sub myfunction(...) {\n # Do whatever; maybe return something\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n然后就可以正常调用该函数了。该函数将根据加载的文件而有所不同。简单而动态。
\n\n考虑到可维护性,我的做法是使用require
. 与 不同的是use
,它是在编译时评估的,require
可用于在运行时加载模块。在调用的各种方法中require
,我会选择:
my $mymodule = \'MyCompany::MyModule\'; # The module name ends up in $mymodule\nrequire $mymodule;\n
Run Code Online (Sandbox Code Playgroud)\n\n也不像use
,require
会加载模块但不会执行import
。因此,我们可以使用模块内的任何函数,并且这些函数名称不会污染调用名称空间。要访问该功能,我们需要使用:
$mymodule->myfunction($a, $b);\n
Run Code Online (Sandbox Code Playgroud)\n\n请参阅下文了解参数如何传递。这种调用函数的方式会在前面添加一个参数$a
,$b
该参数通常命名为$self
。如果您对面向对象一无所知,您可以忽略它。
由于require
将尝试加载模块,并且该模块可能不存在或可能无法编译,因此要捕获错误,最好使用:
eval "require $mymodule";\n
Run Code Online (Sandbox Code Playgroud)\n\n然后$@
可用于检查加载+编译过程中的错误。我们还可以检查该函数是否已定义为:
if( $mymodule->can(\'myfunction\') ) {\n die "myfunction is not defined at module $mymodule\\n";\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n在这种情况下,我们需要为模块创建一个目录,并.pm
为每个模块创建一个扩展名的文件:
MyCompany\n MyModule.pm\n
Run Code Online (Sandbox Code Playgroud)\n\n里面MyModule.pm
我们将有:
package MyCompany::MyModule;\n\nsub myfunction {\n my ($self, $a, $b);\n\n # Do whatever; maybe return something\n # $self will be \'MyCompany::MyModule\'\n}\n\n1;\n
Run Code Online (Sandbox Code Playgroud)\n\n该package
位是必不可少的,它将确保我们放入的任何定义都位于MyCompany::MyModule
名称空间中。最后1;
将表明require
模块初始化是正确的。
如果我们想使用其他可能污染调用者命名空间的库来实现该模块,我们可以使用namespace::clean模块。该模块将确保调用者不会从我们定义的模块中获得任何对命名空间的添加。它的使用方式如下:
\n\npackage MyCompany::MyModule;\n\n# Definitions by these modules will not be available to the code doing the require\nuse Library1 qw(def1 def2);\nuse Library2 qw(def3 def4);\n...\n\n# Private functions go here and will not be visible from the code doing the require\nsub private_function1 {\n ...\n}\n...\n\nuse namespace::clean;\n\n# myfunction will be available\nsub myfunction {\n # Do whatever; maybe return something\n}\n...\n\n1;\n
Run Code Online (Sandbox Code Playgroud)\n\n简短的回答是什么都没有。Perl 使用变量跟踪哪些模块已加载以及从何处加载%INC
。两者use
都require
不会加载库两次。use
会将所有导出的名称添加到调用者名称空间中。require
也不会那样做。如果你想检查模块是否已经加载,你可以使用%INC
或者更好,你可以使用module::loaded,它是现代 Perl 版本核心的一部分:
use Module::Loaded;\n\nif( !is_loaded( $mymodule ) {\n eval "require $mymodule" );\n ...\n}\n
Run Code Online (Sandbox Code Playgroud)\n\nForuse
和require
Perl 使用该@INC
变量来定义将用于查找库的目录列表。向其中添加新目录可以通过将其添加到PERL5LIB
环境变量或使用以下方法来实现(以及其他方式):
use lib \'/the/path/to/my/libs\';\n
Run Code Online (Sandbox Code Playgroud)\n\n我发现了一些库,可以用来使使用动态机制的代码更易于维护。他们是:
\n\nuse if CONDITION, MODULE => ARGUMENTS;
。也可用于卸载模块。摘自 Module::Load::Conditional 文档:
\n\nuse Module::Load::Conditional qw(can_load);\n\nmy $use_list = {\n CPANPLUS => 0.05,\n LWP => 5.60,\n \'Test::More\' => undef,\n};\n\nprint can_load( modules => $use_list )\n ? \'all modules loaded successfully\'\n : \'failed to load required modules\';\n
Run Code Online (Sandbox Code Playgroud)\n
归档时间: |
|
查看次数: |
1393 次 |
最近记录: |