Perl的常见问题是什么?

Common gotchas for Perl?

关于Perl隐藏特性的问题产生了至少一个可以被视为特性或MIS特性的响应。跟进这个问题似乎合乎逻辑:Perl中常见的非明显错误是什么?看起来应该起作用的东西,但不应该起作用。

我不会给出关于如何构建答案的指导方针,也不会给出"太容易"而被认为是正确答案的指导方针,因为这就是投票的目的。

答案表

句法

  • 一般
    • 标识符中的单引号而不是::
    • 间接对象语法
    • 将引用与普通var类型混淆
  • 文件句柄
    • 使用带词汇文件句柄的print时使用HereDoc符号
    • 打印到哈希中包含的词汇文件句柄
    • my声明应在变量列表周围使用parens
    • 将字符串与==和!=

语义/语言特征

  • 一般
    • do不是循环。你不能这样做。
    • 在regex中使用/o修饰符
    • 忘了readdir的结果与CWD无关
    • 一元减号与字符串的相互作用
  • 语境
    • 从数组到列表的标量赋值
    • glob()迭代器(在另一个问题上)
    • 列表上下文中的隐式返回
    • 括号改变了运算符的语义
    • 调用上下文被传播到函数中的返回语句
  • 变量
    • 如果不导出整个typeglob,则无法本地化导出的变量
    • 使用具有相同名称的多个变量(不同类型)
    • while 不自动本地化$_
    • 有效零的变量
    • 常量可以重新定义

调试

  • 警告:在串联中使用未初始化的值

最佳实践

  • 忘记了use strictuse warnings(或use diagnostics)
  • 变量名拼写错误(即,use strict再次出现)

元解答

  • PerlTrap手册页
  • Perl:评论家

另请参见:asp.net-通用gotchas


可以使用单引号替换标识符中的::。

考虑:

1
2
3
use strict;
print"$foo";        #-- Won't compile under use strict
print"$foo's fun!"; #-- Compiles just fine, refers to $foo::s

导致以下问题:

1
2
3
4
5
use strict;
my $name ="John";
print"$name's name is '$name'";
# prints:
#  name is 'John'

建议避免这种情况的方法是在变量名周围使用大括号:

1
2
print"${name}'s name is '$name'";
# John's name is 'John'

还有use warnings,因为它会告诉你未定义变量$name::s的用法。


您可以打印到词汇文件句柄:好。

1
2
print $out"hello, world
"
;

然后您会意识到拥有一个文件句柄的散列值可能会很好:

1
2
3
my %out;
open $out{ok},   '>', 'ok.txt' or die"Could not open ok.txt for output: $!";
open $out{fail}, '>', 'fail.txt' or die"Could not open fail.txt for output: $!";

到目前为止,一切都很好。现在尝试使用它们,并根据条件打印到其中一个或另一个:

1
2
3
4
my $where = (frobnitz() == 10) ? 'ok' : 'fail';

print $out{$where}"it worked!
"
; # it didn't: compile time error

您必须将散列引用包装成一对卷发:

1
2
print {$out{$where}}"it worked!
"
; # now it did

这完全是不直观的行为。如果你没有听说这件事,或者在文档中读到这件事,我怀疑你能否自己解决。


这是一个元答案。很多讨厌的gotcha都被perl::critical捕获,您可以使用perlcritic命令从命令行安装和运行,或者(如果您愿意在Internet上发送代码,但无法自定义您的选项)通过perl::critical网站。

Perl::Critic还提供了Damian Conways Perl最佳实践手册的参考资料,包括页码。所以,如果你太懒了,不愿意读整本书,那么Perl::Critic仍然可以告诉你应该读的内容。


Perl的dwimmer在将print与词汇文件句柄一起使用时,难以使用EDOCX1(本文中的文档)符号:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# here-doc
print $fh <<EOT;
foo
EOT


# here-doc, no interpolation
print $fh <<'EOT';
foo
EOT


# bitshift, syntax error
# Bareword"EOT" not allowed while"strict subs" in use
print $fh<<EOT;
foo
EOT


# bitshift, fatal error
# Argument"EOT" isn't numeric...
# Can't locate object method"foo" via package"EOT"...
print $fh<<'EOT';
foo
EOT

解决方案是小心地在filehandle和<<之间包含空白,或者用{}大括号将filehandle包装以消除其歧义:

1
2
3
print {$fh}<<EOT;
foo
EOT


PerlTrap手册页列出了许多按类型组织的不小心的陷阱。


最常见的方法是用不同于

1
2
use strict;
use diagnostics;

PJF补充道:请注意,诊断对性能有重大影响。它会减慢程序的启动速度,因为它需要加载perldiag.pod,而且直到几个星期前的bleadperl,它也会减慢并膨胀regexps,因为它使用了$&;。建议使用警告并对结果运行splain


