繁体   English   中英

可以在 perl 中定义一个不能在子程序中更改其值的变量吗?

[英]can a variable be defined in perl whose value cannot be changed in a subroutine?

在下面的脚本中,我在主程序中声明并修改了@basearray 里面dosomething子程序,我访问@basearray ,将其分配到一个数组本地的脚本,修改本地副本。 因为我一直小心翼翼地改变子程序内部局部变量的值, @basearray没有改变。

但是,如果我在子例程中错误地为@basearray分配了一个值,那么它就会被更改并且该值会在调用子例程后保持@basearray

这在第二个子程序doagain

此外, doagain将引用\\@basearray作为参数接收,而不是直接访问@basearray 但是去处理那些额外的麻烦并不能提供额外的安全性。 为什么要这样做呢?

有没有办法保证我不会在任何子例程中无意中更改@basearray 我可以在我的代码中构建任何类型的硬安全设备,类似于use strict; ,也许是mylocal某种组合?

我认为答案是否定的,唯一的解决方案是不要犯粗心的程序员错误,我是否正确?

#!/usr/bin/perl
use strict; use warnings;
my @basearray = qw / amoeba /;
my $count;

{
    print "\@basearray==\n"; 
    $count = 0; 
    foreach my $el (@basearray) { $count++; print "$count:\t$el\n" };
}

sub dosomething 
{
    my $sb_name = (caller(0))[3];
    print "entered $sb_name\n";
    my @sb_array=( @basearray , 'dog' );
    {
        print "\@sb_array==\n"; 
        $count = 0; 
        foreach my $el (@sb_array) { $count++; print "$count:\t$el\n" };
    }
    print "return from $sb_name\n";
}

dosomething();
@basearray = ( @basearray, 'rats' );

{
    print "\@basearray==\n"; 
    $count = 0; 
    foreach my $el (@basearray) { $count++; print "$count:\t$el\n" };
}


sub doagain
{
    my $sb_name = (caller(0))[3];
    print "entered $sb_name\n";
    my $sf_array=$_[0];
    my @sb_array=@$sf_array;
    @sb_array=( @sb_array, "piglets ... influenza" );
    {
        print "\@sb_array==\n"; 
        $count = 0; 
        foreach my $el (@sb_array) { $count++; print "$count:\t$el\n" };
    }
    print "now we demonstrate that passing an array as an argument to a subroutine does not protect it from being globally changed by programmer error\n";
    @basearray = ( @sb_array );
    print "return from $sb_name\n";
}
doagain( \@basearray );

{
    print "\@basearray==\n"; 
    $count = 0; 
    foreach my $el (@basearray) { $count++; print "$count:\t$el\n" };
}

