尝试从受限制的哈希中删除只读密钥,当它不受限制时

时间:2014-04-17 04:40:14

标签: perl hash

我经常安排我的子程序条目:

sub mySub {
    my ($self, %opts) = @_;
    lock_keys(%opts, qw(count, name));
    ...
    my $name = delete $opts{name};
    $self->SUPER::mySub(%opts);
}

允许使用如下命名参数调用sub:

$obj->mySub(count=>1, name=>'foobar');

lock_keys防止使用拼写错误的参数名称调用sub。

最后几行是我使用的另一个常用习惯用法,如果我正在编写一个覆盖超类的方法,我可能会提取特定于子类的参数,然后将调用链接到子类。

这在perl 5.8中运行良好,但在升级到Centos 6(具有perl 5.10.1)后,我开始看到看似随机的错误:

Attempt to delete readonly key 'otherOption' from a restricted hash at xxx.pl line 9.

这些错误并不是一直发生的(即使在相同的子程序中),但它们似乎与调用链的调用链有关,这会导致调用轰炸的子。

另请注意,它们不会发生在perl 5.16(or at least not on ideone)上。

在perl 5.10中导致这些错误的原因是什么?根据{{​​3}},delete()仍应在lock_keys之后工作。就像整个哈希以某种方式被锁定一样。

1 个答案:

答案 0 :(得分:1)

我在发布SO之前就找到了答案,但是解决方法并不是很好,所以请随意加入更好的解决方案。

此SSCCE表现出问题:

#!/usr/bin/perl
use strict;
use Hash::Util qw(lock_keys);

sub doSomething {
        my ($a, $b, %opts) = @_;
        lock_keys(%opts, qw(myOption, otherOption));

        my $x = delete $opts{otherOption};

}

my %h = (
        a=>1,
        b=>2
);

foreach my $k (keys %h) {
        doSomething(1, 2, otherOption=>$k);
}

似乎问题与作为值传递给命名参数hash的值有关(在我的示例中为%opt)。如果从哈希的键中复制这些值,如上例所示,它会将值标记为只读,以便以后阻止从哈希中删除键。

实际上你可以使用Devel :: Peek

看到这个
$ perl -e'
   use Devel::Peek;
   my %x=(a=>1);
   foreach my $x (keys %x) {
      my %y = (x => $x);
      Dump($x);
      Dump(\%y);
   }
'
SV = PV(0x22cfb78) at 0x22d1fd0
  REFCNT = 2
  FLAGS = (POK,FAKE,READONLY,pPOK)
  PV = 0x22f8450 "a"
  CUR = 1
  LEN = 0
SV = RV(0x22eeb30) at 0x22eeb20
  REFCNT = 1
  FLAGS = (TEMP,ROK)
  RV = 0x22f8880
  SV = PVHV(0x22d7fb8) at 0x22f8880
    REFCNT = 2
    FLAGS = (PADMY,SHAREKEYS)
    ARRAY = 0x22e99a0  (0:7, 1:1)
    hash quality = 100.0%
    KEYS = 1
    FILL = 1
    MAX = 7
    RITER = -1
    EITER = 0x0
    Elt "x" HASH = 0x9303a5e5
    SV = PV(0x22cfc88) at 0x22d1b98
      REFCNT = 1
      FLAGS = (POK,FAKE,READONLY,pPOK)
      PV = 0x22f8450 "a"
      CUR = 1
      LEN = 0

请注意,哈希条目的FLAGS是" READONLY"事实上,变量$ x和%y中相应值的值实际上指向相同的字符串(在上面的示例中PV = 0x22f8450)。似乎Perl 5.10正在努力避免复制字符串,但这样做无意中锁定了整个哈希值。

我使用的解决方法是强制使用字符串副本,如下所示:

foreach my $k (keys %h) {
        my $j = "$k";
        doSomething(1, 2, otherOption=>$j);
}

这似乎是强制字符串副本的低效方式,并且在任何情况下都很容易忘记,因此欢迎包含更好的解决方法的其他答案。