有人能向我解释一下这个只读异常吗?

5

以下是我的代码(不用担心,模块顶部有USUW)

我正在测试一个数组引用是否为只读,如果是的话,我就将其复制到另一个数组引用中。测试表明该数组并非只读,但在运行时却失败了并出现了那个错误。(对于不熟悉我的人或 Smart::Comments--那些###Smart::Comments。)

### readonly( $arg_ref ) : readonly( $arg_ref )
### readonly( @$arg_ref ) : readonly( @$arg_ref )
my @ro = map { readonly( $_ ) } @$arg_ref;
### @ro
if ( readonly $arg_ref ) {
    $arg_ref = [ @$arg_ref ];
}
return map { my $val = shift @$arg_ref;
             $_ => $val 
            } @_ 
            ;

这是我得到的输出结果:
### readonly( $arg_ref ) : 0
### readonly( @$arg_ref ) : 0

### @ro: [
###        0,
###        0,
###        0,
###        0,
###        0
###      ]

但是这里出现了错误:

Modification of a read-only value attempted at ....pm line 247.

(247表示:

return map { my $val = shift @$arg_ref;

有人对这个问题很熟悉吗?我们正在运行Perl 5.8.7。有什么解决方法吗?


readonly 符号是从哪里来的?我不认为 Readonly 模块提供了这个符号。 - Ether
无论是Scalar::Util::readonly - Axeman
$arg_ref是如何获取它的值的? - Greg Bacon
最终从DBI->fetchrow_arrayref,但我认为Scalar::Util::readonly会捕获它,然后我将执行jit复制以继续工作。 - Axeman
4个回答

2
如果从DBI::fetchrow_arrayref返回的引用是只读的,尝试覆盖它是无效的:引用是只读的,而不是列值数组。如果需要破坏性更新,请在源处创建自己的副本。
my $arg_ref = [ $sth->fetchrow_array ];

@Axeman 但是如果 $arg_ref 可能是只读的,那么像 shift 这样的破坏性操作就不适合使用,这一点你已经注意到了。 - Greg Bacon
编程的一部分就是解决问题。队列是可传输的,而“列表流”则不然,除非你想要把列表和当前列表位置的组合拼凑在一起。但这只能做到与使用Perl数组时获得的好老式队列一样好。现在,我可以总是复制它,但像我这样一个“性能和效率控”想要只做必要的事情而不想产生任何额外负担。 - Axeman
哦,你看,这是复制问题,这个P&E工程师不愿意在查询对象级别上进行复制,因为如果有人只想读取该行,则没有问题。我解决这个问题的方法是--尽管我的好奇心依然存在--现在将一个参数传递给查询对象方法,以便客户端可以让进程知道他们想要做“某些愚蠢的事情”。但我们总是可以编写代码绕过某些东西,我想知道为什么我不能预测它将需要它--并回答那个问题。必须有一些Oracle DBD模块的XS层中的问题。我应该试着看看它是否被“tied”了! - Axeman

1

Readonly 和 readonly 是不同的(注意大写 R 和小写 r)。

请查看 Scalar::Util 文档,了解为什么会这样:

readonly SCALAR

Returns true if SCALAR is readonly.

sub foo { readonly($_[0]) }
$readonly = foo($bar);              # false
$readonly = foo(0);                 # true
这更多涉及到别名(例如通过 foreach 循环或在 @_ 中传递的别名),而不是 Readonly 模块。

那么有没有办法检查一个变量是否被声明为只读(使用大写字母R)? - Ether
3
Readonly::XS 和 readonly 的 XS 实现都依赖于同一个标志位(SvREADONLY),因此它们应该是相同的(如果你正在使用 XS 版本,那么你应该使用相同的版本)。我嗅到了一个 bug。 - Leon Timmermans
1
@Ether,如果我理解正确的话,ref(tied(A_VARIABLE)) =~ /^Readonly::/ 应该可以解决问题,如果这个检查失败了,你需要回退到检查 Scalar::Util::readonly - pilcrow
@Leon,你能详细说明一下吗? Readonly.pm 1.03 确实看起来像是检查了 $XSokay(即 Readonly::XS 是否存在),如果是,则设置 SvREADONLY,但是如果没有,则将其绑定到一个 Readonly::Foo 类的 tie 上,在纯 Perl 逻辑分支中没有可用的 mutators... - pilcrow
1
我刚刚发布了一个详细说明正在发生什么的答案。这是一个只读错误。 - Leon Timmermans

1

我想我已经搞清楚了。Robert P是对的,但他只得到了一半的答案:Readonlyreadonly虽然应该做同样的事情,但它们却有不同的作用,这是一个错误。我将尝试解释正在发生的事情。

Readonly对标量有两种实现:一种(Readonly::XS)基于SvREADONLY标志,另一种(Readonly::Scalar)基于模拟SvREADONLY标志的ties。

readonly也有两个实现,一个在Perl中(它会自我赋值并检查是否抛出异常),另一个在XS中(再次基于SvREADONLY标志)。

有4种可能的组合,不幸的是最常见的那个(纯Perl Readonly和XS readonly)是唯一一个没有按照广告所说的工作的。

这里的关键是:sub Readonly::Readonly 实际上根本没有使用 Readonly::XS,只有 sub Readonly::Scalar 使用了。 这可能是一个错误readonly 正确地 报告该变量不是只读变量:它的只读性是通过 tie 模拟出来的。
在我看来,Readonly 在这里存在问题,它应该在可以时做正确的事情并在不能时进行文档说明。现在它两者都没有做到。

依我之见,Readonly 没有问题,我们的环境中甚至没有安装它。唯一需要注意的是 Scalar::Util::readonly 无法告诉我一个数组引用是否为只读状态,直到我尝试在其上进行 shift 操作。 - Axeman
实际上,似乎不是数组 引用 是只读的,而是它所引用的数组。这是微妙但重要的区别。 - Leon Timmermans
这种行为明显与文档相矛盾--文档中说当安装了Readonly::XS时,它会在所有标量的情况下使用(但不包括数组和哈希表)。真是遗憾。(那么谁来报告一个错误,或者更好地提交一个补丁呢?) :) - Ether
我已经写了一个补丁,但作者还没有回复我的电子邮件。 - Leon Timmermans

0

看起来Scalar::Util::readonly的结果不能被信任或者按照你想要的方式使用。例如:

perl -MScalar::Util=readonly -MReadonly -wle'
    Readonly my $arg_ref => [ qw(a b c)];
    print readonly $arg_ref;
    $arg_ref = 1;'

输出:

0
 Modification of a read-only value attempted at -e line 1.

(在 perl5.8.8 下测试,使用 Readonly 1.03、Scalar::Util 1.23)

我刚在perl5.12.1上运行了上面的测试,结果一样。:( - Ether
这是因为 Readonly 和 readonly 是不同的东西。 :) 请看我的答案解释原因。 - Robert P
@Robert:好知道。这可能让不少人感到困惑。 - Ether
我不喜欢这个“解决方案”,但是注意只读失败似乎就是它。我编写了一个重做循环来解决它 - 这是自从编写VB以来我从未做过的事情! - Axeman

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接