Perl的“存在”可以修改数据结构值吗?

时间:2011-07-31 01:58:31

标签: perl

我有一个嵌套的哈希表,如下所示:

my %myhash = (
    "val1" => {
        "A/B.c" => {
            "funct1" => 1
        }
    },
    "val2" => {
        "C/D.c" => {
            "funct2" => 1
        }
    }
)

我对此数据结构的目标是根据是否存在某些哈希表来生成不同的值。例如,

sub mysub
{
    my $val = shift;
    my $file = shift;
    my $funct = shift;

    if (exists $myhash{$val}{$file}{$funct}) {
        return "return1";
    }
    if (exists $myhash{$val}{$file}) {
        return "return2";
    }
    return "return3";
}

我遇到的行为如下。我及时有一个实例     我的$ val =“val1”;     my $ file =“C / D.c”;     我的$ funct =“funct3”;

此时,返回值为“return2”。这些是我对Perl调试器的观察:

  1. 首先在mysub中打破“if”
  2. 打印p $ proxToBugs {“val1”} {“C / D.c”} ==>返回空行。好的。继续,跳过这个“if”。
  3. 继续并在mysub中的第二个“if”中断。
  4. 打印p $ proxToBugs {“val1”} {“C / D.c”} ==>返回“HASH(0x ...)”。 WTF时刻。函数返回“return2”。
  5. 这告诉我运行第一个如果修改了数据结构,它允许第二个if传递,实际上它不应该。我正在运行的功能与上面显示的功能相同;这个只是消毒了。有人对我有解释吗? :)

3 个答案:

答案 0 :(得分:21)

是。这是因为autovivification。请参阅exists文档的底部:

  
    

虽然大多数深度嵌套的数组或散列只是因为它的存在被测试而不会存在,但是任何介入的数组[autovivified数组或散列]将[弹出存在] 因此,$ ref-&gt; {“A”}和$ ref-&gt; {“A”} - &gt; {“B”}将因为上面的$ key元素的存在测试而存在。< / em>在使用箭头操作符的任何地方都会发生这种情况......

  

其中“......测试上面的$ key元素......”是指:

if (exists $ref->{A}->{B}->{$key})  { }
if (exists $hash{A}{B}{$key})       { } # same idea, implicit arrow

快乐的编码。

答案 1 :(得分:9)

正如pst正确指出的那样,这是自动生成。至少有两种方法可以避免它。第一个(在我的经验中最常见)是在每个级别进行测试:

if (
    exists $h{a}       and
    exists $h{a}{b}    and
    exists $h{a}{b}{c}
) {
    ...
}

如果较早的级别不存在,and的短路性质会导致对exists的第二次和第三次调用无法执行。

更新的解决方案是autovivification编译指示(可从CPAN获得):

#!/usr/bin/perl

use strict;
use warnings;

use Data::Dumper;

$Data::Dumper::Useqq = 1;

{
    my %h;

    if (exists $h{a}{b}{c}) {
        print "impossible, it is empty\n";
    }

    print Dumper \%h;
}

{
    no autovivification;

    my %h;

    if (exists $h{a}{b}{c}) {
        print "impossible, it is empty\n";
    }

    print Dumper \%h;
}

在评论中提及的第三种方法具有核心(如第一个示例)和不重复exists函数调用的好处;但是,我认为它是以牺牲可读性为代价的:

if (exists ${ ${ $h{a} || {} }{b} || {} }{c}) {
    ...
}

它通过用hashref替换不存在的任何级别来进行自动生成。 if语句执行完毕后,将丢弃这些hashref。我们再次看到了短路逻辑的价值。

当然,所有这三种方法都假设哈希值要保留的数据,更强大的方法包括调用refreftype,具体取决于您希望如何处理对象(有第三个选项考虑了重载哈希索引运算符的类,但我记不起它的名字了):

if (
    exists $h{a}           and
    ref $h{a} eq ref {}    and
    exists $h{a}           and
    ref $h{a}{b} eq ref {} and
    exists $h{a}{b}{c}
) {
    ...
}

在评论中,pst询问是否存在myExists($ref,"a","b","c")之类的内容。我确信CPAN中有一个模块可以做类似的事情,但我不知道。有太多边缘情况让我觉得有用,但一个简单的实现就是:

#!/usr/bin/perl

use strict;
use warnings;

use Data::Dumper;

sub safe_exists {
    my ($ref, @keys) = @_;

    for my $k (@keys) {
        return 0 unless ref $ref eq ref {} and exists $ref->{$k};
        $ref = $ref->{$k};
    }
    return 1;
}

my %h = (
    a => {
        b => {
            c => 5,
        },
    },
);

unless (safe_exists \%h, qw/x y z/) {
    print "x/y/z doesn't exist\n";
}

unless (safe_exists \%h, qw/a b c d/) {
    print "a/b/c/d doesn't exist\n";
}

if (safe_exists \%h, qw/a b c/) {
    print "a/b/c does exist\n";
}

print Dumper \%h;

答案 2 :(得分:8)

如果你想关闭自动复制,你可以使用autovivification编译指示词汇来做:

 {
 no autovivification;

 if( exists $hash{A}{B}{$key} ) { ... }
 }

我在The Effective PerlerTurn off autovivification when you don’t want it撰写了更多相关信息。