【问题标题】:How can I dynamically load Perl modules and use their functions?如何动态加载 Perl 模块并使用它们的功能?
【发布时间】:2021-03-29 16:48:11
【问题描述】:

我正在尝试将我的代码中的一些 use 语句切换到 require 用于仅在满足某些条件时才使用的模块(例如,程序可能在需要这些模块的时间之间加载 1,000 次),以便降低开销并提高我的程序的执行速度(我一直在使用 Devel::NYTProf 来分析我的代码,并且即使我在某个会话中没有使用它们,一些模块也会增加显着的执行时间)。

我遇到了一个问题,即脚本在加载时终止,因为未定义符号(在导入该模块之前不会定义符号)。有没有办法告诉 Perl 符号将在运行时导入?一个简单的例子:如果我调用一个触发它的例程,我只想使用 Data::Dumper:

sub dataDumper {
    require Data::Dumper;
    Data::Dumper->import;
    say STDERR Dumper @_;
}

但是,在第三行调用 Dumper 会导致异常。我可以通过参考Data::Dumper->Dump 来解决这个问题。然而,与 LWP::UserAgent 类似的问题证明有点麻烦,因为 LWP::UserAgent 导入 HTTP::Request。我还没有想出避免抛出异常的my $request = new HTTP::Request GET => $params->{'url'}; 的变体。

假设我可以这样做,有什么理由说明这种方法是个坏主意吗?由于在编译时不加载这些模块,我是否会失去任何类型的优化?在像 LWP 这样的模块上,这似乎是一个简单的调用——我只是不怎么使用它。我对是否尝试在(例如)Text::Textile 和 Text::MultiMarkdown 之间动态切换有点困惑(我通常每次执行只需要一个。

【问题讨论】:

  • 如果启动时间如此繁重,可能值得研究一个持久化服务(就像 FCGI 之于 CGI)。
  • @ikegami 这听起来很有趣。您是否可以推荐一个很好的指南来使用现有程序并使用持久系统来实现它?听起来 PSGI 是今天最好的路线?
  • 对不起,没有。 . . . .

标签: perl


【解决方案1】:

在子调用中,参数周围的括号只能为声明的子省略。

say STDERR Dumper(@_);

请注意,您以这种方式加载的 subs 上的原型将被忽略。


第二行没有问题,因为它不涉及子调用。方法调用不受影响。

$ perl -M5.010 -e'
   if ($ARGV[0]) {
      require LWP::UserAgent;   # or HTTP::Request
      my $request = new HTTP::Request GET => "https://stackoverflow.com";
   }

   say "ok";
' 0
ok

$ perl -M5.010 -e'
   if ($ARGV[0]) {
      require LWP::UserAgent;   # or HTTP::Request
      my $request = new HTTP::Request GET => "https://stackoverflow.com";
   }

   say "ok";
' 1
ok

因此,类模块在这里特别有用。考虑复杂情况下的插件模型。


如果在编译时不加载这些模块,我是否会失去任何优化?

常量需要替换较慢且不能折叠的函数调用。

$ perl -MO=Concise,-exec -M5.010 -e'
   use constant FOO => 2;
   say 2 + FOO;
'
...
2  <;> nextstate(main 188 -e:3) v:{,fea=1
3  <0> pushmark s
4  <$> const[IV 4] s/FOLD
5  <@> say vK
...
$ perl -MO=Concise,-exec -M5.010 -e'
   say 2 + FOO();
   require constant; import constant FOO => 2;
'
...
2  <;> nextstate(main 2 -e:2) v:{,fea=1
3  <0> pushmark s
4  <$> const[IV 2] s
5  <0> pushmark s
6  <#> gv[*FOO] s/EARLYCV
7  <1> entersub[t2] sKS/TARG
8  <2> add[t3] sK/2
9  <@> say vK
...

【讨论】:

  • 这可能是一个愚蠢的问题,这里的“类模块”与“插件模型”是什么意思?我有 Perl 模块,这些模块实现了我基本上用作不同功能的插件的类。有没有更好的方法来解决这个问题?
  • 类模块...提供类的模块(相对于子类)
  • 插件是动态加载的模块,期望实现一个接口。
【解决方案2】:

使用完全限定名称,不要导入任何内容。

sub dataDumper {
    require Data::Dumper;
    say STDERR Data::Dumper::Dumper(@_);
}

但是,由于 LWP::UserAgent 导入了 HTTP::Request,因此 LWP::UserAgent 的一个类似问题有点棘手。

此声明中的术语是错误的。 LWP::UserAgent 不导入任何东西。事实上,它根本没有import 方法。它加载 HTTP::Request,通过useing 它。因此 perl 将在 LWP::UA 为 required 时切换到编译时间,编译它,并将 HTTP::Request、HTTP::Response 和 HTTP::Date(以及其他一些)、all explicitly without importing anything 加载到LWP::UserAgent 的命名空间。

$request = new HTTP::Request GET => $params->{'url'};

如果尚未加载 HTTP::Request,我将无法重现此爆炸。不执行就不会出错。

if (0) {
    HTTP::Request->new(GET => '/foo');
    new HTTP::Request GET => '/foo';
}

这很好。你当然应该使用第一种形式,而不是间接对象表示法,因为它是模棱两可的,但没有区别。

如果您要使用HTTP::Request::Common,它导入GETPOST 等以快速生成HTTP::Requests,您也可以在运行时只使用require,然后使用完全限定名称。

if ($make_web_request) {
  require LWP::UserAgent;
  require HTTP::Request::Common;

  my $ua = LWP::UserAgent->new;
  $ua->request(HTTP::Request::Common::GET('https://example.org'));
}

如果在编译时不加载这些模块,我是否会失去任何优化?

没有。如果您的代码启动了很多次,并且经过了很多不同的路径,那么只有在您需要它们时才加载它们是完全可以的。它几乎没有真正的区别。 Perl 无论如何都会在每个require 的编译时间和运行时间之间切换。一般来说,这根本不是惩罚。

【讨论】:

  • Re "使用完全限定的名称,不要导入任何东西。",这不是造成差异的原因;这是添加了括号。
  • @simbabque 谢谢!这似乎解决了我的问题——感谢有关 HTTP::Request 的提示!
【解决方案3】:

我做类似的事情,但像这样。即使require 不会再次加载它,state 也会以某种方式使其更快:

sub something {
    state $rc = require Foo;
    ...
    }

如果我想导入东西

sub something {
    state $rc = do { require Foo; Foo->import };
    ...
    }

记住use 的实际含义:

BEGIN {
    require Foo;
    Foo->import(...);
    }

BEGIN 仅在编译时执行此操作。如果您稍后执行所有相同的步骤,您最终会在同一个地方(稍后)。

【讨论】:

  • 谢谢!你再一次来拯救我的代码。非常感谢!我不知道“状态”。集成到我的代码中似乎是一件好事。尝试这个,我已经将加载时间缩短了 30%。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-09-09
  • 1970-01-01
  • 1970-01-01
  • 2010-10-01
  • 1970-01-01
相关资源
最近更新 更多