没有 pragma 或关键字等,但有完善的“良好实践”,在这种情况下,它们完全解决了您合理思考的问题。

  1. 第一个子dosomething犯下了使用在其范围内可见但在更高范围内定义的变量的罪过。 相反,始终将所需的数据传递给子程序(例外情况很少见,在非常清晰的情况下)。

    直接使用来自“外部”的数据违背了将函数作为封装过程的想法,通过定义明确且清晰的界面与其用户交换数据。 它纠缠(“耦合”)原则上完全不相关的代码段。 在实践中,它也可能是完全危险的。

    此外, @basearray在 sub 中@basearray的事实最好被认为是一个意外——当该 sub 被移动到一个模块时怎么办? 或者引入另一个子来合并定义@basearray代码?

  2. 第二个子doagain很好地引用了该数组。 然后,为了保护调用者中的数据,可以将调用者的数组复制到子本地的另一个数组

    sub doagain { my ($ref_basearray) = @_; my @local_ba = @$ref_basearray; # work with local_ba and the caller's basearray is safe }

    局部词法变量的名称当然是任意的,但是它们类似于调用者的数据名称的约定可能是有用的。

然后,为了安全起见,您可以采用一般做法,始终将输入变量复制到本地变量。 仅当您想更改调用者的数据时才直接使用传入的引用(在 Perl 中相对少见)。 如果处理大量数据或涉及非常大的数据结构,这可能会降低效率。 所以也许然后通过它的引用来创建一个例外并更改数据,并且要格外小心。

有几种解决方案具有不同程度的简洁性,从“只是不要更改它”到“使用对象或绑定数组并锁定更新函数”。 一个中间解决方案,与使用带有 getter 方法的对象不同,是定义一个返回数组但只能作为右值操作的函数,并在子例程中使用该函数。

my @basearray = (...);
sub basearray { return @basearray }

sub foo {
    foreach my $elem (basearray()) {
       ...
    }
    @bar = map { $_ *= 2 } basearray();  # ok
    @bar = map { $_ *= 2 } @basearray;   # modifies @basearray!
    
}

(将我的评论作为答案)保证不更改子例程内的变量的一种方法是不更改它。 在子例程中仅使用词法范围的变量,并将子例程中需要的任何值作为参数传递给子例程。 这是一种足够常见的编码实践,封装。

您可以使用的一个想法——主要是作为实践,我会说——强迫自己使用封装,是在“主”代码周围放置一个块,并将子例程放在它之外。 这样,如果您不小心引用了(以前的)全局变量, use strict将能够完成它的工作并产生致命错误。 在运行之前。

use strict;
use warnings;

main: {                      # lexical scope reduced to this block
    my @basearray = qw / amoeba /;
    print foo(@basearray);   # works
    print bar();             # fatal error
} # END OF MAIN                lexical scope of @basearray ends here

sub foo {
    my @basearray = @_;      # encapsulated 
    return $basearray[1]++;
}
sub bar {
    return $basearray[1]++;  # out of scope ERROR
}

这不会编译,并会产生错误:

Global symbol "@basearray" requires explicit package name at foo.pl line 15.
Execution of foo.pl aborted due to compilation errors.

我认为这是一种训练设备,可以强迫自己使用良好的编码实践,而不是必须在生产代码中使用的东西。

TLDR:是的,但是。

我将从“但是”开始。 但是最好设计您的代码,以便变量根本不存在于定义不受信任函数的范围内。

sub untrusted_function {
  ...
}

my @basearray = qw( ... );  # declared after untrusted_function

如果untrusted_function需要能够访问数组的内容,则将数组的副本作为参数传递给它,这样它就不能修改原始的。

现在是“是”。

您可以在调用不受信任的函数之前将数组标记为只读。

Internals::SvREADONLY($_, 1) for @basearray;
Internals::SvREADONLY(@basearray, 1);

然后在函数完成后再次将其标记为读写。

Internals::SvREADONLY(@basearray, 0);
Internals::SvREADONLY($_, 0) for @basearray;

使用Internals::SvREADONLY(@basearray, $bool)修改数组本身的只读状态,防止元素被添加或删除; Internals::SvREADONLY($_, $bool) for @basearray修改数组中每个元素的只读状态,这也是您可能想要的。

当然,如果你的数组包含像祝福对象这样的引用,那么你需要考虑是否需要递归到引用中,也将它们标记为只读。 (但也可能与我在首选解决方案中提到的数组的浅拷贝有关!)

所以是的,可以通过在调用 sub 之前将该变量标记为只读来防止 sub 意外修改变量,重组代码是一个更好的主意,以便 sub 根本无法访问该变量.

对,但是。

这是一个使用@TLP 答案的原型。

#!/usr/bin/perl
use strict; use warnings;

{   # block_main BEG
    my @basearray = qw / amoeba elephants sequoia /;
    print join ( ' ', 'in main, @basearray==', join ( ' ', @basearray ), "\n" );
    print "Now we call subroutine to print it:\n"; enumerateprintarray ( \@basearray );
    my $ref_basearray = changearray ( \@basearray, 'wolves or coyotes . . . ' );
    @basearray = @$ref_basearray;
    print "Now we call subroutine to print it:\n"; enumerateprintarray ( \@basearray );
}   # block_main END

sub enumerateprintarray
{
    my $sb_name = (caller(0))[3];
    #print join ( '' , @basearray ); # mortal sin! for in the day that thou eatest thereof thou shalt surely die.
    my $sb_exact_count_arg = 1;
    die "$sb_name must have exactly $sb_exact_count_arg arguments" unless ( ( scalar @_ ) == $sb_exact_count_arg );
    my $sf_array = $_[0];
    my @sb_array = @$sf_array;
    my $sb_count = 0;
    foreach (@sb_array)
    {
        $sb_count++; 
        print "\t$sb_count:\t$_\n";
    }
}

sub changearray
{
    my $sb_name = (caller(0))[3];
    #print join ( '' , @basearray ); # in the day that thou eatest thereof thou shalt surely die.
    my $sb_exact_count_arg = 2;
    die "$sb_name must have exactly $sb_exact_count_arg arguments" unless ( ( scalar @_ ) == $sb_exact_count_arg );
    my ( $sf_array,  $addstring ) = @_;
    my @sb_array = @$sf_array;
    push @sb_array, $addstring; 
    return ( \@sb_array );
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM