将动态代码添加到 Perl 应用程序的最佳方法

2023-12-22

我知道这个问题的具体实例之前已经得到回答:

  • 如何在不使用 eval 的情况下动态包含 Perl 模块? https://stackoverflow.com/questions/1917261/how-can-i-dynamically-include-perl-modules-without-using-eval
  • 如何使用仅在运行时已知的 Perl 包? https://stackoverflow.com/questions/442710/how-do-i-use-a-perl-package-known-only-in-runtime

Perl Monks 也有很好的答案:

  • 编写动态加载其他模块的 Perl 模块 http://www.perlmonks.org/?node_id=1021324.
  • 即时创建子例程 http://www.perlmonks.org/?node_id=431623

但我想要一种强大的方法来向 Perl 应用程序添加功能,即:

  1. 高效的:如果不需要代码,则不应编译。
  2. 易于调试:如果动态代码出现问题,则错误报告,应该指向动态代码的正确位置。
  3. 易于扩展:添加新代码应该像添加新文件或目录+文件一样简单。
  4. 易于调用:主应用程序应该能够使用“附加组件”而不会有太多麻烦。一个有效的机制来检查“附加”是否已经加载,如果没有加载,将是一个优点。

为了说明这一点,以下是一些可以从良好解决方案中受益的示例:

  • 一组从不同应用程序移动数据的脚本。例如,将数据从 OpenCart 移动到 Prestashop,其中每个entity数据模型中有一个特定的“附加组件”来处理输入或输出;然后中间数据模型负责数据的转换。这可用于在任何方向上甚至在同一电子商务的不同版本之间移动数据。

  • 需要在不同位置呈现不同类型 HTML 的 Web 应用程序。每个“模块”都知道如何处理特定信息并接受参数来执行此操作。一个模块输出 HTML,另一个模块输出文档列表,另一个模块输出文档,另一个模块输出横幅,等等。

以下是我使用过并且有效的一些示例。

在运行时加载函数并输出可能的编译错误:

eval `cat $file_with_function`;
if( $@ ) {
  print STDERR $@, "\n";
  die "Errors at file $file_with_function\n";
}

或者更稳健地使用File::Slurp:

eval read_file("$file_with_function", binmode => ':utf8');

检查某个函数是否已定义:

if( !defined &myfunction ) {
  die "myfunction is not defined\n";
}

可以从那里调用该函数。这对于一种功能来说没问题,但对于很多功能来说就不行了。

如果将函数放入模块中:

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(...);

哪里的require可能会受到保护内部eval然后使用$@像之前一样。

With 模块::加载 http://search.cpan.org/~bingos/Module-Load-0.32/lib/Module/Load.pm:

load $name_of_module;

随后是import并以同样的方式使用。安全不应该是一个问题因为可以假设动态代码来自受信任的地方。还有更好的方法吗?会考虑哪种方式好的做法?

如果它有帮助,我将在以下范围内使用该解决方案(以及其他地方,但不限于此)Dancer http://perldancer.org/框架。

EDIT:鉴于评论,我添加更多信息。我想到的所有案例都有一个共同点:

  1. 有不止一段动态代码。可能有很多开始。
  2. 每一位代码都有相同的界面.

鉴于评论和缺乏回应,我做了一些研究来回答我自己的问题。欢迎评论或其他答案!

动态代码

动态代码是指在以下位置评估的代码run-time。一般来说,我认为最好编译应用程序,以便在开始执行之前进行 Perl 编译器可以提供的所有错误检查。添加到use strict and use warnings,这样你就可以发现许多常见的错误。那么为什么要使用动态代码呢?我认为的原因有以下几点:

  1. 应用程序执行许多不同的操作,这些操作是根据执行上下文选择的。例如,应用程序从文件中提取某些属性。提取它们的方式取决于文件类型,我们想要处理许多文件类型,但我们不想为添加的每个新文件类型更改应用程序。我们还希望应用程序能够快速启动。
  2. 应用程序需要扩展在飞行中以不需要重新启动应用程序的方式。
  3. 我们有一个包含许多功能的大型应用程序。当我们部署应用程序时,我们不想一直提供所有可能的功能,也许是因为我们单独许可它们,也许是因为并非所有功能都能够在所有平台上运行。通过仅添加具有我们想要的功能的文件,我们可以得到分配不需要更改任何代码或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 requirePerl 使用@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';
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

