将动态代码添加到 perl 应用程序的最佳方法 [英] Best way to add dynamic code to a perl application

查看:53
本文介绍了将动态代码添加到 perl 应用程序的最佳方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I know specific instances of this question have been answered before:

Perl Monks 也有很好的答案:

There are also good answers at Perl Monks:

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

But I would like a robust way to add functionallity to a Perl application that will be:

  1. 高效:如果不需要代码,则不应编译.
  2. 易于调试:如果动态代码出现问题,则报告错误,应指向动态代码的正确位置.
  3. 易于扩展:添加新代码应该像添加新文件或目录+文件一样简单.
  4. 易于调用:主应用程序应该能够轻松使用附加组件".检查附加组件"是否已加载以及如果未加载它的有效机制将是一个加分项.
  1. Efficient: if the code is not needed it should not be compiled.
  2. Easy to debug: error reporting if something goes wrong at the dynamic code, should point at the right place at the dynamic code.
  3. Easy to extend: adding new code should be as easy as adding a new file or directory+file.
  4. Easy to invoke: the main application should be able to use an "add on" without much trouble. An efficient mechanism to check if the "add on" has already been loaded and if not load it, would be a plus.

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

To illustrate the point, here are some examples that would benefit from a good solution:

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

  • A set of scripts that move data from different applications. For instance, moving data from OpenCart to Prestashop, where each entity in the data model has a specific "add on" that deals with the input or output; then an intermediate data model takes care of the transformation of the data. This could be used to move data in any direction or even between different versions of the same ecommerce.

需要在不同位置呈现不同类型 HTML 的 Web 应用程序.每个模块"都知道如何处理特定信息并接受参数来完成它.一个模块输出 HTML,另一个是文档列表,另一个是文档,另一个是横幅,等等.

A web application that needs to render different types of HTML in different places. Each "module" knows how to handle a certain information and accepts parameters to do it. A module outputs HTML, another a list of documents, another a document, another a banner, and so on.

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

Here are some examples that I have used and that work.

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

Load a function at run time and output the possible compile errors:

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');

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

Check that a certain function has been defined:

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

可以从那里调用该函数.这适用于一个功能,但不适用于许多功能.

The function may be called from there on. This is fine with one function, but not for many.

如果函数被放在一个模块中:

If the function is put in a module:

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 中受到保护,然后像以前一样使用 $@.

Where the require may be protected inside an eval and then use $@ as before.

使用 Module::Load:

load $name_of_module;

后面跟着import,用法一样.安全性不应成为问题,因为可以假定动态代码来自受信任的地方.有更好的方法吗?哪种方式会被视为良好做法?

Followed by the import and used in the same way. Security should not be a concern as it may be assumed that the dynamic code comes from a trusted place. Are there better ways? Which way would be considered good practice?

如果有帮助,我将在 Dancer 框架.

In case it helps, I will be using the solution (among other places, but not exclusively) within the Dancer framework.

编辑:根据评论,我添加了更多信息.我想到的所有案例都有一个共同点:

EDIT: Given the comments, I add some more info. All cases that I have in mind have in common:

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

推荐答案

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

Given the comments and the lack of responses, I have done some research to answer my own question. Comments or other answers are welcome!

动态代码是指在运行时评估的代码.一般而言,我认为最好编译一个应用程序,以便在开始执行之前进行 Perl 编译器可以提供的所有错误检查.添加到use strictuse warnings,您可以通过这种方式捕获许多常见错误.那么为什么要使用动态代码呢?这些是我认为的原因:

By dynamic code I mean code that is evaluated at run-time. In general, I consider better to compile an application so that you have all the error checking the Perl compiler can offer before starting to execute. Added to use strict and use warnings, you can catch many common mistakes that way. So why using dynamic code at all? These are the reasons I consider:

  1. 应用程序执行许多不同的操作,这些操作是根据执行上下文选择的.例如,应用程序从文件中提取某些属性.提取它们的方式取决于文件类型,我们想要处理许多文件类型,但我们不想为我们添加的每个新文件类型更改应用程序.我们还希望应用程序能够快速启动.
  2. 需要以不需要重新启动应用程序的方式动态扩展应用程序.
  3. 我们有一个包含许多功能的大型应用程序.当我们部署应用程序时,我们不想一直提供所有可能的功能,可能是因为我们单独许可它们,也可能是因为并非所有功能都能够在所有平台下运行.通过只放入具有我们想要的功能的文件,我们有一个分发,不需要更改任何代码或配置文件.
  1. An application performs many different actions that are chosen depending on the context of execution. For instance, an application extracts certain properties from a file. The way to extract them depends on the file type and we want to deal with many file types, but we do not want to change the application for each new file type we add. We also want the application to start quickly.
  2. An application needs to be expanded on the fly in a way that does not require the application to restart.
  3. We have a large application that contains a number of features. When we deploy the application, we do not want to provide all the possible features all the time, maybe because we licence them separately, maybe because not all of them are able to run under all platforms. By throwing in only the files with the features we want, we have a distribution that does not require changing any code or config files.

我们该怎么做?

考虑到 Perl 提供的可能性,添加动态代码的解决方案有两种:使用 eval 和使用 require.然后是模块可以帮助以更简单或更易于维护的方式做事.

How do we do it?

Given the possibilities that Perl offers, solutions to adding dynamic code come in two flavors: using eval and using require. Then there are modules that may help do things in an easier or more maintainable way.

eval 方式使用表单 eval EXPR运行时编译一段 Perl 代码.表达式可以是一个字符串,但我建议将代码放在一个文件中,并将其他类似的文件分组在一个方便的地方.然后,如果可能,使用 File::Slurp:

