Perl的常见陷阱?
关于Perl的隐藏功能的问题至少产生了一个可以被视为功能或者错误功能的回答。继续以下问题似乎合乎逻辑:Perl中常见的非显而易见的错误是什么?看起来像它们应该起作用的事情,但是不应该起作用。
我不会给出有关如何构造答案的指南,也不会给出什么太容易被认为是陷阱的指导,因为这就是投票的目的。
答案表
句法
- 间接对象语法
- 引用与普通var类型混淆
- 打印到哈希中包含的词汇文件句柄
- 我的声明应在变量列表周围使用括号
- 用==和!=比较字符串
语义/语言功能
- 在正则表达式中使用/ o修饰符
- 忘记" readdir"的结果与CWD无关
- 一元减号与字符串的交互
- glob()迭代器(在另一个问题上)
- 列表上下文中的隐式返回
- 括号改变了运算符的语义
- 调用上下文传播到函数内的返回语句
- 使用具有相同名称的多个(不同类型的)变量
- 而<FH>不会自动将$ _本地化
- 有效为零的变量
- 常量可以重新定义
调试
- 警告:在串联中使用未初始化的值
最佳实践
- 忘记使用"严格"和"使用警告"(或者"使用诊断")
- 变量名拼写错误(即再次使用strict)
元答案
- Perltrap联机帮助页
- Perl :: Critic
另请参见:ASP.NET常见陷阱
解决方案
单引号可用于替换标识符中的::的事实。
考虑:
use strict; print "$foo"; #-- Won't compile under use strict print "$foo's fun!"; #-- Compiles just fine, refers to $foo::s
导致以下问题:
use strict; my $name = "John"; print "$name's name is '$name'"; # prints: # name is 'John'
避免这种情况的建议方法是在变量名周围使用花括号:
print "${name}'s name is '$name'"; # John's name is 'John'
还有使用警告,因为它会告诉我们未定义变量$ name :: s的用法。
最常见的陷阱是使用不同于以下内容的文件来启动文件
use strict; use diagnostics;
pjf补充:请注意,诊断会严重影响性能。由于需要加载perldiag.pod,它会减慢程序的启动速度,直到几周前直到bleadperl为止,由于使用$&,它也会减慢并膨胀正则表达式。建议使用警告并在结果上运行" splain"。
混淆的引用和实际对象:
$a = [1,2,3,4]; print $a[0];
(应该是" $ a-> [0]"(最佳)," $$ a [0]"," @ {$ a} [0]"或者" @ $ a [0]"之一))
给标量分配数组对我来说毫无意义。例如:
$foo = ( 'a', 'b', 'c' );
为$ foo分配'c'并丢弃其余数组。这很奇怪:
@foo = ( 'a', 'b', 'c' ); $foo = @foo;
看起来它应该与第一个示例执行相同的操作,但是将$ foo设置为@foo的长度,因此$ foo == 3.
除非导出整个typeglob,否则无法本地化导出的变量。
将/ o
修饰符与存储在变量中的正则表达式一起使用。
m/$pattern/o
指定/ o
是$ pattern
不会改变的承诺。 Perl非常聪明,可以识别它是否已更改并有条件地重新编译正则表达式,因此,没有充分的理由再使用/ o
了。或者,我们可以使用qr //
(例如,如果我们沉迷于避免检查)。
my $x = <>; do { next if $x !~ /TODO\s*[:-]/; ... } while ( $x );
" do"不是一个循环。我们不能"下一步"。这是执行块的指令。和...一样
$inc++ while <>;
尽管如此,它看起来像是C语言族的构造。
我们可以打印到词汇文件句柄:很好。
print $out "hello, world\n";
然后,我们意识到拥有文件句柄的哈希值可能会很好:
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: $!";
到目前为止,一切都很好。现在尝试使用它们,并根据条件打印到一个或者另一个:
my $where = (frobnitz() == 10) ? 'ok' : 'fail'; print $out{$where} "it worked!\n"; # it didn't: compile time error
我们必须将散列取消引用包装在一对curlies中:
print {$out{$where}} "it worked!\n"; # now it did
这完全是非直觉的行为。如果我们没有听说过,或者在文档中没有读过,我怀疑我们可以自己解决这个问题。
如果我们足够幸运地在不过敏升级的地方工作,此问题已在Perl 5.10中修复:>-(
我说的是有效为零的变量。我们知道,导致子句出现意外结果的原因如下:
unless ($x) { ... } $x ||= do { ... };
Perl 5.10具有// =或者define-or运算符。
当有效零是由于某些边缘条件导致的,这是特别隐蔽的,而这些边缘条件是在代码投入生产之前在测试中没有考虑的...
当对带有词法文件句柄的print
进行使用时,Perl的DWIMmer很难与<<
(here-document)表示法相抗衡:
# 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
解决方案是小心地在文件句柄和<<
之间包含空格,或者通过将文件句柄包装在`{}'花括号中来消除歧义:
print {$fh}<<EOT; foo EOT
如果我们这样做很愚蠢,Perl将允许我们声明多个具有相同名称的变量:
my ($x, @x, %x);
因为Perl使用sigils来标识上下文而不是变量类型,所以这几乎可以保证以后的代码使用变量时会造成混淆,尤其是在$ x
是引用的情况下:
$x[0] $x{key} $x->[0] $x->{key} @x[0,1] @x{'foo', 'bar'} @$x[0,1] @$x{'foo', 'bar'} ...
我曾经做过一次:
my $object = new Some::Random::Class->new;
花了我很长时间才发现错误。间接方法语法是eeevil。
Perl的大多数循环运算符(" foreach"," map"," grep")都会自动本地化" $ _",而" while(<FH>)"则不会。这可能会导致奇怪的远距离动作。
perltrap联机帮助页列出了许多按类型组织的粗心陷阱。
常量可以重新定义。意外重新定义常数的一种简单方法是将常数定义为参考。
use constant FOO => { bar => 1 }; ... my $hash = FOO; ... $hash->{bar} = 2;
现在FOO是{bar => 2};
如果我们使用的是mod_perl(至少在1.3中使用),则新的FOO值将保持不变,直到刷新模块为止。
变量名拼写错误...我曾经花了整个下午的时间进行故障排除代码,该代码的行为不正确,只是为了找到变量名上的错字,这不是Perl中的错误,而是声明新变量。
这是一个元答案。 Perl :: Critic捕获了许多讨厌的陷阱,我们可以使用" perlcritic"命令从命令行安装并运行它们,或者(如果我们希望通过Internet发送代码,并且不能通过Perl :: Critic网站自定义选项)。
" Perl :: Critic"还提供了对Damian Conways Perl最佳实践书的引用,包括页码。因此,如果我们懒于阅读整本书,Perl :: Critic
仍然可以告诉我们应该阅读的内容。
在以下情况下,我们希望@_包含哪些值?
sub foo { } # empty subroutine called in parameters bar( foo(), "The second parameter." ) ;
我希望在酒吧收到:
undef, "The second parameter."
但是@_仅包含第二个参数,至少在使用perl 5.88进行测试时。
" my"声明应在变量列表周围使用括号
use strict; my $a = 1; mysub(); print "a is $a\n"; sub { my $b, $a; # Gotcha! $a = 2; }
它打印a为2,因为my
声明仅适用于$ b
(在该行上提及$ a根本没有做任何事情)。请注意,即使"严格使用"有效,这种情况也会在没有警告的情况下发生。
添加"使用警告"(或者-w标志)后,Perl会在"我的"列表周围缺少括号的情况下大大改善了情况。正如许多人已经看到的那样,这表明严格的和警告性的编译指示总是一个好主意。
在串联中使用未初始化的值...
这使我发疯。打印中包含许多变量,例如:
print "$label: $field1, $field2, $field3\n";
变量之一是undef。我们认为这是程序中的错误-这就是为什么我们使用"严格"编译指示的原因。也许数据库模式允许在我们不期望的字段中使用NULL,或者我们忘记了初始化变量等。但是所有错误消息都告诉我们,在连接(.
)操作期间遇到了未初始化的值。如果仅告诉我们未初始化的变量的名称!
由于Perl出于某种原因不想在错误消息中打印变量名,因此最终通过设置断点(以查看哪个变量是undef)或者添加代码来检查条件来对其进行跟踪。当它在CGI脚本中只发生千分之一而我们又不能轻易地重新创建它时,这非常令人讨厌。
添加额外的括号永远不会改变代码的含义,对吗?正确的?
my @x = ( "A" x 5 ); # @x contains 1 element, "AAAAA" my @y = (("A") x 5 ); # @y contains 5 elements: "A", "A", "A", "A", "A"
哦,是的,这是Perl。
编辑:只是为了很好的衡量,如果在标量上下文中调用x,则括号毕竟不重要:
my $z = ( "A" x 5 ); # $z contains "AAAAA" my $w = (("A") x 5 ); # $w contains "AAAAA" too
直觉的。
Graeme Perrow的回答很好,但是甚至更好!
给定一个典型的在列表上下文中返回漂亮列表的函数,我们可能会问:在标量上下文中它将返回什么? ("典型"是指文档中没有说明的常见情况,我们假设它不使用任何" wantarray"有趣的事情。也许这是我们自己编写的函数。)
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,最好让调用者的角色每次都在被调用函数的源代码中进行遍历。
使用==
和!=
而不是eq
和ne
比较字符串。例如:
$x = "abc"; if ($x == "abc") { # do something }
代替:
$x = "abc"; if ($x eq "abc") { # do something }
在对结果进行测试之前,忘记将目录路径添加到readdir
结果的前面。这是一个例子:
#!/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\n" 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
的文档中提到了这个陷阱,但是我认为这仍然是一个很常见的错误。
一元减号与"" foo"会创建
" -foo"`:
perl -le 'print -"foo" eq "-foo" ? "true" : "false"'
仅当第一个字符与/ [__ a-zA-Z] /
相匹配时才有效。如果第一个字符是"-",则将第一个字符更改为" +",如果第一个字符是" +",则将第一个字符更改为"-"。 。如果第一个字符与/ [^-+ _ a-zA-Z] /
相匹配,则它将尝试将字符串转换为数字并取反。
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 '
上面的代码打印
-foo +foo -foo -0 -5
此功能主要是为了使人们能够说出类似
my %options = ( -depth => 5, -width => 2, -height => 3, );
修改要在for(每个)中循环的数组,如下所示:
my @array = qw/a b c d e f g h/; for ( @array ) { my $val = shift @array; print $val, "\n"; }
它会感到困惑,并且没有达到期望