将动态代码添加到 Perl 应用程序的最佳方法 的相关文章

  • 如何忽略 perl 中的“证书验证失败”错误?

    我想访问一个无法验证证书的网站 我正在使用 WWW Mechanize 获取请求 那么如何忽略这一点并继续连接到该网站呢 use IO Socket SSL qw use WWW Mechanize qw my mech WWW Mecha
  • 打包/解包 - 小端 - 64 位 - 问题

    usr bin env perl use warnings use 5 012 my var 1 lt lt 31 say unpack B pack N var 10000000000000000000000000000000 我怎样才能
  • 如何使用“子例程引用”作为哈希键

    在 Perl 中 我正在学习如何取消引用 子例程引用 但我似乎无法使用子例程引用作为哈希 键 在下面的示例代码中 我可以创建对子例程 subref 的引用 然后取消引用它以运行子例程 subref 我可以使用引用作为哈希 值 然后轻松取消引
  • 在哪里可以找到将现有 CPAN 模块转换为使用 Dist::Zilla 的简明指南?

    我曾多次阅读过相关文档和一些博客文章地区 齐拉 http search cpan org dist Dist Zilla 我从来没有对自己的理解充满信心 在回答另一个问题时 Ether 提出了转换的可能性地穴 SSLeay http sea
  • Perl:管理 Windows 上的路径编码

    我正在努力处理包含非英文字符的路径 Activestate Perl Windows XP 如何打开 写入 复制等位于包含希腊语 俄语 法语重音字符的路径中的文件 假设我要将 text txt 文件复制到的目录是 C Documents a
  • 使用perl创建层次结构文件

    我的任务是使用 perl 创建父子层次结构文件 示例输入文件 制表符分隔 记录将以随机顺序排列在文件中 父项 可能出现在 子项 之后 S5 S3 S5 S8 ROOT S1 S1 S7 S2 S5 S3 S4 S1 S2 S4 77 S2
  • 本地“关闭”binmode(STDOUT, ":utf8")

    我的脚本开头有以下块 usr bin perl5 w use strict binmode STDIN utf8 binmode STDOUT utf8 binmode STDERR utf8 在某些子程序中 当存在其他编码 来自远程子程序
  • 为什么 Perl 找不到我在 ClearCase 中的文件?

    Perl 的这段代码告诉我 ClearCase 中的文件不存在 但它确实存在 x PATH TO FILE if e x print This file exists on the file system else print I can
  • 如何只读取文件的第一行

    我已经用谷歌搜索了一段时间 但我找不到只读取文件第一行的函数 我需要读取文本文件的第一行并从中提取日期 Perl 新手 open my file lt filename txt my firstLine lt file gt close f
  • Perl 三元条件运算符内部赋值问题

    我的程序中的这段 Perl 代码给出了错误的结果 condition a 2 a 3 print a 无论价值如何 condition就是 输出总是3 为什么呢 Perl 中对此进行了解释文档 http perldoc perl org p
  • 如何 grep 遍历数组,同时过滤掉匹配项?

    有没有一种快速简便的方法来 grep 遍历数组 找到满足某些测试的元素and从原始数组中删除这些 例如我想要 a 1 7 6 3 8 4 b grep filter gt 5 a now b 7 6 8 and a 1 3 4 换句话说 我
  • 导入 .pl 文件

    我想知道如何将 Perl 文件导入到脚本中 我尝试了 use require 和 do 但似乎没有什么对我有用 这就是我用 require 做到的 usr bin perl require equations print x1 n 是否可以
  • 为什么这个特定 Perl 脚本的线程版本比非线程版本慢 200 倍?

    A 推介会 http migo sixbit org papers Perl Threads by 米哈埃尔 戈伊克曼 http migo sixbit org 2003 年 Perl 会议上的文档包含两个质数查找脚本的示例 One htt
  • 如何在 Perl 中发送此 SOAP XML?

    我必须发送下面的 XML 我有no idea从哪儿开始 我知道我需要在 Perl 中查找 SOAP 但大致就是这样
  • Perl 中的线程定时循环

    本质上 我希望有一个高优先级线程 它以给定的时间间隔 此处为 0 5 毫秒 运行并中断 一切 执行一个短任务 然后返回 睡眠 状态 使用 Ubuntu 11 04 和 perl v5 10 1 问题是 虽然我得到了某种结果 但我不确定是否有
  • 在 Perl 中实现 CLI 工具的最佳实践是什么?

    我正在使用 Perl 实现 CLI 工具 我们可以遵循哪些最佳实践 作为前言 我花了 3 年时间为一家大型金融公司设计并实现了一个相当复杂的 Perl 命令行工具集 以下想法基本上是我们团队设计指南的一部分 用户界面 命令行选项 允许尽可能
  • 使用 sed 替换复杂模式

    我想使用 sed 命令替换模式 要删除的图案如下所示 带有一个空格 var 0xaae8 x6A x6F x69 x6E x72 x65 x76 x65 x72 x73 x65 x73 x70 x6C x69 x74 x3E x74 x70
  • 零垫重命名,例如图片 (2).jpg -> 图片 (002).jpg

    我需要按照标题在所有子文件夹中解释的方式重命名所有图像 我正在考虑用正则表达式提取括号内的数字 然后重命名它 搜索周围我看到有类似的工具rename and mmv但我无法让他们重新命名 jpg 我将不胜感激任何解决我的问题的建议 顺便说一
  • 为什么不鼓励在 Windows 上将 PPM perl 模块与 Strawberry perl 一起使用?

    任何人都可以解释为什么人们会发表此评论吗 我在 stackoverflow 上看到了几个线程 像这个 https stackoverflow com questions 6643939 installing modules using st
  • 使用 ActivePerl 时为什么必须指定带有备份扩展的 -i 开关?

    除非我使用备份扩展指定它们 否则我无法就地编辑在 ActivePerl 下运行的 Perl 单行代码 C gt perl i ape splice F 2 0 q inserted text qq F n file1 txt Can t d

随机推荐