Может ли Perl «существовать» изменять значения структуры данных? - PullRequest
14 голосов
/ 31 июля 2011

У меня есть вложенная хеш-таблица, которая выглядит следующим образом:

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"; мой $ file = "C / D.c"; my $ funct = "funct3";

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

  1. Разбить сначала "если" в mysub
  2. Print p $ proxToBugs {"val1"} {"C / D.c"} ==> Возвращает пустую строку. Хорошо. Продолжайте, и это «если» пропускается.
  3. Продолжить и разбить на втором «если» в MySub
  4. Print p $ proxToBugs {"val1"} {"C / D.c"} ==> Возвращает "HASH (0x ...)". Момент WTF . Функция возвращает «return2».

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

Ответы [ 3 ]

21 голосов
/ 31 июля 2011

Да. Это из-за автовивификации . См. Нижнюю часть документации exists:

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

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

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

Удачного кодирования.

9 голосов
/ 31 июля 2011

Как правильно заметил 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}) {
    ...
}

Он работает путем замены любого уровня, который не существует, с помощью хэш-функции для выполнения автовивификации.Эти хэш-ссылки будут отброшены после выполнения оператора 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;
8 голосов
/ 31 июля 2011

Если вы хотите отключить автовивификацию, вы можете сделать это с помощью autovivification pragma:

 {
 no autovivification;

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

Я написал об этом подробнее на The Effective Perler as Отключите автовивификацию, когда она вам не нужна .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...