在深度哈希中读取但不写入非现有密钥时,如何使Perl死掉?

时间:2010-09-18 08:03:08

标签: perl hash

我正在使用动态多级哈希,我从中读取数据但也会写入数据。

我常见的陷阱是访问不存在的密钥(拼写错误,数据库修订版等)。我得到undef s传播到其他部分并导致问题。每当我尝试读取不存在的密钥时,我想die,但仍然可以添加新密钥。

所以想要的行为是:

my %hash;
$hash{A} = 5;  # ok
print $hash{A}, "\n";  # ok
print $hash{X}, "\n";  # should die
$hash{B}{C}{D} = 10; # ok 
print $hash{B}{C}{X}, "\n";  # should die

我之前发布过similar question并得到了很好的答案。我特别喜欢接受的,它允许使用普通的哈希语法。唯一的问题是我不确定如何轻松地将其概括为深哈希,如上例所示。

P.S。 我发现这个功能非常有用,我想知道我是否遗漏了一些东西,因为它似乎不太受欢迎。也许从同一个哈希读/写是不常见的?

6 个答案:

答案 0 :(得分:3)

对我来说已经很晚了所以我会很简短,但是你可以使用tie功能做到这一点 - 让你的哈希由下面的对象表示,并实现与哈希交互所需的功能。 / p>

查看perldoc -f tie;还有many classes on CPAN要查看,包括Tie::Hash本身,这是你可以构建的绑定哈希的一个很好的基类,覆盖一些方法来添加你的错误检查。

答案 1 :(得分:3)

如果你想围绕一个哈希包装检查,创建一个子程序来做它并将它用作你的界面:

 use 5.010;
 use Carp qw(croak);

 sub read_from_hash {
     my( $hash, @keys ) = @_;

     return check_hash( $hash, @keys ) // croak ...;
     }

但是现在你开始看起来像一个班级。当您需要专门的行为时,开始编写面向对象的类。做你需要做的事。我认为那是你失踪的部分。

坚持哈希界面的问题是人们希望哈希语法充当普通哈希值。当你改变这种行为时,其他人将很难弄清楚发生了什么以及为什么。

答案 2 :(得分:2)

打开warnings pragma后,您会在要死的两行发出Use of uninitialized value in print at...警告。

因此,如果你让warnings致命,那么他们就会死:

use warnings FATAL => 'all';


更新

根据您所做的评论,我认为您的常见案例问题是:

my $x = $hash{B}{C}{X};

在您稍后使用$x之前,不会发出警告/错误。

要解决这个问题,你可以这样做:

my $x = $hash{B}{C}{X} // 'some default value';

my $z = $hash{B}{C}{Z} // die "Invalid hash value";

不幸的是,上述意味着很多额外打字:(

这至少是一个捷径:

use 5.012;
use warnings FATAL => 'all';
use Carp 'croak';

# Value Or Croak!
sub voc { $_[0] // croak "Invalid hash" }

然后下面会呱呱叫!

my $x = voc $hash{B}{C}{X};

希望这个以及致命的警告对你有帮助。

/ I3az /

答案 3 :(得分:2)

如果您不知道哈希可能具有哪些密钥,请使用其中一个绑定哈希建议或仅打开警告。请注意,绑定非常慢,比常规哈希慢9倍,比对象慢3倍。

如果你有一个固定集的可能键,你想要的是restricted hash。受限制的哈希只允许您访问一组给定的密钥,如果您尝试访问其他任何密钥,则会抛出错误。它也可以递归。这比搭售要快得多。

否则,我建议使用方法而不是直接散列访问将数据转换为对象。这比散列或受限散列慢,但比绑定散列更快。 CPAN上有许多模块可以从Class::Accessor开始为您生成方法。

如果您的数据没有修复,您可以编写简单的get()和set()方法,如下所示:

package Safe::Hash;

use strict;
use warnings;
use Carp;

sub new {
    my $class = shift;
    my $self = shift || {};
    return bless $self, $class;
}

sub get {
    my($self, $key) = @_;
    croak "$key has no value" unless exists $self->{$key};
    return $self->{$key};
}

sub set {
    my($self, $key, $value) = @_;
    $self->{$key} = $value;
    return;
}

您可以通过在对象中存储对象来获得递归行为。

my $inner = Safe::Hash->new({ foo => 42 });
my $outer = Safe::Hash->new({ bar => 23 });
$outer->set( inner => $inner );
print $outer->get("inner")->get("foo");

最后,由于您提到了数据库修订,如果您正在从数据库中读取数据,那么您将需要查看对象关系映射器(ORM)以为您生成类和对象以及SQL语句。 DBIx::ClassRose::DB::Object是两个很好的例子。

答案 4 :(得分:0)

关于以太tie答案,

重新:您的评论 - hints on how to get the recursive effect

我不适合胆小的人,但下面是单向的一个基本示例,您可以使用Tie::Hash执行您所追求的目标:

<强> HashX.pm

package HashX;
use 5.012;
use warnings FATAL => 'all';
use Carp 'croak';
use Tie::Hash;
use base 'Tie::StdHash';

sub import {
    no strict 'refs';
    *{caller . '::hash'} = sub {
        tie my %h, 'HashX', @_;
        \%h;
    }
}

sub TIEHASH {
    my $class = shift;
    croak "Please define a structure!" unless @_;
    bless { @_ }, $class;
}

sub STORE {
    my ($self, $key, $value) = @_;
    croak "Invalid hash key used to store a value" unless exists $self->{$key};
    $self->{$key} = $value;
}

sub FETCH {
    my ($self, $key) = @_;
    exists $self->{$key} 
        ?  $self->{$key} 
        :  croak "Invalid hash key used to fetch a value";
}

1;

上面的模块就像一个严格的哈希。您必须预先声明哈希结构,然后任何FETCH或STORE将croak,除非哈希键确实存在。

该模块有一个简单的hash函数,该函数被导入到调用程序中,用于为所有工作构建必要的tie

use 5.012;
use warnings;
use HashX;

# all my hashref are ties by using hash()
my $hash = hash(
    a => hash(
        b => hash(
            c => undef,
        ),
    ),
);

$hash->{a}{b}{c} = 1;      # ok
$hash->{a}{b}{c} = 2;      # also ok!
$hash->{a}{b}{d} = 3;      # throws error
my $x = $hash->{a}{b}{x};  # ditto

请记住这是一个快速的&amp;肮脏的例子,并且未经测试超出上述范围。我希望它会让你了解如何使用Tie::Hash完成它,甚至是否值得尝试:)

答案 5 :(得分:0)

使用Data::Diver中的DiveDie:

use Data::Diver qw(DiveDie);

my $href = { a => { g => 4}, b => 2 };

print DiveDie($href, qw(a g)), "\n"; # prints "4"
print DiveDie($href, qw(c)), "\n";   # dies