Может ли 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";
}

Поведение, с которым я сталкиваюсь, выглядит следующим образом. У меня есть пример, когда   my $val = "val1" ;   my $file = "C/D.c" ;   my $funct = "funct3";

В этот момент времени возвращаемое значение получает "return2". Это мои наблюдения с помощью отладчика Perl:

  • Сначала перерыв "if" в mysub
  • Печать p $proxToBugs { "val1" } { "C/D.c" } == > Возвращает пустую строку. Хорошо. Продолжайте, и это "если" пропущено.
  • Продолжить и разбить второй "if" в mysub
  • Печать p $proxToBugs { "val1" } { "C/D.c" } == > Возвращает "HASH (0x...)". WTF момент. Функция возвращает "return2".

Это говорит мне, что запускать первый, если изменить структуру данных, которая позволяет второму проходить, когда на самом деле это не должно. Функция, которую я запускаю, идентична функции, показанной выше; этот человек просто дезинфицирован. У кого-нибудь есть объяснение для меня?:)

Ответы

Ответ 1

Да. Это происходит из-за autovivification. См. Нижнюю часть документа exists:

Хотя в основном глубоко вложенный массив или хэш не будет spring существовать только потому, что его существование было протестировано, любые промежуточные (автогенерируемые массивы или хэши) будут [spring в existance]. Таким образом, $ref → { "A" } и $ref → { "A" } → { "B" } будет spring существовать из-за теста существования для элемента $key выше. Это происходит везде, где используется оператор стрелки...

Где "... тест для элемента $key выше..." относится к:

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

Счастливое кодирование.

Ответ 2

Как справедливо указывает 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;
}

Третий метод, который ysth упоминает в комментариях, имеет преимущества быть в ядре (например, в первом примере) и не повторять вызов функции exists; однако я считаю, что это делается за счет удобочитаемости:

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

Он работает, заменяя любой уровень, который не существует, с hashref, чтобы взять автовивитацию. Эти hashrefs будут отброшены после выполнения инструкции if. Снова мы видим значение короткозамкнутой логики.

Конечно, все три из этих методов делают предположение о данных, которые, как ожидается, будут содержать хеш, более надежный метод включает вызовы ref или reftype в зависимости от того, как вы хотите обрабатывать объекты (есть третий вариант, который учитывает классы, которые перегружают хеш-индексацию оператор, но я не могу запомнить его имя):

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;