修改 $_ 的 Perl 函数
Perl functions that modify $_
我试图在我的代码中扩展隐式 $_
(全局 "topic" 变量)的用法。 Perlmonks 在没有显式变量的情况下接受 $_
的函数有 this (outdated?) article。
我遇到的问题是我不知道设置了哪些函数 $_
。我知道至少 map
、grep
和 for
/foreach
会改变 $_
的值,但我认为肯定还有更多。我也不清楚与 $_
相关的任何范围问题,如:
for (@array_of_array_refs)
{
for (@$_)
{
print;
}
print; # what does this print?
}
是否有函数列表或一组准则可供遵循,这样我就能凭直觉知道如何避免破坏$_
?
请参阅以获得更详细的解释。但我留下这个答案是因为问题上下文中的某些问题可能很难理解,并且除了 Brian 的答案之外,此答案中的不同描述和评论可能有助于更好地理解问题。
阅读 Wikipedia page for "scope" 以了解各种作用域,尤其是词法作用域和动态作用域可能也很有用。
地图、grep、for/foreach 等 "localize" $_
。这意味着它们将一个新变量绑定到 $_
并且原始变量仅在离开词法范围时才绑定到 $_
。有关此 "localizing" 的更详细描述,请参阅答案末尾。例如:
for(qw(1 2)) {
for(qw(a b)) {
print map { uc($_) } ($_,'x');
print $_
}
print $_
}
会给你 AXaBXb1AXaBXb2
,这表明每次使用 for/map 都会将 $_
绑定到一个不同的变量,并在离开块后将其绑定回前一个变量。
对于以 $_
作为默认参数的函数:除了预期的(即替代 s///
)之外,这些没有任何副作用,并且在 perldoc 中记录了当该函数或操作将使用 $_
作为默认参数。
不过自己用$_
要注意,要确保不影响之前的意思。在这种情况下,自己本地化 $_
有助于防止意外更改之前的 $_
:
sub myfunction {
local $_;
# from now on until the functions gets left changes to $_
# will not affect the previous $_
...
}
这也可以用块
{
local $_;
# from now on until the block gets left changes to $_
# will not affect the previous $_
...
}
但注意经常使用的while (<>)
不会本地化$_
:
$_ = 'foo';
say $_;
while (<>) {
say $_;
}
say $_;
在这种情况下,循环后的 say $_
将不会显示循环前的值 ('foo'
),而是显示循环中的最后一个隐式赋值 (undef
)。
到底什么是本地化?大多数习惯于在 Perl 中使用 "my" 来完成词法范围界定。但是 "localizing" 变量是不同的,无论它是用显式 local
还是隐式在 for, map...
中完成的
主要思想是通过本地化像 $_
这样的全局符号绑定到不同的变量,并且只有在词法范围结束后才恢复原始变量。因此,与词法作用域相反,这个新绑定甚至会影响从这个词法作用域内部调用的函数,即
sub foo { say $_}
$_ = 1;
foo(); # 1
{
local $_; # bind symbol $_ to new variable
$_ = 2;
foo(); # 2 - because $_ inside foo() is the same as in this block
}
foo(); # 1 # leaving the block restored original binding of $_
is misleading so I'll have to respond here. I might have missed a few things, but it's late. And, Learning Perl 已经说明了一切。 ;)
local operator does not work in lexical scope. It's not limited to the block it's in, despite what he says. People typically have this problem in understanding because they don't try it. Terms like "outside" and "inside" are misleading and dangerous for local.
考虑这种用法,其中有一个函数打印 $_
:
的全局值
$_ = 'Outside';
show_it();
inside();
$_ = 'Outside';
show_it();
sub show_it { print "$_ is $_\n"; }
sub inside {
local $_;
$_ = 'Inside';
show_it();
}
当您 运行 这样做时,您会看到块内设置的 $_
值在块外可用:
$_ is Outside
$_ is Inside
$_ is Outside
local works on package variables. It temporarily uses a new value until the end of the block. As a package variable, though, it's changed everywhere in the program until local's scope ends. The operator has a lexical scope, but its effect is everywhere. You are giving a global variable a temporary value, and that global variable is still global. local 变量具有全局效果但词法生命周期。他们更改程序中 处处 的值,直到该范围结束。
我之前写过,用local来谈论"inside"和"outside"是错误的。是 "before" 和 "after"。我将展示更多即将到来的内容,其中甚至时间都会瓦解。
my is completely different. It does not work with package variables at all. Also called "lexical variables", these don't exist at all outside their scope (even though back magic modules such as PadWalker 看看他们)。程序的任何其他部分都无法看到它们。它们仅在其作用域和在该作用域中创建的子作用域中可见。
Perl v5.10 允许我们创建 $_
(和 fixed and made experimental in v5.16—don't use it. See also The good, the bad, and the ugly of lexical $_ in Perl 5.10+)的词法版本。我可以让我之前的例子使用:
use v5.10;
$_ = 'Outside';
show_it();
inside();
$_ = 'Outside';
show_it();
sub show_it { print "$_ is $_\n"; }
sub inside {
my $_;
$_ = 'Inside';
show_it();
}
现在输出不同了。词法 $_
与任何其他词法变量具有相同的效果。它不会影响其范围之外的任何东西,同样,因为这些变量只存在于它们的词法范围内:
$_ is Outside
$_ is Outside
$_ is Outside
但是,要回答原来的问题。 Perlmonks post Builtin functions defaulting to $_ 仍然不错,但我认为它与此无关。这些功能使用$_
,而不是设置它。
了解 Perl 最重要的一点是没有简短的答案。 Perl 做有意义的事情,而不是让它保持一致的事情。毕竟,它是 post-modern language.
不担心改变$_
的方法就是不改变$_
。避免使用它。我们在 Effective Perl Programming.
中有很多类似的建议
foreach
循环结构 foreach 及其 for
同义词使用 $_
的本地化版本来引用当前主题。在循环内部,包括循环调用的任何内容,使用当前主题:
use v5.10;
$_ = 'Outside';
show_it();
sub show_it { say "$_ is $_"; }
my @array = 'a' .. 'c';
foreach ( @array ) {
show_it();
$_++
}
say "array: @array";
注意 foreach
循环后的数组。尽管 foreach
本地化了 $_
,Perl 为该值设置别名而不是复制它。更改控制变量会更改原始值,即使该值在外部词法范围内也是如此:
$_ is Outside
$_ is a
$_ is b
$_ is c
array: b c d
不要使用$_
作为控制变量。我只在非常短的程序中使用默认值,主要是因为我希望控制变量在大程序中有一个有意义的名称。
映射和 grep
与 foreach
、map
和 grep
一样,使用 $_
作为控制变量。您不能为这些使用不同的变量。您仍然可以通过我在上一节中展示的性能增强别名来影响作用域外的变量。
同样,这意味着存在一些范围泄漏。如果更改块内的 $_
并且 $_
是输入列表中的一项,则外部 $_
更改:
use v5.10;
$_ = 'Outside';
my @transformed = map { $_ = 'From map' } ( $_ );
say $_;
对于中等复杂的内联块,我将$_
分配给词法变量:
my @output = map { my $s = $_; ... } @input;
如果您真的对 $_
感到紧张,请不要在 map
中使用 map
的恶作剧:
my @words = map {
map { split } $_
} <>;
这是一个愚蠢的例子,但我过去曾做过这样的事情,我需要将主题变成一个列表。
同时( <> )
Perl 有一个方便的小习惯用法,它将文件句柄的下一行分配给 $_
。这意味着,而不是这个:
while( defined( $_ = <> ) )
你可以得到完全相同的东西:
while( <> )
但是,$_
中的任何值都会保留在 $_
中。
$_ = "Outside\n";
show_it();
sub show_it { print "$_ is $_" }
while( <DATA> ) {
show_it();
}
show_it();
__DATA__
first line
second line
third line
输出看起来有点奇怪,因为最后一行没有值,但那是分配给 $_
的最后一个值:行输入运算符在 defined
测试停止之前分配的 undef循环:
$_ is Outside
$_ is first line
$_ is second line
$_ is third line
$_ is
在那里放一个last
,输出会改变
$_ = "Outside\n";
show_it();
sub show_it { print "$_ is $_" }
while( <DATA> ) {
show_it();
last;
}
show_it();
__DATA__
first line
second line
third line
现在最后分配的值是第一行:
$_ is Outside
$_ is first line
$_ is first line
如果你不喜欢这个,请不要使用成语:
while( defined( my $line = <> ) )
模式匹配
替换运算符 s///
默认绑定到 $_
并且可以更改它(这就是重点)。但是,对于 v5.14,您可以使用 the /r
flag,它保留原始版本,returns 修改版本。
匹配运算符 m//
也可以更改 $_
。它不会更改值,但可以设置位置标志。这就是 Perl 如何在标量上下文中进行全局匹配:
use v5.10;
$_ = 'Outside';
show_it();
sub show_it { say "$_ is $_ with pos ", pos(); }
foreach my $time ( 1 .. 5 ) {
my $scalar = m/./g;
show_it();
}
show_it();
即使值相同,$_
中的某些标量设置也会发生变化:
$_ is Outside with pos
$_ is Outside with pos 1
$_ is Outside with pos 2
$_ is Outside with pos 3
$_ is Outside with pos 4
$_ is Outside with pos 5
$_ is Outside with pos 5
您可能不会对此有任何问题。您可以重置与 $_
匹配不成功的位置。也就是说,除非您使用 /c
标志。尽管标量值没有改变,但其簿记的一部分发生了变化。这是 problems with lexical $_
.
之一
匹配还有一件奇怪的事情。每个匹配变量是动态范围的。他们不改变他们在外部范围内的值:
use v5.10;
my $string = 'The quick brown fox';
OUTER: {
$string =~ /\A(\w+)/;
say "$1 is ";
INNER: {
$string =~ /(\w{5})/;
say "$1 is ";
}
say "$1 is ";
}
OUTER
范围内 </code> 的值未被 <code>INNER
中的 </code> 替换:</p>
<pre><code> is The
is quick
is The
如果这让您头疼,请不要使用每场比赛的变量。立即分配它们(并且仅当您成功匹配时):
my $string = 'The quick brown fox';
OUTER: {
my( @captures ) = $string =~ /\A(\w)/;
INNER: {
my $second_word;
if( $string =~ /(\w{5})/ ) {
$second_word =
}
}
}
除了学习所有的内置函数(你至少应该为你使用的那些)之外,这里是我认为使用 $_
的最佳指南:
- Only use
$_
when it is clearer and more obvious than using explicit variables.
感谢 $_
作为 "it"。 "It" 是代词。当你想 "I went to the store and bought ice cream" 时,你不会说 "I went to it and bought it"。只有当代词所指的内容很明显时才使用代词。
稍微修改一下您的原始代码,这为我们提供了一个有用的示例。请注意在外部作用域中使用命名(显式)变量,并在可能的最小作用域中使用 $_
(将整个内部循环缩减为一行):
# Process student records one year at a time
for my $student_records_aref (@student_records_by_year_array_refs)
{
# Print a report on each student for the current year
print_report_on($_) for @{$student_records_aref};
}
我试图在我的代码中扩展隐式 $_
(全局 "topic" 变量)的用法。 Perlmonks 在没有显式变量的情况下接受 $_
的函数有 this (outdated?) article。
我遇到的问题是我不知道设置了哪些函数 $_
。我知道至少 map
、grep
和 for
/foreach
会改变 $_
的值,但我认为肯定还有更多。我也不清楚与 $_
相关的任何范围问题,如:
for (@array_of_array_refs)
{
for (@$_)
{
print;
}
print; # what does this print?
}
是否有函数列表或一组准则可供遵循,这样我就能凭直觉知道如何避免破坏$_
?
请参阅
阅读 Wikipedia page for "scope" 以了解各种作用域,尤其是词法作用域和动态作用域可能也很有用。
地图、grep、for/foreach 等 "localize" $_
。这意味着它们将一个新变量绑定到 $_
并且原始变量仅在离开词法范围时才绑定到 $_
。有关此 "localizing" 的更详细描述,请参阅答案末尾。例如:
for(qw(1 2)) {
for(qw(a b)) {
print map { uc($_) } ($_,'x');
print $_
}
print $_
}
会给你 AXaBXb1AXaBXb2
,这表明每次使用 for/map 都会将 $_
绑定到一个不同的变量,并在离开块后将其绑定回前一个变量。
对于以 $_
作为默认参数的函数:除了预期的(即替代 s///
)之外,这些没有任何副作用,并且在 perldoc 中记录了当该函数或操作将使用 $_
作为默认参数。
不过自己用$_
要注意,要确保不影响之前的意思。在这种情况下,自己本地化 $_
有助于防止意外更改之前的 $_
:
sub myfunction {
local $_;
# from now on until the functions gets left changes to $_
# will not affect the previous $_
...
}
这也可以用块
{
local $_;
# from now on until the block gets left changes to $_
# will not affect the previous $_
...
}
但注意经常使用的while (<>)
不会本地化$_
:
$_ = 'foo';
say $_;
while (<>) {
say $_;
}
say $_;
在这种情况下,循环后的 say $_
将不会显示循环前的值 ('foo'
),而是显示循环中的最后一个隐式赋值 (undef
)。
到底什么是本地化?大多数习惯于在 Perl 中使用 "my" 来完成词法范围界定。但是 "localizing" 变量是不同的,无论它是用显式 local
还是隐式在 for, map...
主要思想是通过本地化像 $_
这样的全局符号绑定到不同的变量,并且只有在词法范围结束后才恢复原始变量。因此,与词法作用域相反,这个新绑定甚至会影响从这个词法作用域内部调用的函数,即
sub foo { say $_}
$_ = 1;
foo(); # 1
{
local $_; # bind symbol $_ to new variable
$_ = 2;
foo(); # 2 - because $_ inside foo() is the same as in this block
}
foo(); # 1 # leaving the block restored original binding of $_
local operator does not work in lexical scope. It's not limited to the block it's in, despite what he says. People typically have this problem in understanding because they don't try it. Terms like "outside" and "inside" are misleading and dangerous for local.
考虑这种用法,其中有一个函数打印 $_
:
$_ = 'Outside';
show_it();
inside();
$_ = 'Outside';
show_it();
sub show_it { print "$_ is $_\n"; }
sub inside {
local $_;
$_ = 'Inside';
show_it();
}
当您 运行 这样做时,您会看到块内设置的 $_
值在块外可用:
$_ is Outside
$_ is Inside
$_ is Outside
local works on package variables. It temporarily uses a new value until the end of the block. As a package variable, though, it's changed everywhere in the program until local's scope ends. The operator has a lexical scope, but its effect is everywhere. You are giving a global variable a temporary value, and that global variable is still global. local 变量具有全局效果但词法生命周期。他们更改程序中 处处 的值,直到该范围结束。
我之前写过,用local来谈论"inside"和"outside"是错误的。是 "before" 和 "after"。我将展示更多即将到来的内容,其中甚至时间都会瓦解。
my is completely different. It does not work with package variables at all. Also called "lexical variables", these don't exist at all outside their scope (even though back magic modules such as PadWalker 看看他们)。程序的任何其他部分都无法看到它们。它们仅在其作用域和在该作用域中创建的子作用域中可见。
Perl v5.10 允许我们创建 $_
(和 fixed and made experimental in v5.16—don't use it. See also The good, the bad, and the ugly of lexical $_ in Perl 5.10+)的词法版本。我可以让我之前的例子使用:
use v5.10;
$_ = 'Outside';
show_it();
inside();
$_ = 'Outside';
show_it();
sub show_it { print "$_ is $_\n"; }
sub inside {
my $_;
$_ = 'Inside';
show_it();
}
现在输出不同了。词法 $_
与任何其他词法变量具有相同的效果。它不会影响其范围之外的任何东西,同样,因为这些变量只存在于它们的词法范围内:
$_ is Outside
$_ is Outside
$_ is Outside
但是,要回答原来的问题。 Perlmonks post Builtin functions defaulting to $_ 仍然不错,但我认为它与此无关。这些功能使用$_
,而不是设置它。
了解 Perl 最重要的一点是没有简短的答案。 Perl 做有意义的事情,而不是让它保持一致的事情。毕竟,它是 post-modern language.
不担心改变$_
的方法就是不改变$_
。避免使用它。我们在 Effective Perl Programming.
foreach
循环结构 foreach 及其 for
同义词使用 $_
的本地化版本来引用当前主题。在循环内部,包括循环调用的任何内容,使用当前主题:
use v5.10;
$_ = 'Outside';
show_it();
sub show_it { say "$_ is $_"; }
my @array = 'a' .. 'c';
foreach ( @array ) {
show_it();
$_++
}
say "array: @array";
注意 foreach
循环后的数组。尽管 foreach
本地化了 $_
,Perl 为该值设置别名而不是复制它。更改控制变量会更改原始值,即使该值在外部词法范围内也是如此:
$_ is Outside
$_ is a
$_ is b
$_ is c
array: b c d
不要使用$_
作为控制变量。我只在非常短的程序中使用默认值,主要是因为我希望控制变量在大程序中有一个有意义的名称。
映射和 grep
与 foreach
、map
和 grep
一样,使用 $_
作为控制变量。您不能为这些使用不同的变量。您仍然可以通过我在上一节中展示的性能增强别名来影响作用域外的变量。
同样,这意味着存在一些范围泄漏。如果更改块内的 $_
并且 $_
是输入列表中的一项,则外部 $_
更改:
use v5.10;
$_ = 'Outside';
my @transformed = map { $_ = 'From map' } ( $_ );
say $_;
对于中等复杂的内联块,我将$_
分配给词法变量:
my @output = map { my $s = $_; ... } @input;
如果您真的对 $_
感到紧张,请不要在 map
中使用 map
的恶作剧:
my @words = map {
map { split } $_
} <>;
这是一个愚蠢的例子,但我过去曾做过这样的事情,我需要将主题变成一个列表。
同时( <> )
Perl 有一个方便的小习惯用法,它将文件句柄的下一行分配给 $_
。这意味着,而不是这个:
while( defined( $_ = <> ) )
你可以得到完全相同的东西:
while( <> )
但是,$_
中的任何值都会保留在 $_
中。
$_ = "Outside\n";
show_it();
sub show_it { print "$_ is $_" }
while( <DATA> ) {
show_it();
}
show_it();
__DATA__
first line
second line
third line
输出看起来有点奇怪,因为最后一行没有值,但那是分配给 $_
的最后一个值:行输入运算符在 defined
测试停止之前分配的 undef循环:
$_ is Outside
$_ is first line
$_ is second line
$_ is third line
$_ is
在那里放一个last
,输出会改变
$_ = "Outside\n";
show_it();
sub show_it { print "$_ is $_" }
while( <DATA> ) {
show_it();
last;
}
show_it();
__DATA__
first line
second line
third line
现在最后分配的值是第一行:
$_ is Outside
$_ is first line
$_ is first line
如果你不喜欢这个,请不要使用成语:
while( defined( my $line = <> ) )
模式匹配
替换运算符 s///
默认绑定到 $_
并且可以更改它(这就是重点)。但是,对于 v5.14,您可以使用 the /r
flag,它保留原始版本,returns 修改版本。
匹配运算符 m//
也可以更改 $_
。它不会更改值,但可以设置位置标志。这就是 Perl 如何在标量上下文中进行全局匹配:
use v5.10;
$_ = 'Outside';
show_it();
sub show_it { say "$_ is $_ with pos ", pos(); }
foreach my $time ( 1 .. 5 ) {
my $scalar = m/./g;
show_it();
}
show_it();
即使值相同,$_
中的某些标量设置也会发生变化:
$_ is Outside with pos
$_ is Outside with pos 1
$_ is Outside with pos 2
$_ is Outside with pos 3
$_ is Outside with pos 4
$_ is Outside with pos 5
$_ is Outside with pos 5
您可能不会对此有任何问题。您可以重置与 $_
匹配不成功的位置。也就是说,除非您使用 /c
标志。尽管标量值没有改变,但其簿记的一部分发生了变化。这是 problems with lexical $_
.
匹配还有一件奇怪的事情。每个匹配变量是动态范围的。他们不改变他们在外部范围内的值:
use v5.10;
my $string = 'The quick brown fox';
OUTER: {
$string =~ /\A(\w+)/;
say "$1 is ";
INNER: {
$string =~ /(\w{5})/;
say "$1 is ";
}
say "$1 is ";
}
OUTER
范围内 </code> 的值未被 <code>INNER
中的 </code> 替换:</p>
<pre><code> is The
is quick
is The
如果这让您头疼,请不要使用每场比赛的变量。立即分配它们(并且仅当您成功匹配时):
my $string = 'The quick brown fox';
OUTER: {
my( @captures ) = $string =~ /\A(\w)/;
INNER: {
my $second_word;
if( $string =~ /(\w{5})/ ) {
$second_word =
}
}
}
除了学习所有的内置函数(你至少应该为你使用的那些)之外,这里是我认为使用 $_
的最佳指南:
- Only use
$_
when it is clearer and more obvious than using explicit variables.
感谢 $_
作为 "it"。 "It" 是代词。当你想 "I went to the store and bought ice cream" 时,你不会说 "I went to it and bought it"。只有当代词所指的内容很明显时才使用代词。
稍微修改一下您的原始代码,这为我们提供了一个有用的示例。请注意在外部作用域中使用命名(显式)变量,并在可能的最小作用域中使用 $_
(将整个内部循环缩减为一行):
# Process student records one year at a time
for my $student_records_aref (@student_records_by_year_array_refs)
{
# Print a report on each student for the current year
print_report_on($_) for @{$student_records_aref};
}