Perl 变量范围问题

Posted

技术标签:

【中文标题】Perl 变量范围问题【英文标题】:Perl variable scope question 【发布时间】:2010-11-22 11:04:06 【问题描述】:

所以我有一个 Perl 类。它有一个sort() 方法,我希望它或多或少与内置的sort() 函数相同:

$object->sort(sub ($$)  $_[0] <=> $_[1] );

但我做不到:

$object->sort(sub  $a <=> $b );

因为范围。但是 List::Util 模块使用reduce() 执行此操作。我查看了 List::Util 模块,他们使用no strict 'vars' 做了一些相当讨厌的事情来实现这一点。我试过了,但没有用。

据我了解,reduce() 以它的方式工作,因为它被导出到适当的命名空间中,因此我的类不能这样做,因为该函数非常牢固地位于另一个命名空间中。这是正确的,还是在我的情况下有一些(无疑更可怕和不明智的)方法可以做到这一点?

【问题讨论】:

【参考方案1】:

嗯,其他两个答案都对了一半。这是一个实际排序的有效解决方案:

package Foo;

use strict;
use warnings;

sub sort 
    my ($self, $sub) = @_;

    my ($pkg) = caller;
    my @x = qw(1 6 39 2 5);
    print "@x\n";
    
        no strict 'refs';
        @x = sort 
            local ($$pkg.'::a', $$pkg.'::b') = ($a, $b);
            $sub->();
         @x;
    
    print "@x\n";

    return;



package main;

use strict;
use warnings;

my $foo = ;
bless $foo, 'Foo';

$foo->sort(sub  $a <=> $b );
# 1 6 39 2 5
# 1 2 5 6 39

大概你会对一些实际上是对象一部分的数据进行排序。

您需要caller 魔法,以便在调用者的包中本地化$a$b,这是Perl 将在其中寻找它们的地方。它正在创建仅在调用 sub 时才存在的全局变量。

请注意,warnings 将获得一个“仅使用一次的名称”;我敢肯定,您可以通过某种方式来避免这种情况。

【讨论】:

这对于您的目的可能已经足够了,但它很脆弱。不能保证比较函数与sort 方法的调用者属于同一个包。这就是 Sub::Identify 的用武之地。 @cjm - 这是真的,我肯定会研究 Sub::Identify,但我更大的问题是让它工作,而不是让它在一般情况下工作。具体解决方案优于一般故障。但是,将此答案与您的答案结合起来会给我一个通用的解决方案,这是一件好事。 虽然事实证明 sort 内置有同样的问题。它假定比较函数来自与调用者相同的包。因此,如果您可以忍受,您可以保存对 Sub::Identify 的依赖。 (或者您可以有条件地要求 Sub::Identify,如果未安装,则回退到 caller。但这需要更多工作。) 其实这正是我一直在考虑的,我想我会去做的。 啊哈,我从来没想过。虽然我不确定是否应该鼓励这样的事情。【参考方案2】:

您可以使用Sub::Identify 找出与coderef 关联的包(它称为stash_name)。然后根据需要在该包中设置 $a 和 $b。您可能需要在您的方法中使用 no strict 'refs' 才能使其正常工作。

这是修改后的 Evee 的答案以适用于一般情况:

use strict;
use warnings;

package Foo;

use Sub::Identify 'stash_name';

sub sort 
    my ($self, $sub) = @_;

    my $pkg = stash_name($sub);
    my @x = qw(1 6 39 2 5);
    print "@x\n";
    
        no strict 'refs';
        @x = sort 
            local ($$pkg.'::a', $$pkg.'::b') = ($a, $b);
            $sub->();
         @x;
    
    print "@x\n";

    return;



package Sorter;

sub compare  $a <=> $b 

package main;

use strict;
use warnings;

my $foo = ;
bless $foo, 'Foo';

$foo->sort(\&Sorter::compare );

$foo->sort(sub  $b <=> $a );

【讨论】:

List::Util 只使用caller。我想这还不够,在一般情况下,是吗?如果调用者从其他包传递一个函数,那么 List::Util 将设置调用者的$a 而不是函数的。 5.10.1 中的 List::Util 版本使用 XS 代码,基本上与 Sub::Identify 做相同的事情来找出 coderef 所属的包。 另外,我可以在 XS 中重写我的模块,从而失去依赖并且让我有机会学习 XS。不过,与此同时,我会调查一下。【参考方案3】:

您可以使用the local operator 在子例程调用期间设置$a$b 的值:

sub sample

    my $callback = shift;
    for (my $i = 0; $i < @_; $i += 2) 
        local ($a, $b) = @_[$i, $i + 1];
        $callback->();
    
       

sample sub  print "$a $b\n" , qw(a b c d e f g h i j);

如果你有一个普通的子程序,而不是一个方法,那么你可以让它更像sort,所以你不需要在你的回调函数之前使用sub。在函数上使用原型:

sub sample (&@)

那你这样称呼它:

sample  print "$a $b\n"  qw(a b c d e f g h i j);

不过,方法不受原型的影响。

【讨论】:

他专门询问method,而不是普通的子。 如果您从类外调用该方法,那将不起作用。您正在本地化 类的 $a 和 $b,而不是调用者的。 啊。我以为我已经看到了这个,但我想没有。我对 perlvar 的印象是 $a$b 足够神奇,它们在这种情况下“正常工作”。 不,$a 和 $b 只是普通的包变量。 @Rob:是的,我也是这么想的——但是当我检查时,perlvar 实际上确实说它们是包变量。

以上是关于Perl 变量范围问题的主要内容,如果未能解决你的问题,请参考以下文章

Perl subs 不是词法范围的设计缺陷吗?

搜索日志文件以获取 2 个纪元时间之间的条目范围

您好!想请问您一个关于perl安装路径及系统环境变量的问题。

perl6 subs 真的是词法范围还是有额外的?

Perl 中的快速字符串校验和函数生成 0..2^32-1 范围内的值

Perl DBI Sybase Asanywhere 绑定变量问题