考虑到Perl中的包名,如何从包中调用子例程?

Sam*_*Sam 5 reflection methods perl package

给定一个包含表示包名称的字符串的变量,如何调用包的特定子例程?

这是我发现的最接近的事情:

package MyPackage;

sub echo {
    print shift;
}

my $package_name = 'MyPackage';
$package_name->echo('Hello World');

1;
Run Code Online (Sandbox Code Playgroud)

这段代码的问题是子程序被称为类方法; 包名称作为第一个参数传入.我想从包名称调用子例程,而不会隐式传入特殊的第一个参数.

fri*_*edo 8

听起来你不想将它实际称为方法,而是作为常规子例程.在这种情况下,您可以使用符号引用:

my $package_name = 'MyPackage';
{ 
    no strict 'refs';
    &{ $package_name . '::echo' }( 'Hello World' );
}
Run Code Online (Sandbox Code Playgroud)

  • 如果你使用`$ :: {$ package_name.'::'} {echo}`你不需要使用`no strict'refs';` (2认同)

Bra*_*ert 4

Perl 方法调用只是常规子例程,它将调用者作为第一个值。

use strict;
use warnings;
use 5.10.1;

{
  package MyPackage;
  sub new{ bless {}, shift } # overly simplistic constructor (DO NOT REUSE)
  sub echo{ say @_ }
}

my $package_name = 'MyPackage';
$package_name->echo;

my $object = $package_name->new();
$object->echo; # effectively the same as MyPackage::echo($object)
Run Code Online (Sandbox Code Playgroud)
use strict;
use warnings;
use 5.10.1;

{
  package MyPackage;
  sub new{ bless {}, shift } # overly simplistic constructor (DO NOT REUSE)
  sub echo{ say @_ }
}

my $package_name = 'MyPackage';
$package_name->echo;

my $object = $package_name->new();
$object->echo; # effectively the same as MyPackage::echo($object)
Run Code Online (Sandbox Code Playgroud)

如果您想在没有调用者的情况下调用子例程,则需要以不同的方式调用它。

{
  no strict 'refs';
  ${$package_name.'::'}{echo}->('Hello World');
  &{$package_name.'::echo'}('Hello World');
}

# only works for packages without :: in the name
$::{$package_name.'::'}{echo}->('Hello World');

$package_name->can('echo')->('Hello World');
Run Code Online (Sandbox Code Playgroud)
  • can方法返回对子例程的引用,如果在调用者上调用了该子例程,则该子例程将被调用。然后可以单独使用 coderef。

    my $code_ref = $package_name->can('echo');
    $code_ref->('Hello World');
    
    Run Code Online (Sandbox Code Playgroud)

    使用时有一些注意事项can

    • can可能会被包或其继承的任何类覆盖。
    • 定义方法的包可能与调用者不同。


    这实际上可能是您正在寻找的行为。

  • 另一种方法是使用称为符号引用的东西。

    {
      no strict 'refs';
      &{ $package_name.'::echo' }('Hello World');
    }
    
    Run Code Online (Sandbox Code Playgroud)

    通常不建议使用符号引用。部分问题在于,可能会意外地使用您不打算使用的符号引用。这就是为什么你不能use strict 'refs';生效的原因。

    这可能是做你想做的事情的最简单的方法。

  • 如果您不想使用符号引用,可以使用Stash

    $MyPackage::{echo}->('Hello World');
    $::{'MyPackage::'}{echo}->('Hello World');
    
    $main::{'MyPackage::'}{echo}->('Hello World');
    $main::{'main::'}{'MyPackage::'}{echo}->('Hello World');
    $main::{'main::'}{'main::'}{'main::'}{'MyPackage::'}{echo}->('Hello World');
    
    Run Code Online (Sandbox Code Playgroud)

    唯一的问题是你必须$package_name分开::

    *Some::Long::Package::Name::echo = \&MyPackage::echo;
    
    $::{'Some::'}{'Long::'}{'Package::'}{'Name::'}{echo}('Hello World');
    
    sub get_package_stash{
      my $package = shift.'::';
      my @package = split /(?<=::)/, $package;
      my $stash = \%:: ;
      $stash = $stash->{$_} for @package;
      return $stash;
    }
    get_package_stash('Some::Long::Package::Name')->{echo}('Hello World');
    
    Run Code Online (Sandbox Code Playgroud)

    但这并不是什么大问题。快速查看CPAN后,您会发现Package::Stash

    use Package::Stash;
    my $stash = Package::Stash->new($package_name);
    my $coderef = $stash->get_symbol('&echo');
    $coderef->('Hello World');
    
    Run Code Online (Sandbox Code Playgroud)

    纯 Perl版本的Package::Stash使用符号引用,而不是 Stash)


甚至可以为子例程/方法创建一个别名,就好像从使用Exporter 的模块导入一样:

*echo = \&{$package_name.'::echo'};
echo('Hello World');
Run Code Online (Sandbox Code Playgroud)

我建议限制别名的范围:

{
  local *echo = \&{$package_name.'::echo'};
  echo('Hello World');
}
Run Code Online (Sandbox Code Playgroud)

这是一个例外,您可以在启用的情况下使用符号引用strict 'refs'