鉴于评论和缺乏回应,我做了一些研究来回答我自己的问题。欢迎评论或其他答案!
动态代码
动态代码是指在以下位置评估的代码run-time。一般来说,我认为最好编译应用程序,以便在开始执行之前进行 Perl 编译器可以提供的所有错误检查。添加到use strict
and use warnings
,这样你就可以发现许多常见的错误。那么为什么要使用动态代码呢?我认为的原因有以下几点:
- 应用程序执行许多不同的操作,这些操作是根据执行上下文选择的。例如,应用程序从文件中提取某些属性。提取它们的方式取决于文件类型,我们想要处理许多文件类型,但我们不想为添加的每个新文件类型更改应用程序。我们还希望应用程序能够快速启动。
- 应用程序需要扩展在飞行中以不需要重新启动应用程序的方式。
- 我们有一个包含许多功能的大型应用程序。当我们部署应用程序时,我们不想一直提供所有可能的功能,也许是因为我们单独许可它们,也许是因为并非所有功能都能够在所有平台上运行。通过仅添加具有我们想要的功能的文件,我们可以得到分配不需要更改任何代码或config files.
我们该怎么做呢?
鉴于 Perl 提供的可能性,添加动态代码的解决方案有两种:eval
并使用require
。然后还有modules这可能有助于以更简单或更易于维护的方式做事。
快速而肮脏的方法
The eval https://perldoc.perl.org/functions/eval.html方式使用形式eval EXPR
编译一段 Perl 代码run-time。该表达式可以是一个字符串,但我建议将代码放入一个文件中,并将其他类似的文件分组到一个方便的位置。然后,如果可能的话使用文件::啜饮 http://search.cpan.org/~uri/File-Slurp-9999.19/lib/File/Slurp.pm:
eval read_file("$file_with_code", binmode => ':utf8');
if( $@ ) {
die "$file_with_code: error $@\n";
}
if( !defined &myfunction ) {
die "myfunction is not defined at $file_with_code\n";
}
指定字符集为read_file
确保该文件将被正确解释。检查编译是否正确以及我们期望的函数是否已定义也很好。所以在$file_with_code
, 我们将有:
sub myfunction(...) {
# Do whatever; maybe return something
}
然后就可以正常调用该函数了。该函数将根据加载的文件而有所不同。简单而动态。
模块化方式(推荐)
考虑到可维护性,我的做法是使用require https://perldoc.perl.org/functions/require.html。不像use
,即评估于编译时, require
可用于加载模块run-time。从各种调用方式中require
,我会选择:
my $mymodule = 'MyCompany::MyModule'; # The module name ends up in $mymodule
require $mymodule;
也不像use
, require
将加载模块但不会执行import
。因此,我们可以使用模块内的任何函数,并且这些函数名称不会污染调用名称空间。要访问该功能,我们需要使用:
$mymodule->myfunction($a, $b);
请参阅下文了解参数如何传递。这种调用函数的方式会在前面添加一个参数$a
and $b
通常被命名为$self
。如果您对面向对象一无所知,您可以忽略它。
As require
将尝试加载模块,该模块可能不存在或可能无法编译,要捕获错误,最好使用:
eval "require $mymodule";
Then $@
可用于检查加载+编译过程中的错误。我们还可以检查该函数是否已定义为:
if( $mymodule->can('myfunction') ) {
die "myfunction is not defined at module $mymodule\n";
}
在这种情况下,我们需要为模块创建一个目录和一个带有以下内容的文件.pm
每一项的扩展名:
MyCompany
MyModule.pm
Inside MyModule.pm
我们将有:
package MyCompany::MyModule;
sub myfunction {
my ($self, $a, $b);
# Do whatever; maybe return something
# $self will be 'MyCompany::MyModule'
}
1;
The package https://perldoc.perl.org/functions/package.htmlbit 是必不可少的,它将确保我们放入的任何定义都会出现在MyCompany::MyModule
命名空间。这1;
最后会告诉require
模块初始化正确。
如果我们想使用其他可能污染调用者命名空间的库来实现该模块,我们可以使用命名空间::干净 http://search.cpan.org/~ribasushi/namespace-clean-0.27/lib/namespace/clean.pm模块。该模块将确保调用者不会从我们定义的模块中获得任何对命名空间的添加。它的使用方式如下:
package MyCompany::MyModule;
# Definitions by these modules will not be available to the code doing the require
use Library1 qw(def1 def2);
use Library2 qw(def3 def4);
...
# Private functions go here and will not be visible from the code doing the require
sub private_function1 {
...
}
...
use namespace::clean;
# myfunction will be available
sub myfunction {
# Do whatever; maybe return something
}
...
1;
如果我们多次包含一个模块会发生什么?
简短的答案是nothing。 Perl 使用以下命令跟踪已加载的模块以及从何处加载%INC
多变的。两个都use
and require
不会加载库两次。use
会将所有导出的名称添加到调用者名称空间中。require
也不会那样做。如果您想检查模块是否已加载,您可以使用%INC
或者更好的是,你可以使用模块::已加载 https://perldoc.perl.org/Module/Loaded.html这是现代 Perl 版本核心的一部分:
use Module::Loaded;
if( !is_loaded( $mymodule ) {
eval "require $mymodule" );
...
}
如何确保 Perl 找到我的模块文件?
For use
and require
Perl 使用@INC
变量来定义将用于查找库的目录列表。向其中添加新目录可以通过将其添加到PERL5LIB
环境变量或使用:
use lib '/the/path/to/my/libs';
辅助库
我发现了一些库,可以用来使使用动态机制的代码更易于维护。他们是:
- The if http://search.cpan.org/~rjbs/if-0.0606/if.pmmodule:根据条件加载或不加载模块:
use if CONDITION, MODULE => ARGUMENTS;
。也可用于卸载模块。
-
模块::加载::有条件 http://search.cpan.org/dist/Module-Load-Conditional/lib/Module/Load/Conditional.pm:在尝试加载模块时不会死掉,也可用于检查模块版本或其依赖项。它还能够一次加载所有模块列表,甚至在加载之前检查它们的版本。
摘自 Module::Load::Conditional 文档:
use Module::Load::Conditional qw(can_load);
my $use_list = {
CPANPLUS => 0.05,
LWP => 5.60,
'Test::More' => undef,
};
print can_load( modules => $use_list )
? 'all modules loaded successfully'
: 'failed to load required modules';