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