混淆引用和实际对象:

1
2
$a = [1,2,3,4];
print $a[0];

(最好是$a->[0]$$a[0]@{$a}[0]@$a[0]中的一个)


给scalars分配数组对我来说毫无意义。例如:

1
$foo = ( 'a', 'b', 'c' );

将"c"分配给$foo并丢弃数组的其余部分。这个更奇怪:

1
2
@foo = ( 'a', 'b', 'c' );
$foo = @foo;

这看起来应该和第一个例子做同样的事情,但是它将$foo设置为@foo的长度,所以$foo == 3


在串联中使用未初始化的值…

这个让我发疯。您有一个包含许多变量的打印,例如:

1
2
print"$label: $field1, $field2, $field3
"
;

其中一个变量是UNdef。您认为这是程序中的一个bug——这就是为什么您使用"strict"pragma的原因。也许您的数据库模式在您不期望的字段中允许空值,或者您忘记初始化变量等,但是所有的错误消息都告诉您,在串联(.操作)期间遇到了未初始化的值。如果它告诉您未初始化的变量的名称就好了!

由于某种原因,Perl不想在错误消息中打印变量名,所以您最终会通过设置断点(查看哪个变量是未定义的)或添加代码来跟踪变量名,以检查条件。当它在CGI脚本中只发生了千分之一的时候非常恼人,而且您不能很容易地重新创建它。


"我的"声明应在变量列表周围使用括号

1
2
3
4
5
6
7
8
9
10
use strict;
my $a = 1;
mysub();
print"a is $a
"
;

sub {
    my $b, $a;   # Gotcha!
    $a = 2;
}

因为my声明只适用于$b(该行提到$a根本没有做任何事情)。请注意,即使在"使用严格"有效的情况下,也不会发出警告。

添加"use warnings"(或-w标志)可以极大地改进Perl所说的"my"列表周围缺少括号的情况。这表明,正如许多人已经看到的,为什么严格和警告语用总是一个好主意。


