【问题标题】:How can I elegantly call a Perl subroutine whose name is held in a variable?如何优雅地调用名称保存在变量中的 Perl 子例程?
【发布时间】:2010-12-27 07:14:09
【问题描述】:

我将要在运行时调用的子例程的名称保存在一个名为 $action 的变量中。然后我用它在正确的时间调用那个子:

&{\&{$action}}();

工作正常。我唯一不喜欢的是它很丑,每次我这样做时,我都觉得很感激为下一个开发者添加评论:

# call the sub by the name of $action

有人知道这样做更漂亮的方法吗?


更新:这里的想法是避免每次我添加一个新的可调用子时都必须维护一个调度表,因为我是唯一的开发人员,我不担心其他程序员遵循或不遵循“规则”。为了我的方便,牺牲了一点安全性。相反,我的调度模块将检查 $action 以确保 1)它是定义的子例程的名称,而不是使用 eval 运行的恶意代码,以及 2)它不会运行任何以下划线开头的子程序,这将是按此命名约定标记为仅限内部的子。

对这种方法有什么想法吗?将调度表中的子例程列入白名单是我一直都会忘记的事情,我的客户宁愿我在“它有效”方面犯错,而不是“它非常安全”。 (开发应用的时间非常有限)


最终更新:我想我已经决定使用调度表了。虽然我很好奇读到这个问题的人是否曾经试图废除一个问题以及他们是如何做到的,但我必须向这里的集体智慧低头。谢谢大家,很多很好的回应。

【问题讨论】:

  • 意识到当你引用某个东西来立即取消引用它时,你可以跳过中间步骤。 :)
  • @brian:通常(在这种情况下肯定是这样),但请参阅stackoverflow.com/questions/1836178/… 以获取反例。 :)
  • 一个调度表也意味着你可以use strict,这通常是一件好事。

标签: perl


【解决方案1】:

与其将子例程名称存储在变量中并调用它们,更好的方法是使用子例程引用的哈希(也称为dispatch table。)

my %actions = ( foo => \&foo,
                bar => \&bar,
                baz => sub { print 'baz!' } 
                ... 
              );

那么你就可以轻松调用正确的了:

$actions{$action}->();

您还可以添加一些检查以确保 $action 是哈希中的有效键,等等。

一般来说,您应该避免符号引用(您现在正在做的事情),因为它们会导致各种问题。此外,使用真正的子程序引用将在 strict 开启的情况下工作。

【讨论】:

  • 我在下面的另一个答案中重复了我的评论,因为这个答案是迄今为止最高分!如果它对任何人有帮助,如果您还想将参数传递给子程序,只需将它们放在 () 之间 - 即与普通子程序相同。例如$actions{$action}->($parmVariable,11,'a'); 参数必须在所有可能被调用的子例程中保持一致,尽管值当然可以来自变量。
  • “调度表”链接似乎被劫持到了一个不相关的站点。也许不违法,但肯定与 perl 无关。
【解决方案2】:

只是&$action(),但通常从一开始就使用 coderefs 或使用调度程序哈希会更好。例如:

my $disp = {foo => \&some_sub, bar => \&some_other_sub };
$disp->{'foo'}->();

【讨论】:

  • 使用 &$action 解决方案我得到:不能使用字符串(“main::instructors”)作为子程序引用,而“严格引用”。那我做体操是不是因为我严格开启了?
  • 是的,这种用法是 'use strict' 阻止的(除其他外)。使用 'no strict "refs"' 来允许它,但同样,真正的引用或调度程序通常更清晰。
  • 调度表答案,它使用真实的(而不是符号的)引用,当然会在strict打开的情况下工作,这是这样做的另一个好处。
  • use strict qw(vars subs)use strict; no strict 'refs'; 的更短方式
  • @mobrule: 直到有人添加第四类限制,即:)
【解决方案3】:

嗯?你可以说

    $action->()

