【问题标题】:Call a subroutine defined as a variable调用定义为变量的子程序
【发布时间】:2018-07-12 12:26:16
【问题描述】:

我正在开发一个在不同文件中使用不同子例程的程序。

分为三个部分

  • 带有子程序名称的文本文件

  • 带有子例程的 Perl 程序

  • 提取子程序名称并启动它的主程序

子程序从文本文件中获取数据。

我需要用户选择文本文件,然后程序提取子程序的名称。

文本文件包含

cycle.name=cycle01

这是主程序:

# !/usr/bin/perl -w

use strict;
use warnings;

use cycle01;

my $nb_cycle = 10;

# The user chooses a text file

print STDERR "\nfilename: ";

chomp($filename = <STDIN>);

# Extract the name of the cycle 

open (my $fh, "<", "$filename.txt") or die "cannot open $filename";

while ( <$fh> ) {

    if ( /cycle\.name/ ) {

        (undef, $cycleToUse) = split /\s*=\s*/;
    }
}

# I then try to launch the subroutine by passing variables.
# This fails because the subroutine is defined as a variable.

$cycleToUse($filename, $nb_cycle);

这是另一个文件中的子程序

# !/usr/bin/perl

package cycle01;

use strict;
use warnings;

sub cycle01 {

    # Get the total number of arguments passed
    my ($filename, $nb_cycle) = @_;
    print "$filename, $nb_cycle";

【问题讨论】:

  • 不应该将$cycleToUse 分配给split 调用返回的第二个项目吗?喜欢:(undef, $cycleToUse) = split /\s*=\s*/。注意:那么您还应该在调用 split 之前在行中使用 chomp 以避免换行符潜入值中...要调用在变量中定义的子例程,我将使用调度表,例如my %dispatch = (cycle01 =&gt; \&amp;cycle01::cycle01 ) 然后像这样调用sub$dispatch{$cycleToUse}-&gt;($filename, $nb_cycle)

标签: perl subroutine data-transfer


【解决方案1】:

您的代码无法编译,因为在最后一次调用中,您输入了错误的名称$nb_cycle。如果您发布实际运行的代码会很有帮助:-)

传统上,Perl 模块名称以大写字母开头,因此您可能希望将包重命名为 Cycle01

执行此操作的快速而肮脏的方法是使用eval 的字符串版本。但是评估包含代码的任意字符串是危险的,所以我不会向您展示。最好的方法是使用调度表——基本上是一个散列,其中键是有效的子例程名称,值是对子例程本身的引用。添加它的最佳位置是在Cycle01.pm 文件中:

our %subs = (
  cycle01 => \&cycle01,
);

那么,你的程序结束就变成了:

if (exists $Cycle01::subs{$cycleToUse}) {
  $Cycle01::subs{$cycleToUse}->($filename, $nb_cycle);
} else {
  die "$cycleToUse is not a valid subroutine name";
}

(请注意,在while 循环中阅读这些行时,您还需要chomp()。)

【讨论】:

  • 非常感谢,您知道在主程序末尾不要使用子程序cycle01的名称吗?因为后面会添加一些子程序cycle02、03...,主程序一定不能改。
  • 这不是子程序的名称。这是模块的名称。您在程序顶部加载的那个 - use Cycle01
  • $filename也没有声明。
【解决方案2】:

以 Dave Cross 的回答为基础,我通常避免使用哈希表,部分原因是在 perl 中,无论如何,一切都是哈希表。相反,我的所有入口点子都以特定前缀开头,该前缀取决于我在做什么,但在这里我们只使用ep_ 作为入口点。然后我做这样的事情:

my $subname = 'ep_' . $cycleToUse;
if (my $func = Cycle01->can($subname))
{
  $func->($filename, $nb_cycle);
}
else
{
  die "$cycleToUse is not a valid subroutine name";
}

UNIVERSAL 中的 can 方法为我从 perl 的哈希表中提取 CODE 引用,而不是我自己维护(忘记更新它)。前缀允许我在同一命名空间中拥有其他无法由用户代码直接调用的函数和方法,允许我仍然将代码重构为常用函数等。

如果您还想拥有其他命名空间,我建议将它们都放在一个父命名空间中,并且可能都以相同的方式添加前缀,并且理想情况下,不要允许 ::' (单引号)在这些名称中,以便您将用户可能调用的范围缩小到您愿意测试的范围内。

例如,

die "Invalid namespace $cycleNameSpaceToUse"
  if $cycleNameSpaceToUse =~ /::|'/;
my $ns = 'UserCallable::' . $cycleNameSpaceToUse;
my $subname = 'ep_' . $cycleToUse;
if (my $func = $ns->can($subname))
# ... as before

采用其他方式肯定有好处,例如明确说明您要公开的内容。这里的优点是不必维护单独的列表。我总是不擅长这样做。

【讨论】:

  • 您在子例程名称中添加前缀的奇怪习惯与问题无关,反而会使事情变得混乱。
  • @Borodin 这里的重点是使用can 来获取代码参考,奇怪的前缀习惯就是我如何确保不受信任的数据不会调用任意代码(因为接受的答案也可以确保) .
  • 这个方法被污染了。你怎么知道一个模块不会为其导入的子例程使用前缀?最好使用调度表并尽量减少可能的错误。
猜你喜欢
  • 1970-01-01
  • 2015-05-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多