【问题标题】:Perl variables scopingPerl 变量作用域
【发布时间】:2015-04-17 12:00:24
【问题描述】:

我想使用this 方法从数组中删除重复值。重复删除必须在循环内执行。 这是一个演示我遇到的问题的最小示例:

use strict;

for (0..1){
    my %seen;
    sub testUnique{
        return !$seen{shift}++;
    }

    my $res = testUnique(1);

    if($res){
        print "unique\n";
    }else{
        print "non-unique\n";
    }
}

我在循环中定义了%seen 哈希,因此我希望它仅在循环的单次迭代中定义。 然而,上面代码的结果是:

unique
non-unique

通过一些调试打印,我发现%seen 的值在一次迭代中保留了下来。

我尝试了一个微不足道的

for (0..1){
    my %seen;
    $seen{1}++;
    print "$seen{1}\n";
}

而且这个按预期工作。它打印:

1
1

所以,我想问题出在内部函数testUnique 上。 有人可以解释一下这里发生了什么吗?

【问题讨论】:

    标签: perl scope


    【解决方案1】:

    您的testUnique 子关闭%seen 的第一个实例。即使它在for 循环内,子例程也不会被重复编译。

    您的代码只编译一次,包括 initialize a lexically scoped variable %hash 就在 for 循环的顶部

    以下将产生您想要的输出,但我不确定我是否看到了这条路径:

    #!/usr/bin/env perl
    
    use warnings;
    use strict;
    
    for (0..1){
        my %seen;
        my $tester = sub {
            return !$seen{shift}++;
        };
    
        print $tester->(1) ? "unique\n" : "not unique\n";
    }
    

    【讨论】:

    • 所以变量被重复定义,子程序不被重复编译。这不是一个连贯的行为,但肯定可以解释这些现象。谢谢。
    • 当您意识到perl 穿过a compile stage, and a run time stage 时,这是非常一致的行为。 %seen 是一个词法变量。此外,变量不会重复重新定义。一切都编译一次。
    • 非常感谢您发布正确的解决方案,而不仅仅是解释问题。
    • @jutky:区别不在于定义,而在于声明%seen 是封闭块的本地,但testUnique(应该正确地称为test_unique)是全局的,与大多数语言一样,并且在子块中定义它会产生误导。 Sinan 已经展示了如何在一个块中重新定义一个子程序,如果这是你想做的。
    • @Borodin 但我不能在循环之外调用testUnique,所以它不是很全球化。 (我来自 Java,因此命名约定 ;))
    【解决方案2】:

    子例程只能定义一次,并且不会为循环的每次迭代重新创建。结果,它只包含对初始 %seen 哈希的引用。添加一些输出有助于澄清这一点:

    use strict;
    use warnings;
    
    for(0 .. 1) {
        my %seen = ();
        print "Just created " . \%seen . "\n";
    
        sub testUnique {
            print "Testing " . \%seen . "\n";
            return ! $seen{shift} ++;
        }
    
        if(testUnique(1)) {
            print "unique\n";
        }
        else {
            print "non-unique\n";
        }
    }
    

    输出:

    Just created HASH(0x994fc18)
    Testing HASH(0x994fc18)
    unique
    Just created HASH(0x993f048)
    Testing HASH(0x994fc18)
    non-unique
    

    这里可以看出,初始哈希是唯一被测试的。

    【讨论】:

    • 这个答案是最好的。它很好地演示了for 循环创建的闭包。
    • ++ 进一步的功课可以与my $test_unique = sub { print "Testing " . \%seen . "\n"; return ! $seen{shift()} ++;}进行比较;和if ( $test_unique->($num) ) {say "you neat"} 进一步解释与匿名子例程的区别。
    【解决方案3】:

    欢迎来到闭包的世界。

    sub make_closure {
        my $counter = 0;
        return sub { return ++$counter };
    }
    
    my $counter1 = make_closure();
    my $counter2 = make_closure();
    
    say $counter1->();  # 1
    say $counter1->();  # 2
    say $counter1->();  # 3
    say $counter2->();  # 1
    say $counter2->();  # 2
    say $counter1->();  # 4
    

    sub { } 捕获范围内的词法变量,即使它们所在的范围已经消失,子也可以访问它们。

    你每天都在不知不觉中使用这种能力。

     my $foo = ...;
     sub print_foo { print "$foo\n"; }
    

    如果 subs 没有捕获,上述方法在模块中不起作用,因为文件的词法范围通常在调用模块中的任何函数之前退出。

    print_foo 不仅需要捕获$foo 才能使上述工作正常工作,而且在编译时也必须这样做。

    sub testUnique {
        return !$seen{shift}++;
    }
    

    基本上是一样的

    BEGIN {
        *testUnique = sub {
            return !$seen{shift}++;
        };
    }
    

    这意味着sub { } 在编译时执行,这意味着它捕获了存在于编译时%seen,这意味着甚至在循环开始之前。

    循环的第一遍将使用相同的%seen,但将为每个后续遍创建一个新的%seen,以允许类似

    my @outer;
    for (...) {
       my @inner = ...;
       push @outer, \@inner;
    }
    

    如果您在运行时执行sub { },就没有问题。

    for (0..1){
        my %seen;
        local *testUnique = sub {
            return !$seen{shift}++;
        };
    
        my $res = testUnique(1);
    
        if($res){
            print "unique\n";
        }else{
            print "non-unique\n";
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-08-06
      • 2011-07-06
      • 2021-10-11
      • 2011-08-03
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多