例子:

    sub f { return 11 }
    $action = 'f';
    print $action->();


    $ perl subfromscalar.pl
    11

类似的结构

    'f'->()     # equivalent to   &f()

也可以。

【讨论】:

  • 如果你 use strict(当然你这样做了),你需要说 no strict 'refs' 或只是 use strict qw(vars subs) 来使用这个语法。
【解决方案4】:

我不确定我明白你的意思。 (我认为这是最近一组“如何将变量用作变量名?”的另一个问题,但也许不是。)

无论如何,你应该能够将整个子程序分配给一个变量(作为引用),然后直接调用它:

# create the $action variable - a reference to the subroutine
my $action = \&preach_it;
# later - perhaps much later - I call it
$action->();

sub preach_it {
    print "Can I get an amen!\n"
}

【讨论】:

    【解决方案5】:

    最重要的是:为什么要用变量作为函数名。如果它是“eval”会发生什么? 是否有可以使用的功能列表?或者它可以是任何功能吗?如果列表存在 - 它有多长?

    一般来说,处理这种情况的最好方法是使用调度表:

    my %dispatch = (
       'addition' => \&some_addition_function,
       'multiplication' => sub { $self->call_method( @_ ) },
    );
    

    然后就是:

    $dispatch{ $your_variable }->( 'any', 'args' );
    

    【讨论】:

      【解决方案6】:
      __PACKAGE__->can($action)->(@args);
      

      有关 can() 的更多信息:http://perldoc.perl.org/UNIVERSAL.html

      【讨论】:

        【解决方案7】:

        我做类似的事情。我把它分成两行让它更容易辨认,但它并没有更漂亮。

        my $sub = \&{$action};
        $sub->();
        

        我不知道更正确或更漂亮的方法。值得一提的是,我们有生产代码可以完成您正在做的事情,并且无需禁用 use strict 即可工作。

        【讨论】:

        • 如果它对任何人有帮助,如果您还想将参数传递给子程序,您只需将它们放在 () 之间 - 即与普通子程序相同。例如$sub->($parmVariable,11,'a');
        • 从子内部反射性地调用它;我的 $this = \&{(调用者(0))[3]}; $return = $this->(); $return .= $this->(\@flags,$arguments,\%foo);
        【解决方案8】:

        Perl 中的每个包都已经是一个哈希表。您可以添加元素并通过正常的散列操作引用它们。一般来说,不需要通过额外的哈希表来复制功能。

        #! /usr/bin/perl -T
        use strict;
        use warnings;
        
        my $tag = 'HTML';
        
        *::->{$tag} = sub { print '<html>', @_, '</html>', "\n" };
        
        HTML("body1");
        
        *::->{$tag}("body2");
        

        代码打印:

        body1 body2

        如果你需要一个单独的名字空间,你可以定义一个专用的包。

        更多信息请参见perlmod

        【讨论】:

        • 链接到的页面很长,但我认为您指的是Symbol Tables 部分。
        【解决方案9】:

        随便用

        &{\&{$action}}();
        

        或者使用eval执行函数:

        eval("$action()");
        

        【讨论】:

          【解决方案10】:

          我是这样做的:

          @func = qw(cpu mem net disk);
          foreach my $item (@func){
              $ret .= &$item(1);
          }
          

          【讨论】:

            【解决方案11】:

            如果只在一个程序中,写一个函数,使用变量名调用子程序,只需要记录/道歉一次?

            【讨论】:

              【解决方案12】:

              我用过这个:它对我有用。

              (\$action)->();
              

              或者你可以使用'do',与之前的帖子非常相似:

              $p = do { \&$conn;}; 
              $p->();
              

              【讨论】:

                猜你喜欢
                • 2023-03-13
                • 2015-04-08
                • 2020-03-02
                • 2017-05-19
                • 1970-01-01
                • 1970-01-01
                • 2015-09-24
                • 2013-02-15
                • 1970-01-01
                相关资源
                最近更新 更多