使用 Perl 的动态作用域时如何避免全局变量声明?

How to avoid global variable declaration when using Perl's dynamic scoping?

我正在尝试编写一个 perl 脚本,该脚本调用在其他地方(由其他人)编写的函数,该函数操纵我脚本范围内的一些变量。假设脚本是 main.pl,函数在 funcs.pm 中。我的 main.pl 看起来像这样:

use warnings;
use strict;

package plshelp;
use funcs;

my $var = 3;
print "$var\n";   # <--- prints 3

{                 # New scope somehow prevents visibility of $pointer outside
    local our $pointer = $var;
    change();
}

print "$var\n";   # <--- Ideally should print whatever funcs.pm wanted

出于某种原因,使用 local our $pointer; 会阻止 $pointer 在范围外的可见性。但是,如果我只使用 our $pointer;,则可以使用 $plshelp::pointermain.pl 的范围之外看到该变量(但在 funcs.pm 中则看不到,所以无论如何它都没有用)。作为旁注,有人可以解释一下吗?

funcs.pm 看起来像这样:

use warnings;
use strict;

package plshelp;

sub change
{
    ${$pointer} = 4;
}

我预计这会更改 $var 的值并在主脚本为 运行 时打印 4。但是我得到一个编译错误,说 $pointer 没有声明。可以通过在 funcs.pmchange 的顶部添加 our $pointer; 来消除此错误,但这会创建一个不必要的全局变量,该变量随处可见。我们也可以通过删除 use strict; 来消除这个错误,但这似乎是个坏主意。我们也可以通过在 funcs.pm 中使用 $plshelp::pointer 来让它工作,但是写 funcs.pm 的人不想那样做。

有没有什么好的方法可以实现让 funcs.pm 在我的范围内操作变量而不声明全局变量的功能?如果我们无论如何都要使用全局变量,我想我根本不需要使用动态范围。

假设由于某种原因无法将参数传递给函数。

更新

就防止可见性而言,local our 似乎没有做任何事情 "special"。来自 perldoc:

This means that when use strict 'vars' is in effect, our lets you use a package variable without qualifying it with the package name, but only within the lexical scope of the our declaration. This applies immediately--even within the same statement.

This works even if the package variable has not been used before, as package variables spring into existence when first used.

所以这意味着 $pointer "exists" 即使在我们离开花括号之后。只是我们必须使用 $plshelp::pointer 而不是 $pointer 来引用它。但是由于我们在初始化 $pointer 之前使用了 local,因此它在范围之外仍然是未定义的(尽管它仍然是 "declared",不管那是什么意思)。更清晰的写法是 (local (our $pointer)) = $var;。在这里,our $pointer "declares" $pointer 和 returns $pointer 也是如此。我们现在将 local 应用于此返回值,并再次将此操作 returns $pointer 分配给 $var.

但这仍然没有解决是否有实现所需功能的好方法这一主要问题。

您的代码错误太多,无法修复

您在主脚本和模块中都使用了 package plshelp,即使主入口点在 main.pl 中而您的模块在 funcs.pm 中。那是不负责任的。您是否认为 package 声明只是为了寻求帮助而发布的内容无关紧要?

你的 post 没有说你写的有什么问题,但令人惊讶的是它没有抛出错误。

这里有一些接近您预期的东西。我无法真正解释事情,因为您自己的代码目前还无法正常工作

Functions.pm

package Functions;

use strict;
use warnings;

use Exporter 'import';

our @EXPORT_OK = 'change';

sub change {

    my ($ref) = @_;

    $$ref = 4;
}

main.pl

use strict;
use warnings 'all';

use Functions 'change';

my $var = 44;

print "$var\n";
change($var);
print "$var\n";

输出

44
4

让我们弄清楚带有 our 的全局变量是如何工作的,以及为什么必须声明它们:全局变量的存储与其非限定名称的可见性之间存在差异。在use strict下,未定义的变量名不会隐式引用全局变量。

  • 我们总是可以使用全限定名访问全局变量,例如$Foo::bar

  • 如果当前包中的全局变量在编译时已经存在并且被标记为导入变量,我们可以使用非限定名称访问它,例如$bar。如果 Foo 包编写得当,我们可以说 use Foo qw($bar); say $bar 其中 $bar 现在是我们包中的全局变量。

  • 使用 our $foo,如果该变量不存在,我们会在当前包中创建一个全局变量。变量的名称也在当前词法范围内可用,就像 my 声明的变量一样。

local 运算符不创建变量。相反,它保存全局变量的当前值并清除该变量。在当前作用域结束时,旧值被恢复。您可以将每个全局变量名称解释为一堆值。使用 local 您可以在堆栈上添加(和删除)值。 因此,虽然 local 可以动态范围值,但它不会创建动态范围变量名称。

通过仔细考虑何时编译哪些代码,您的示例当前无法运行的原因就很清楚了:

  • 在您的主脚本中,您加载了模块 funcsuse 语句在 BEGIN 阶段执行,即在解析期间执行。

    use warnings;
    use strict;
    
    package plshelp;
    use funcs;
    
  • 编译funcs模块:

    use warnings;
    use strict;
    
    package plshelp;
    
    sub change
    {
        ${$pointer} = 4;
    }
    

    此时,词法范围内没有 $pointer 变量,也不存在导入的全局 $pointer 变量。因此你会得到一个错误。此编译时观察与运行时 $pointer 变量的存在无关。

修复此错误的规范方法是在 sub change:

的范围内声明一个 our $pointer 变量名
sub change {
    our $pointer;
    ${$pointer} = 4;
}

请注意,全局变量无论如何都会存在,这只是将名称引入范围以用作非限定变量名。


仅仅因为您可以使用全局变量并不意味着您应该这样做。它们有两个问题:

  • 在设计层面上,全局变量没有声明清晰的接口。通过使用完全限定名称,您可以直接访问变量而无需任何检查。它们不提供任何封装。这导致脆弱的软件和奇怪的远距离动作。

  • 在实现层面上,全局变量的效率简直不如词法变量。这件事我还真没见过,但是想想循环!

此外,全局变量是全局变量:它们一次只能有一个值!在某些情况下,使用 local 限定值可以帮助避免这种情况,但在复杂系统中仍然存在冲突,其中两个模块想要将相同的全局变量设置为不同的值并且这些模块相互调用。

我见过的全局变量唯一好的用途是为不能接受额外参数的回调提供额外的上下文,与您的方法大致相似。但在可能的情况下,最好将上下文作为参数传递。子例程参数已经有效地动态限定了范围:

sub change {
  my ($pointer) = @_;
  ${$pointer} = 4;
}

...
my $var = 3;
change($var);

如果有很多上下文,传递所有这些引用可能会很麻烦:change($foo, $bar, $baz, \@something_else, \%even_more, ...)。然后将该上下文捆绑到一个对象中可能是有意义的,然后可以以更受控的方式对其进行操作。操纵局部或全局变量并不总是最好的设计。