【问题标题】:How can I require an optional Perl module if installed?如果已安装,我如何需要可选的 Perl 模块?
【发布时间】:2010-09-20 02:00:47
【问题描述】:

我有依赖Term::ReadKey 来获取终端宽度的 Perl 代码。我的安装缺少这个模块,所以如果模块不存在,我想提供一个默认值而不是抛出异常。

如何有条件地使用可选模块,而不提前知道它是否可用。

# but only if the module is installed and exists
use Term::ReadKey;
...

我怎样才能做到这一点?

【问题讨论】:

  • 在我看来,要么是标题错误,要么所有答案(除了使用 Module::Load::Conditional 的答案,如果使用 check_install() 的话)都是错误的。标题询问如何检查“如果我有一个 Perl 模块在使用它之前”。所有答案都使用“使用 eval 检测错误,而需要/加载/使用它”的一些变体。
  • 我考虑加载和使用 distinct。你可能不同意……
  • Perl 关键字use 具有非常特定的含义,因此我认为动词“使用”具有类似的特定含义。我不确定“加载”指的是什么......所以是的,我想我们会同意不同意;-)
  • @EvanCarroll 您的答案很好,尽管它在功能上似乎与所选答案相同。但是有必要改变问题吗?如果我在 Google 上搜索“如何在 Perl 中检查我是否有一个模块,然后再使用它”,我不太可能搜索“在 Perl 中如何可以可选地需要一个模块”。而且我认为展示我默认的模块中的内容有助于获得严肃的答案,而不是“为了什么?”厘米。
  • @EvanCarroll 我了解有时您有一个问题,它会作为现有问题的副本而关闭。发生的频率超出了应有的程度。这并不意味着我的问题“不是问题”。您应该与审阅者一起讨论,或者在此处添加您的答案,注意它是另一种框架。

标签: perl module require


【解决方案1】:

这是一个不需要其他模块的简单解决方案:

my $rc = eval
{
  require Term::ReadKey;
  Term::ReadKey->import();
  1;
};

if($rc)
{
  # Term::ReadKey loaded and imported successfully
  ...
}

请注意,下面所有使用eval { use SomeModule } 的答案(我希望它们低于这个答案!:-) 都是错误的,因为use 语句是在编译时评估的,无论它们出现在代码中的什么位置。因此,如果SomeModule 不可用,脚本将在编译后立即死亡。

(use 语句的字符串 eval 也可以工作 (eval 'use SomeModule';),但是当 require/import 对做同样的事情时,在运行时解析和编译新代码是没有意义的,并且是在编译时进行语法检查以启动。)

最后,请注意,我在这里使用的eval { ... }$@ 是为了本示例的目的而简洁的。在实际代码中,您应该使用类似Try::Tiny 或至少be aware of the issues it addresses

【讨论】:

  • 天啊,我应该首先想到的。 +1
  • 是的,确实有效,并且 eval 块后面的分号非常重要。
  • 尽量避免依赖$@。例如,某些模块可以在加载时将 $@ 设置为副作用,而不会实际引发异常。更好的选择是依靠eval 将在捕获异常时返回 undef 的事实,即。 if ( eval "use Term::ReadKey" ) { ... }.
  • 如果模块(或其他任何东西)在生成异常后执行 eval(例如,在 SIGDIE 处理程序或 DESTROY 方法中),则 $@ 的值将替换为最新评估。
  • 答案中use Term::ReadKey;eval { require Term::ReadKey }; 之间的细微差别是,现在Term::ReadKey 将无法在BEGIN 块中使用。但这在 OP 的示例中不是问题。
【解决方案2】:

查看 CPAN 模块 Module::Load::Conditional。它会做你想做的事。

【讨论】:

  • 当然,只有在您也安装了那个时才有效。如果你这样做可能是一个更好的解决方案。
  • 是的...我不能保证我有那个。虽然 Detect::Module 是,但它并没有列出 $installed->modules() 返回的模块名称列表中的所有模块。
【解决方案3】:

经典的答案(至少可以追溯到 Perl 4,早在出现“使用”之前)是“require()”一个模块。这是在脚本运行时执行的,而不是在编译时执行的,您可以测试成功或失败并做出适当的反应。

【讨论】:

    【解决方案4】:

    如果您需要特定版本的模块:

    my $GOT_READKEY;
    BEGIN {
        eval {
            require Term::ReadKey;
            Term::ReadKey->import();
            $GOT_READKEY = 1 if $Term::ReadKey::VERSION >= 2.30;
        };
    }
    
    
    # elsewhere in the code
    if ($GOT_READKEY) {
        # ...
    }
    

    【讨论】:

      【解决方案5】:
      if  (eval {require Term::ReadKey;1;} ne 1) {
      # if module can't load
      } else {
      Term::ReadKey->import();
      }
      

      if  (eval {require Term::ReadKey;1;}) {
      #module loaded
      Term::ReadKey->import();
      }
      

      注意:1; 仅在正确加载 require Term::... 时才会执行。

      【讨论】:

        【解决方案6】:
        use Module::Load::Conditional qw(check_install);
        
        use if check_install(module => 'Clipboard') != undef, 'Clipboard'; # class methods: paste, copy
        

        使用if pragma 和Module::Load::Conditional 核心模块。

        check_install 返回 hashref 或 undef


        编译指示文档的see also 部分也提到了这个模块:

        Module::Load::Conditional 提供了许多函数,可用于查询哪些模块可用,然后在运行时加载其中的一个或多个。

        【讨论】:

        • 我真的希望使用内置的use 指令的变体有一种更简单、噪音更少的方法。
        【解决方案7】:

        这是加载可选模块 (so long as you're not using it in a code base with sigdie handler) 的有效习惯用法,

        use constant HAS_MODULE => defined eval { require Module };
        

        这将需要模块(如果可用),并将状态存储在常量中。

        你可以这样使用,

        use constant HAS_READLINE => defined eval { require Term::ReadKey };
        
        my $width = 80;
        if ( HAS_READLINE ) {
          $width = # ... code, override default.
        }
        

        请注意,如果您需要导入它并引入符号,您也可以轻松地做到这一点。你可以跟进。

        use constant HAS_READLINE => defined eval { require Term::ReadKey };
        Term::ReadKey->import if HAS_READLINE;
        

        此方法使用常量。这样做的好处是,如果您没有此模块,则会从 optree 中清除死代码路径。

        【讨论】:

          【解决方案8】:

          我认为使用变量时它不起作用。 请查看this link,它解释了它如何与变量一起使用

          $class = 'Foo::Bar';
                  require $class;       # $class is not a bareword
              #or
                  require "Foo::Bar";   # not a bareword because of the ""
          

          require 函数将在@INC 数组中查找“Foo::Bar”文件,并抱怨在该处找不到“Foo::Bar”。在这种情况下,您可以这样做:

           eval "require $class";
          

          【讨论】:

            猜你喜欢
            • 2014-09-19
            • 1970-01-01
            • 1970-01-01
            • 2010-09-13
            • 1970-01-01
            • 2012-02-05
            • 2020-10-28
            相关资源
            最近更新 更多