The eval way uses the form eval EXPR to compile a piece of Perl code at run-time. The expression could be a string but I suggest putting the code in a file and grouping other similar files in a convenient place. Then, if possible using File::Slurp:

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 中,我们将有:

Specifying the character set to read_file makes sure that the file will be interpreted correctly. It is also good to check that the compilation was correct and that the function we expect was defined. So in $file_with_code, we will have:

sub myfunction(...) {
  # Do whatever; maybe return something
}

然后就可以正常调用该函数了.该函数将根据加载的文件而不同.简单而动态.

Then you may invoke the function normally. The function will be a different one depending on which file was loaded. Simple and dynamic.

考虑到可维护性,我的做法是使用 require.与 use编译时评估不同,require 可用于在运行时加载模块>.在调用 require 的各种方法中,我会选择:

The way I would do it with maintainability in mind would be using require. Unlike use, that is evaluated at compile-time, require may be used to load a module at run-time. Out of the various ways to invoke require, I would go for:

my $mymodule = 'MyCompany::MyModule'; # The module name ends up in $mymodule
require $mymodule;

也不同于userequire 将加载模块但不会执行import.所以我们可以使用模块内的任何函数,这些函数名称不会污染调用命名空间.要访问我们需要使用的函数:

Also unlike use, require will load the module but will not execute import. So we may use any functions inside the module and those function names will not polute the calling namespace. To access the function we will need to use:

$mymodule->myfunction($a, $b);

有关如何传递参数的信息,请参见下文.这种调用函数的方式会在 $a$b 之前添加一个参数,通常命名为 $self.如果您对面向对象一无所知,可以忽略它.

See below as to how the arguments get passed. This way of invoking a function will add an argument before $a and $b that is usually named $self. You may ignore it if you don´t know anything about object orientation.

由于 require 会尝试加载一个模块,并且该模块可能不存在或无法编译,因此最好使用以下方法来捕获错误:

As require will try to load a module and the module may not exist or it may not compile, to catch the error it will be better to use:

eval "require $mymodule";

然后$@可以用来检查加载+编译过程中的错误.我们还可以检查函数是否已定义:

Then $@ may be used to check for an error in the loading+compiling process. We may also check that the function has been defined with:

if( $mymodule->can('myfunction') ) {
  die "myfunction is not defined at module $mymodule\n";
}

在这种情况下,我们需要为模块创建一个目录,并为每个模块创建一个带有 .pm 扩展名的文件:

In this case we will need to create a directory for the modules and a file with the .pm extension for each one:

MyCompany
  MyModule.pm

MyModule.pm 中,我们将有:

package MyCompany::MyModule;

sub myfunction {
  my ($self, $a, $b);

  # Do whatever; maybe return something
  # $self will be 'MyCompany::MyModule'
}

1;

package 位是必不可少的,将使确保我们放入的任何定义都在 MyCompany::MyModule 命名空间中.末尾的 1; 会告诉 require 模块初始化是正确的.

The package bit is essential and will make sure that whatever definitions we put inside will be at the MyCompany::MyModule namespace. The 1; at the end will tell require that the module initialization was correct.

如果我们想使用其他可能污染调用者命名空间的库来实现该模块,我们可以使用 namespace::clean 模块.该模块将确保调用者不会从我们正在定义的模块中获得对命名空间的任何添加.它是这样使用的:

In case we wanted to implement the module by using other libraries that could polute the caller namespace, we could use the namespace::clean module. This module will make sure the caller does not get any additions to the namespace coming from the module we are defining. It is used in this way:

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;

如果我们多次包含一个模块会怎样?

简短的回答是什么都没有.Perl 使用 %INC 变量跟踪哪些模块已加载以及从何处加载.userequire 都不会两次加载库.use 将任何导出的名称添加到调用者命名空间.require 也不会这样做.如果你想检查一个模块是否已经加载,你可以使用 %INC 或者更好的,你可以使用 module::loaded 这是现代 Perl 版本核心的一部分:

What happens if we include a module more than once?

The short answer is nothing. Perl keeps track of which modules have been loaded and from where using the %INC variable. Both use and require will not load a library twice. use will add any exported names to the callers namespace. require will not do that either. In case you want to check that a module has been loaded already, you could use %INC or better yet, you could use module::loaded which is part of the core in modern Perl versions:

use Module::Loaded;

if( !is_loaded( $mymodule ) {
  eval "require $mymodule" );
  ...
}

如何确保 Perl 找到我的模块文件?

对于userequire Perl 使用@INC 变量来定义将用于查找库的目录列表.可以通过将新目录添加到 PERL5LIB 环境变量或使用:

How do I make sure Perl finds my module files?

For use and require Perl uses the @INC variable to define the list of directories that will be used to look for libraries. Adding a new directory to it may be achieved (among other ways) by adding it to the PERL5LIB environment variable or by using:

use lib '/the/path/to/my/libs';

辅助库

我发现了一些可以用来使使用动态机制的代码更易于维护的库.它们是:

Helper libraries

I have found some libraries that may be used to make the code that uses the dynamic mechanism more maintainable. They are:

  • if 模块:是否加载模块取决于条件:use if CONDITION, MODULE =>参数;.也可用于卸载模块.
  • Module::Load::Conditional:在尝试加载模块时不会死在你身上,也可用于检查模块版本或其依赖项.它还能够一次性加载模块列表,甚至在加载之前检查它们的版本.
  • The if module: will load a module or not depending on a condition: use if CONDITION, MODULE => ARGUMENTS;. May also be used to unload a module.
  • Module::Load::Conditional: will not die on you while trying to load a module and may also be used to check the module version or its dependencies. It is also able to load a list of modules all at once even checking their versions before doing so.

取自 Module::Load::Conditional 文档:

Taken from the Module::Load::Conditional documentation:

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';

这篇关于将动态代码添加到 perl 应用程序的最佳方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