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

发布于 2024-11-04 21:14:04 字数 509 浏览 0 评论 0原文

以下 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} @_;
}

生成的警告是:

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

有关该警告的更多信息请参见此处

我不理解该警告,因为我不认为我正在修改 $_,尽管我想我必须这样做。 有人可以向我解释一下吗?

The following perl code generates a warning in PerlCritic (by 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} @_;
}

The warning generated is:

Don't modify $_ in list functions

More info about that warning here

I don't understand the warning because I don't think I'm modifying $_, although I suppose I must be.
Can someone explain it to me please?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

只想待在家 2024-11-11 21:14:04

您的两个 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 }
           @_;
}

这应该做同样的事情并且让批评者保持安静。您可能想看看 List::MoreUtils 如果您想要一些比普通 map 更好的列表运算符。

Both of your greps are modifying $_ because you're using s//. For example, this:

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

is the same as this:

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

I think you'd be better off using map as you are not filtering anything with your greps, you're just using grep as an iterator:

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 }
           @_;
}

That should do the same thing and keep critic quiet. You might want to have a look at List::MoreUtils if you want some nicer list operators than plain map.

无力看清 2024-11-11 21:14:04

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

You are doing a substitution ( i.e. s/// ) in the grep, which modifies $_ i.e. the list being grepped.

和我恋爱吧 2024-11-11 21:14:04

这种情况和其他情况在 perldoc perlvar 中进行了解释:

这里是 Perl 会用到的地方
即使您不使用它,也假设 $_:

  • 以下功能:

abs、警报、chomp、chop、chr、chroot、
cos、已定义、eval、exp、glob、十六进制、
int、lc、lcfirst、长度、日志、lstat、
mkdir、oct、ord、pos、打印、
quotemeta、readlink、readpipe、ref、
要求,反转(在标量上下文中
仅)、rmdir、sin、split(在其
第二个参数)、sqrt、stat、study、
uc、ucfirst、取消链接、解压。

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

  • 模式匹配操作 m//、s/// 和 tr///(又名 y///)时
    不使用 =~ 运算符即可使用。

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

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

  • given() 的隐式变量。

  • 操作结果时放置输入记录的默认位置
    单独测试
    一段时间测试的标准。外面
    在测试时,不会发生这种情况。

This and other cases are explained in perldoc perlvar:

Here are the places where Perl will
assume $_ even if you don't use it:

  • The following functions:

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,
require, reverse (in scalar context
only), rmdir, sin, split (on its
second argument), sqrt, stat, study,
uc, ucfirst, unlink, unpack.

  • All file tests (-f , -d ) except for -t , which defaults to STDIN.
    See -X

  • The pattern matching operations m//, s/// and tr/// (aka y///) when
    used without an =~ operator.

  • The default iterator variable in a foreach loop if no other variable is
    supplied.

  • The implicit iterator variable in the grep() and map() functions.

  • The implicit variable of given().

  • The default place to put an input record when a operation's result
    is tested by itself as the sole
    criterion of a while test. Outside a
    while test, this will not happen.

第七度阳光i 2024-11-11 21:14:04

很多人都正确地回答了 s 运算符正在修改 $_,但是在即将发布的 Perl 5.14.0 中将会有新的 r s 运算符(即 s///r)的 code> 标志,它不会就地修改,而是返回修改后的元素。欲了解更多信息,请访问 有效的佩勒。您可以使用 perlbrew 来安装这个新版本。

编辑:Perl 5.14 现已推出! 公告 公告 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 }
           @_;
}

Many people have correctly answered that the s operator is modifying $_, however in the soon to be released Perl 5.14.0 there will be the new r flag for the s operator (i.e. s///r) which rather than modify in-place will return the modified elements. Read more at The Effective Perler . You can use perlbrew to install this new version.

Edit: Perl 5.14 is now available! Announcement Announcement Delta

Here is the function suggested by mu (using map) but using this functionality:

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 }
           @_;
}
鸠书 2024-11-11 21:14:04

其他答案忽略的非常重要的部分是,该行

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 变换 的 Perl 习惯用法,这是实现该目标的更有效方法。

The VERY important part that other answers have missed is that the line

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

Is actually modifying the arguments passed into the function, and not copies of them.

grep is a filtering command, and the value in $_ inside the code block is an alias to one of the values in @_. @_ in turn contains aliases to the arguments passed to the function, so when the s/// operator performs its substitution, the change is being made to the original argument. This is shown in the following example:

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

The behavior you are looking for (apply a function that modifies $_ to a copy of a list) has been encapsulated as the apply function in a number of modules. My module List::Gen contains such an implementation. apply is also fairly simple to write yourself:

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

With that, your code could be rewritten as:

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

If your goal with the repeated substitutions is to perform a sort of the original data with a transient modification applied, you should look into a Perl idiom known as the Schwartzian transform which is a more efficient way of achieving that goal.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文