Perl的大多数循环操作符(foreachmapgrep自动本地化$_,但while()不自动本地化,这会导致奇怪的远距离操作。


我做过一次:

1
my $object = new Some::Random::Class->new;

我花了很长时间才发现错误。间接方法语法是eEvil。


1
2
3
4
5
my $x = <>;
do {
    next if $x !~ /TODO\s*[:-]/;
    ...
} while ( $x );

do不是循环。你不能这样做。这是执行一个块的指令。这和

1
$inc++ while <>;

尽管如此,它看起来像是C语言家族的一个结构。


常量可以重新定义。意外地重新定义常量的一个简单方法是将常量定义为引用。

1
2
3
4
5
 use constant FOO => { bar => 1 };
 ...
 my $hash = FOO;
 ...
 $hash->{bar} = 2;

现在foo是bar=>2

如果您使用的是mod_perl(至少在1.3中),那么新的foo值将一直保持到刷新模块为止。


这个gotcha在Perl5.10中是固定的-如果你足够幸运地在一个对升级不过敏的地方工作>:-(

我说的是有效零的变量。你知道,在下列句子中会导致意想不到的结果:

1
2
unless ($x) { ... }
$x ||= do { ... };

Perl5.10具有//=或定义的或运算符。

当有效的零是由一些在代码投入生产之前测试中没有考虑到的边缘条件引起时,这是特别阴险的。


一元减去"foo"产生"-foo"

1
perl -le 'print -"foo" eq"-foo" ?"true" :"false"'

这仅在第一个字符与/[_a-zA-Z]/匹配时才有效。如果第一个字符是"-",则将第一个字符更改为"+",如果第一个字符是"+",则将第一个字符更改为"-"。如果第一个字符与/[^-+_a-zA-Z]/匹配,那么它将尝试将字符串转换为数字并否定结果。

1
2
3
4
5
6
7
perl -le '
    print -"foo";
    print -"-foo";
    print -"+foo";
    print -"\x{e9}"; #e acute is not in the accepted range
    print -"5foo";   #same thing for 5
'

上面的代码打印

1
2
3
4
5
-foo
+foo
-foo
-0
-5

这个功能主要是允许人们说

1
2
3
4
5
my %options = (
    -depth  => 5,
    -width  => 2,
    -height => 3,
);


在下面的场景中,您希望@包含哪些值?

1
2
3
4
5
sub foo { }

# empty subroutine called in parameters

bar( foo(),"The second parameter." ) ;

我希望在酒吧里收到:

1
undef,"The second parameter."

但是@仅包含第二个参数,至少在使用Perl5.88进行测试时是这样。


格雷姆·佩罗的回答很好,但它会变得更好!

给定一个在列表上下文中返回好列表的典型函数,您可能会问:在标量上下文中它将返回什么?(我所说的"典型",是指文档没有说明的常见情况,我们假设它没有使用任何wantarray有趣的业务。也许这是你自己写的函数。)

1
2
3
4
5
sub f { return ('a', 'b', 'c'); }
sub g { my @x = ('a', 'b', 'c'); return @x; }

my $x = f();           # $x is now 'c'
my $y = g();           # $y is now 3

调用函数的上下文被传播到该函数中的return语句。

我猜调用方想要一个简单的经验法则来对代码行为进行有效的推理是错误的。你说得对,Perl,每次调用函数的源代码时,调用方的角色最好仔细检查一遍。


比较使用==!=而不是eqne的字符串。例如:

1
2
3
4
$x ="abc";
if ($x =="abc") {
    # do something
}

而不是:

1
2
3
4
$x ="abc";
if ($x eq"abc") {
    # do something
}


使用带有存储在变量中的regex模式的/o修饰符。

1
m/$pattern/o

指定/o$pattern不变的承诺。Perl足够聪明,可以识别它是否发生了变化,并有条件地重新编译regex,因此没有足够的理由再使用/o。或者,您可以使用qr//(例如,如果您一心想避免支票)。


那事实呢

1
@array = split( / /, $string );

结果与

1
@array = split( ' ', $string );

如果$string有前导空格?

这可能会让一些人感到惊讶。


除非导出整个typeglob,否则无法本地化导出的变量。


添加额外的括号永远不会改变代码的含义,对吗?对吗?

1
2
my @x = ("A"  x 5 );      # @x contains 1 element,"AAAAA"
my @y = (("A") x 5 );      # @y contains 5 elements:"A","A","A","A","A"

哦,对了,这是波尔。

编辑:为了更好的衡量,如果在标量上下文中调用x,那么括号就不重要了:

1
2
my $z = ("A"  x 5 );      # $z contains"AAAAA"
my $w = (("A") x 5 );      # $w contains"AAAAA" too

直观。


如果您愚蠢到这样做,Perl将允许您声明具有相同名称的多个变量:

1
my ($x, @x, %x);

因为Perl使用sigils来标识上下文而不是变量类型,所以当以后的代码使用变量时,这几乎保证了混淆,特别是当$x是一个引用时:

1
2
3
4
5
6
7
8
9
$x[0]
$x{key}
$x->[0]
$x->{key}
@x[0,1]
@x{'foo', 'bar'}
@$x[0,1]
@$x{'foo', 'bar'}
...


在对这些结果进行测试之前,忘记预先准备指向readdir结果的目录路径。下面是一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/usr/bin/env perl
use strict;
use warnings;

opendir my $dh, '/path/to/directory/of/interest'
  or die"Can't open '/path/to/directory/of/interest for reading: [$!]";

my @files = readdir $dh; # Bad in many cases; see below
# my @files = map {"/path/to/directory/of/interest/$_" } readdir $dh;

closedir $dh or die"Can't close /path/to/directory/of/interest: [$!]";

for my $item (@files) {
  print"File: $item
"
if -f $item;
  # Nothing happens. No files? That's odd...
}

# Scratching head...let's see...
use Data::Dumper;
print Dumper @files;
# Whoops, there it is...

readdir的文档中提到了这个gotcha,但我认为这仍然是一个非常常见的错误。


散列"constructor"只不过是一个列表,而=>脂肪逗号只不过是句法上的糖而已。当将[]arrayref语法与()list语法混淆时,您可能会被咬到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
my %var = (
    ("bar","baz"),
    fred =>"barney",
    foo => (42, 95, 22)
);

# result
{ 'bar' => 'baz',
  '95' => 22,
  'foo' => 42,
  'fred' => 'barney' };

# wanted
{ 'foo' => [ 42, 95, 22 ] }


变量名拼写错误…有一次,我花了整整一个下午的时间对行为不正确的代码进行故障排除,结果发现变量名的拼写错误,这在Perl中不是错误,而是新变量的声明。


修改for(each)中循环使用的数组,如下所示:

1
2
3
4
5
6
7
my @array = qw/a b c d e f g h/;

for ( @array ) {
    my $val = shift @array;
    print $val,"
"
;
}

它会变得混乱,不会像你期望的那样做。


将标量视为整数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$n = 1729;
$s = 0;
$i = 0;

while ($n) {
    $s += $n % 10;
    $n/=10;
    $i ++
}

print"Sum : $s
"
;
print"Number of iterations : $i
"

金额:19

迭代次数:327次

理想情况下,它应该只有四次迭代,但是标量不是int,我们得到了一个意外的结果。