Perl的常见问题?
关于Perl隐藏特性的问题至less得到了一个可能被视为一个特征或一个错误特征的回应 。 跟进这个问题似乎是合乎逻辑的:Perl中常见的非显而易见的错误是什么? 似乎他们应该工作的东西,但不要。
我不会给出如何构build答案的指导方针,也不会给出指导方针,因为这就是投票的目的。
答案表
句法
- 一般
- 单引号代替
::
标识符 - 间接对象语法
- 用普通的vartypes混淆引用
- 单引号代替
- 文件句柄
- 使用带有词法文件句柄的打印时的Heredoc符号
- 打印到包含在散列中的词法文件句柄
-
my
声明应该在variables列表中使用parens - 用==和!=比较string
语义/语言特征
- 一般
-
do
不是一个循环。 你不能next
。 - 在正则expression式中使用/ o修饰符
- 忘记
readdir
的结果与CWD无关 - 一元减号与string的交互
-
- 上下文
- 赋值为数组与标量的标量
- glob()迭代器 (在另一个问题上)
- 列表上下文中的隐式返回
- 括号改变了操作符的语义
- 调用上下文传播到函数内的返回语句
- variables
- 不能在不导出整个typeglob的情况下本地化导出的variables
- 使用具有相同名称的多个variables(不同types)
-
while <FH>
不自动本地化$_
- variables是有效的零
- 常量可以重新定义
debugging
- 警告:在串联中使用未初始化的值
最佳实践
- 忘记
use strict
use warnings
(或use diagnostics
) - 拼写错误的variables名 (即再次
use strict
)
元回答
- perltrap manpage
- Perl的::评论家
另请参见: ASP.NET – 常见陷阱
单引号可以用来replace::中的标识符。
考虑:
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'
避免这种情况的推荐方法是在variables名称周围使用大括号。
print "${name}'s name is '$name'"; # John's name is 'John'
同时也use warnings
,因为它会告诉你使用未定义的variables$name::s
你可以打印到一个词法文件句柄:好的。
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
你必须把哈希解引用包装在一对:
print {$out{$where}} "it worked!\n"; # now it did
这是完全不直观的行为。 如果你没有听说这个,或者在文档中看到,我怀疑你可以自己弄清楚。
这是一个元回答。 Perl :: Critic捕获了很多令人讨厌的陷阱,您可以使用perlcritic
命令从命令行安装并运行perlcritic
,或者(如果您很高兴通过Internet发送代码,并且无法自定义您的代码选项)通过Perl :: Critic网站 。
Perl::Critic
还提供了Damian Conways Perl最佳实践书的参考资料,包括页码。 所以,如果你懒得看完整本书, Perl::Critic
仍然可以告诉你应该阅读的部分。
当使用带有词法文件句柄的print
,Perl的DWIMMO与<<
(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
解决的办法是小心地在文件句柄和<<
之间join空格,或者通过将文件句柄包装在{}
括号中来消除歧义:
print {$fh}<<EOT; foo EOT
perltrap手册页列出了按照types组织的粗心大意的许多陷阱。
将数组分配给标量对我来说没有意义。 例如:
$foo = ( 'a', 'b', 'c' );
将“c”赋值给$ foo,并抛出数组的其余部分。 这一个是更伟大的:
@foo = ( 'a', 'b', 'c' ); $foo = @foo;
这看起来应该和第一个例子一样,但是将$foo
设置$foo
@foo
的长度 ,所以$foo == 3
。
最常见的问题是用任何不同于。的文件来启动你的文件
use strict; use diagnostics;
pjf补充说:请注意,诊断对性能有重大影响。 它会减慢程序的启动速度,因为它需要加载perldiag.pod,直到几周前的bleadperl,它也会减慢和膨胀正则expression式,因为它使用$&。 build议在结果上使用警告并运行splain
。
混淆引用和实际对象:
$a = [1,2,3,4]; print $a[0];
(它应该是$a->[0]
(最好), $$a[0]
, @{$a}[0]
或@$a[0]
)
“我的”声明应该围绕variables列表使用括号
use strict; my $a = 1; mysub(); print "a is $a\n"; sub { my $b, $a; # Gotcha! $a = 2; }
它打印一个是2,因为my
声明只适用于$b
(该行提到$a
根本没有做任何事情)。 请注意,即使“严格使用”有效,这种情况也不会发生警告。
Perl中使用“使用警告”(或-w标志)可以大大改善事情,并在“我的”列表中缺less括号 。 这表明,正如已经有很多人一样,为什么严格和警告的编译指示总是一个好主意。
在串联中使用未初始化的值…
这个让我疯狂。 您有一个打印,其中包含一些variables,如:
print "$label: $field1, $field2, $field3\n";
其中一个variables是undef 。 你认为这是你的程序中的一个错误 – 这就是为什么你使用“严格”的编译指示。 也许你的数据库模式在你没有想到的字段中允许NULL,或者你忘记初始化一个variables等等。但是所有的错误信息都告诉你在串联( .
)操作中遇到了一个未初始化的值。 如果只是告诉你未初始化的variables的名字 !
由于Perl不想在错误消息中出于某种原因而输出variables名,所以最终通过设置断点(查看哪个variables是undef )或者添加代码来检查条件来追踪它。 当CGI脚本只发生一次数千次的非常烦人的时候,你不能轻易地重新创build它。
Perl的循环运算符( foreach
, map
, grep
)大部分自动本地化$_
但while(<FH>)
不会。 这可能会导致奇怪的行动。
我曾经这样做过:
my $object = new Some::Random::Class->new;
花了我很多时间来find错误。 间接方法的语法是eeevil 。
my $x = <>; do { next if $x !~ /TODO\s*[:-]/; ... } while ( $x );
do
不是一个循环。 你不能next
。 这是一个执行块的指令。 这是一样的事情
$inc++ while <>;
尽pipe它看起来像C语言系列中的一个构造。
常量可以重新定义。 意外重新定义常量的一个简单方法是定义一个常量作为参考。
use constant FOO => { bar => 1 }; ... my $hash = FOO; ... $hash->{bar} = 2;
现在FOO是{bar => 2};
如果你正在使用mod_perl(至less在1.3),新的FOO值将一直持续到模块被刷新。
在以下情况下,您期望@_包含哪些值?
sub foo { } # empty subroutine called in parameters bar( foo(), "The second parameter." ) ;
我希望收到的酒吧 :
undef, "The second parameter."
但@_仅包含第二个参数,至less在使用perl 5.88进行testing时。
一元减号"foo"
创build"-foo"
:
perl -le 'print -"foo" eq "-foo" ? "true" : "false"'
这只有在第一个字符匹配/[_a-zA-Z]/
时才有效。 如果第一个字符是"-"
那么它将第一个字符更改为"+"
,如果第一个字符是"+"
,则将第一个字符更改为"-"
。 如果第一个字符与/[^-+_a-zA-Z]/
匹配,那么它会尝试将该string转换为数字并取消结果。
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
这个function主要是为了让人们说出类似的东西
my %options = ( -depth => 5, -width => 2, -height => 3, );
这个问题在Perl 5.10中得到了解决 – 如果你足够幸运的话可以在某些不会对升级过敏的地方工作::-(
我说的是“有效零的variables”。 你知道,导致意想不到的结果的条款,如:
unless ($x) { ... } $x ||= do { ... };
Perl 5.10具有// =或定义的或运算符。
当有效的零是由在你的代码生产之前在testing中没有考虑到的边缘条件引起的时,这是特别危险的。
格雷姆·佩罗的回答很好,但它变得更好!
给定一个在列表上下文中返回一个好列表的典型函数,你可能会问:在标量上下文中它将返回什么? (“典型的”,我的意思是文档没有说的常见情况,我们假设它不会使用任何有趣的业务,也许这是你自己写的一个函数。)
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,对于调用者的angular色来说, 每次都要调用被调用函数的源代码 。
除非导出整个typeglob,否则不能本地化导出的variables。
使用存储在variables中的正则expression式模式的/o
修饰符。
m/$pattern/o
指定/o
是$pattern
不会改变的承诺。 Perl足够聪明,可以识别是否有条件地更改和重新编译正则expression式,因此没有理由再使用/o
。 或者,你可以使用qr//
(例如,如果你痴迷于避免检查)。
使用==
和!=
而不是eq
和ne
来比较string。 例如:
$x = "abc"; if ($x == "abc") { # do something }
代替:
$x = "abc"; if ($x eq "abc") { # do something }
那么这个事实呢?
@array = split( / /, $string );
并没有给出与之相同的结果
@array = split( ' ', $string );
如果$ string有前导空格?
这可能会让一些人吃惊。
添加额外的括号永远不能改变代码的含义 ,对吧? 对?
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
直观。
如果你愚蠢的话,Perl会允许你用相同的名字声明多个variables:
my ($x, @x, %x);
因为Perl使用标识来标识上下文而不是variablestypes ,所以当后面的代码使用variables时,这几乎可以保证混淆,特别是如果$x
是引用:
$x[0] $x{key} $x->[0] $x->{key} @x[0,1] @x{'foo', 'bar'} @$x[0,1] @$x{'foo', 'bar'} ...
在对这些结果进行testing之前,忘记在readdir
的结果前加上目录path。 这是一个例子:
#!/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
的文档中提到了这个问题,但我认为这仍然是一个相当常见的错误。
哈希“构造函数”只不过是一个列表,而胖逗号只不过是语法糖。 当[]
arrayref语法与()
列表语法混淆时,您可能会被此叮咬:
my %var = ( ("bar", "baz"), fred => "barney", foo => (42, 95, 22) ); # result { 'bar' => 'baz', '95' => 22, 'foo' => 42, 'fred' => 'barney' }; # wanted { 'foo' => [ 42, 95, 22 ] }
拼写错误的variables名称 …我曾经花了整整一个下午的时间排除故障代码,这些代码的行为不正确,只能在variables名称上find一个错字,这在Perl中不是一个错误,而是一个新variables的声明。
修改您正在循环的数组(for each()),如下所示:
my @array = qw/abcdefgh/; for ( @array ) { my $val = shift @array; print $val, "\n"; }
它变得困惑,并没有做你所期望的
将一个标量视为一个整数:
$n = 1729; $s = 0; $i = 0; while ($n) { $s += $n % 10; $n/=10; $i ++ } print "Sum : $s\n"; print "Number of iterations : $i\n"
总和:19
迭代次数:327
理想情况下,它应该只有四次迭代,但标量不是一个整数,我们得到了意想不到的结果。