在这个perl代码中,$ _被修改在哪里?

时间:2011-05-05 07:47:21

标签: perl natural-sort perl-critic

以下perl代码在PerlCritic中生成警告(由Activestate生成):

sub natural_sort {
    my @sorted;
    @sorted = grep {s/(^|\D)0+(\d)/$1$2/g,1} sort grep {s/(\d+)/sprintf"%06.6d",$1/ge,1} @_;
}

生成的警告是:

  

不要修改列表函数中的$ _

More info about that warning here

我不明白警告,因为我不认为我正在修改$ _,虽然我想我一定是。 有人可以向我解释一下吗?

5 个答案:

答案 0 :(得分:10)

您的grep正在修改$_,因为您正在使用s//。例如,这个:

grep {s/(^|\D)0+(\d)/$1$2/g,1}

与此相同:

grep { $_ =~ s/(^|\D)0+(\d)/$1$2/g; 1 }

我认为您最好不要使用map,因为您没有使用grep过滤任何内容,而只是使用grep作为迭代器:

sub natural_sort {
    my $t;
    return map { ($t = $_) =~ s/(^|\D)0+(\d)/$1$2/g; $t }
           sort
           map { ($t = $_) =~ s/(\d+)/sprintf"%06.6d",$1/ge; $t }
           @_;
}

那应该做同样的事情,让评论家保持沉默。如果您想要一些比普通map更好的列表运算符,您可能需要查看List::MoreUtils

答案 1 :(得分:3)

你正在grep中进行替换(即s///),这会修改$_,即列表被grepped。

答案 2 :(得分:3)

perldoc perlvar

中解释了这个和其他情况
  

以下是Perl的地方   假设$ _即使您不使用它:

     
      
  • 以下功能:
  •   
     

abs,alarm,chomp,chop,chr,chroot,   cos,defined,eval,exp,glob,hex,   int,lc,lcfirst,length,log,lstat,   mkdir,oct,ord,pos,print,   quotemeta,readlink,readpipe,ref,   要求,反向(在标量上下文中   只有),rmdir,罪,分裂(在它上面)   第二个论点),sqrt,stat,study,   uc,ucfirst,unlink,unpack。

     
      
  • 除-t之外的所有文件测试(-f,-d),默认为STDIN。   见-X

  •   
  • 模式匹配操作m //,s ///和tr ///(又名y ///)时   在没有=〜运算符的情况下使用。

  •   
  • 如果没有其他变量,则foreach循环中的默认迭代器变量   提供。

  •   
  • grep()和map()函数中的隐式迭代器变量。

  •   
  • 给定()的隐式变量。

  •   
  • 操作结果时输入记录的默认位置   被自己作为唯一的测试   一时间测试的标准。在外面   在测试时,这不会发生。

  •   

答案 3 :(得分:2)

很多人都正确地回答了s运算符正在修改$_,但是在即将发布的Perl 5.14.0中,r会有s个标记。 1}}运算符(即s///r)而不是就地修改将返回修改后的元素。阅读更多 The Effective Perler 。您可以使用 perlbrew 安装此新版本。

编辑:Perl 5.14现已推出! Announcement Announcement Delta

以下是mu建议的功能(使用map),但使用此功能:

use 5.14.0;

sub natural_sort {
    return map { s/(^|\D)0+(\d)/$1$2/gr }
           sort
           map { s/(\d+)/sprintf"%06.6d",$1/gre }
           @_;
}

答案 4 :(得分:1)

其他答案错过的非常重要的部分是行

grep {s/(\d+)/sprintf"%06.6d",$1/ge,1} @_;

实际上是修改传递给函数的参数,而不是它们的副本。

grep是一个过滤命令,代码块中$_内的值是@_中某个值的别名。 @_反过来包含传递给函数的参数的别名,因此当s///运算符执行其替换时,将对原始参数进行更改。这在以下示例中显示:

sub test {grep {s/a/b/g; 1} @_}

my @array = qw(cat bat sat);

my @new = test @array;

say "@new";   # prints "cbt bbt sbt" as it should
say "@array"; # prints "cbt bbt sbt" as well, which is probably an error

您正在寻找的行为(应用将$_修改为列表副本的函数)已被封装为许多模块中的apply函数。我的模块List::Gen包含这样的实现。 apply写自己也很简单:

sub apply (&@) {
    my ($sub, @ret) = @_;
    $sub->() for @ret;
    wantarray ? @ret : pop @ret
}

这样,您的代码可以重写为:

sub natural_sort {
    apply {s/(^|\D)0+(\d)/$1$2/g} sort apply {s/(\d+)/sprintf"%06.6d",$1/ge} @_
}

如果重复替换的目标是执行一种应用了瞬态修改的原始数据,那么您应该研究一种称为Schwartzian transform的Perl习语,这是实现该目标的更有效方法