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

时间:2018-07-06 14:10:26

标签: 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

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

funcs.pm看起来像这样:

use warnings;
use strict;

package plshelp;

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

我希望这样可以在运行主脚本时更改$var的值并打印4。但是我收到一个编译错误,指出未声明$pointer。可以通过在our $pointer;的{​​{1}}的顶部添加change来消除此错误,但这将创建一个不必要的全局变量,该变量在任何地方都可见。我们也可以通过删除funcs.pm来消除此错误,但这似乎是一个坏主意。我们也可以通过在use strict;中使用$plshelp::pointer使它工作,但是写funcs.pm的人不想这样做。

是否有一种好的方法来实现让funcs.pm在我的范围内操作变量而不声明全局变量的功能?如果我们还是要使用全局变量,我想我根本不需要使用动态作用域。

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

更新

就防止可见性而言,funcs.pm似乎没有做任何“特殊”操作。来自perldoc

  

这意味着local our生效时,use strict 'vars'允许您使用包变量而不用包名来限定它,而只能在我们声明的词法范围内使用。这立即适用-即使在同一条语句内。

  

即使以前没有使用过package变量,它也可以工作,因为package变量在首次使用时就已经存在。

因此,这意味着our“存在”,即使我们离开了花括号也是如此。只是我们必须使用$pointer而不是$plshelp::pointer来引用它。但是,由于我们在初始化$pointer之前使用了local,因此在范围之外仍未定义它(尽管无论如何它都被“声明了”)。一种更清晰的写方法是$pointer。在这里,(local (our $pointer)) = \$var;“声明” our $pointer并返回$pointer。现在,我们将$pointer应用于此返回值,并且此操作再次返回local,我们将其分配给$pointer

但这仍然是一个主要问题,即是否存在一种很好的方法来实现所需的功能而未得到答复。

2 个答案:

答案 0 :(得分:5)

让我们清楚了解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变量的存在无关。

解决此错误的规范方法是在our $pointer范围内声明一个sub change变量名称:

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

请注意,全局变量无论如何都会存在,这只会使名称成为范围,用作不合格的变量名称。


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

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

  • 在实现级别,全局变量的效率仅比词法变量低。我从来没有真正看过这个问题,但是想想周期!

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

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

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

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

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

答案 1 :(得分:-1)

您的代码有太多错误,无法对其进行修复

即使主入口点位于package plshelp中,而模块位于main.pl中,您也已在主脚本和模块中使用了funcs.pm。那是不负责任的。您是否认为package语句仅用于广告寻求帮助,而不管您在此处输入什么内容?

您的帖子没有说您写的内容有什么问题,但是令人惊讶的是它没有引发错误。

这很接近您期望的结果。我真的无法解释问题,因为您自己的代码尚无法正常工作

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