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

Akh*_*hil 1 perl scope global-variables dynamic-scope

我正在尝试编写一个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
Run Code Online (Sandbox Code Playgroud)

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

funcs.pm 看起来像这样:

use warnings;
use strict;

package plshelp;

sub change
{
    ${$pointer} = 4;
}
Run Code Online (Sandbox Code Playgroud)

我希望这可以在运行主脚本时更改值$var并打印4.但我得到一个编译错误说$pointer没有声明.可以通过our $pointer;changein 的顶部添加来删除此错误funcs.pm,但这会创建一个在任何地方都可见的不必要的全局变量.我们也可以删除这个错误use strict;,但这似乎是一个坏主意.我们也可以把它用工作$plshelp::pointerfuncs.pm,而是人的写作funcs.pm并不想这样做.

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

我们只是说由于某种原因不可能将参数传递给函数.

更新

local our就防止能见度而言,似乎没有做任何"特殊".来自perldoc:

这意味着当use strict 'vars'有效时,our允许您使用包变量而不使用包名限定它,但仅限于我们声明的词法范围内.这立即适用 - 即使在同一声明中也是如此.

即使之前没有使用过包变量,这仍然有效,因为首次使用时包变量会存在.

所以这意味着$pointer即使在我们离开花括号之后"存在".只是我们必须使用$plshelp::pointer而不是仅使用它来引用它$pointer.但是,自从我们local在初始化之前使用$pointer它之后,它仍然在范围之外未定义(尽管它仍然是"声明",无论这意味着什么).写一个更清晰的方法就是(local (our $pointer)) = \$var;.在这里,our $pointer"声明" $pointer并返回$pointer.我们现在应用local此返回值,此操作$pointer再次返回我们分配给的值\$var.

但是,这仍然存在一个主要问题,即是否有一种很好的方法可以实现所需的功能.

amo*_*mon 5

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

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

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

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

local运营商不创建一个变量.相反,它保存全局变量的当前值并清除该变量.在当前范围的末尾,将恢复旧值.您可以将每个全局变量名称解释为一堆值.随着local您可以添加(和删除)值在堆栈中.因此,虽然local可以动态范围化值,但它不会创建动态范围的变量名称.

通过仔细考虑编译哪些代码,很明显为什么您的示例当前不起作用:

  • 在主脚本中,加载模块funcs.该use语句在BEGIN阶段执行,即在解析期间执行.

    use warnings;
    use strict;
    
    package plshelp;
    use funcs;
    
    Run Code Online (Sandbox Code Playgroud)
  • funcs模块编译:

    use warnings;
    use strict;
    
    package plshelp;
    
    sub change
    {
        ${$pointer} = 4;
    }
    
    Run Code Online (Sandbox Code Playgroud)

    此时,没有$pointer变量在词法范围内,并且不$pointer存在导入的全局变量.因此,您会收到错误消息.此编译时观察与$pointer运行时变量的存在无关.

修复此错误的规范方法是our $pointer在以下范围内声明变量名称sub change:

sub change {
    our $pointer;
    ${$pointer} = 4;
}
Run Code Online (Sandbox Code Playgroud)

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


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

  • 在设计级别,全局变量不声明明确的接口.通过使用完全限定名称,您可以简单地访问变量而无需任何检查.它们不提供任何封装.这使得脆弱的软件和远距离的奇怪行动成为可能.

  • 在实现级别上,全局变量的效率低于词法变量.我从来没有真正看过这件事,但想到了周期!

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

我所看到的全局变量的唯一好处是为回调提供额外的上下文,这些回调不能采用额外的参数,大致类似于你的方法.但在可能的情况下,最好将上下文作为参数传递.子例程参数已经有效地动态范围化:

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

...
my $var = 3;
change(\$var);
Run Code Online (Sandbox Code Playgroud)

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