【问题标题】:Trying to localize an outside package variable through a lexical binding in Perl尝试通过 Perl 中的词法绑定本地化外部包变量
【发布时间】:2011-12-09 14:09:01
【问题描述】:

标题很长,但我恐怕一个字都说不出来,又不失问题的真意。我将简要介绍一下我首先要实现的目标,然后再详细说明我为什么要以这种方式完成。如果您打算按照问题标题直接回答,请跳过漫谈:-)

简要说明

假设存在一个可以满足我要求的 lexicalize 内置函数,如下所示:

package Whatever;
{
  lexicalize $Other::Package::var;
  local $var = 42;
  Other::Package->do_stuff; # depends on that variable internally
}

为什么和为什么不

就上下文而言,我正在对第三方模块进行白盒测试。

我希望变量本地化,因为我希望它只在有限的时间内更改,然后再进行其他测试。 local 是我发现的最好的方法,它不需要知道模块选择的初始值。

我想要一个词法绑定有两个主要原因:

  1. 我不想污染测试文件的高级命名空间。
  2. 我想要比完全限定名称更短的名称。为简洁起见,它不在示例代码中,但我使用的标识符比显示的要多得多,计算和更新都与之前的值有关。

我不能正确地使用our,因为它不会从另一个包中获取变量:“No package name allowed for variable $Other::Package::var in "our".”作弊暂时进入 Other::Package 的范围并没有减少它:如果我使用一个块 ({ package Other::Package; our $var }),那么绑定的持续时间不够长,无法使用;如果我不这样做 (package Other::Package; our @var; package main),那么我需要知道并复制前一个包的名称,这样可以防止过多地移动那段代码。

在问这个问题之前的形式之前做功课时,我发现了Lexical::Var,这似乎正是我需要的。唉:“无法通过引用进行本地化。”

我也尽我最大的努力对基于my *var 的表单的直觉,但不断遇到语法错误。今天我学到的关于范围界定和绑定的知识比我关心的要多:-)

我想不出为什么我想要的东西不应该是可能的,但我找不到正确的咒语。我是在要求一个不幸的未实现的边缘案例吗?

【问题讨论】:

    标签: perl variables internals lexical-scope


    【解决方案1】:

    有几种方法可以做到这一点:

    • 给定:

      {package Test::Pkg;
          our $var = 'init';
          sub method {print "Test::Pkg::var = $var\n"}
      }
      
    • 将当前包中的包变量别名为目标包。

      {
          package main;
          our $var;
      
          Test::Pkg->method; #     Test::Pkg::var = init
      
          local *var = \local $Test::Pkg::var;  
              # alias $var to a localized $Test::Pkg::var
      
          $var = 'new value';
      
          Test::Pkg->method; #    Test::Pkg::var = new value
      }
      
      Test::Pkg->method;     #    Test::Pkg::var = init
      
    • 通过全局引用进行本地化:

      {
          package main;
      
          my $var = \*Test::Pkg::var;  # globref to target the local
      
          Test::Pkg->method; #     Test::Pkg::var = init
      
          local *$var = \'new value';  # fills the scalar slot of the glob
      
          Test::Pkg->method; #    Test::Pkg::var = new value
      }
      
      Test::Pkg->method;     #    Test::Pkg::var = init
      
    • 在目标包中绑定一个词法,让它溢出到当前包中:

      {
          package Test::Pkg;  # in the target package
          our $var;           # create the lexical name $var for $Test::Pkg::var
      
          package main;       # enter main package, lexical still in scope
      
          Test::Pkg->method; #     Test::Pkg::var = init
      
          local $var = 'new value';
      
          Test::Pkg->method; #    Test::Pkg::var = new value
      }
      
      Test::Pkg->method;     #    Test::Pkg::var = init
      

    最后一个例子使用了一个有点尴尬的双包声明来在一个包中创建一个词法并将其放入另一个包中。这个技巧在其他场景中对local 很有用:

     no strict 'refs';
     local ${'Some::Pkg::'.$name} = 'something';
     use strict 'refs';
    

    【讨论】:

    • 在霍布斯(SO 订单)之后阅读您的答案,同样的评论也适用。非常感谢您的帮助!
    • @JB。 => 添加了一些不污染命名空间的示例。
    • 你的第二个,除了提醒我不想在 typeglobs 上记住的事情(这个问题的主旨),似乎是我想做的事情的一个理智的基础。稍微调整一下...
    • 是的!对您的第二个答案进行细微调整:my $ref = \*Other::Package::var; local *$ref = \(my $var = f($$$ref));(我需要本地化变量非常量,可以轻松修改为$var,并初始化为原始值的函数)这似乎适合我所有的表达和非表达(提出问题是困难)要求。恭喜! PS您介意编辑以将答案的那部分放在首位吗? (第一个答案非词法泄漏;第三个答案需要知道包含包名的知识)PPS 很高兴找到了 $$$form 的最合理用途。
    【解决方案2】:

    我不太确定我是否理解正确。这有帮助吗?

    #!/usr/bin/env perl
    
    {
        package This;
        use warnings; use strict;
        our $var = 42;
    
        sub do_stuff {
            print "$var\n";
        }
    }
    
    {
        package That;
        use warnings; use strict;
    
        use Lexical::Alias;
        local $This::var;
    
        alias $This::var, my $var;
    
        $var = 24;
        print "$var\n";
    
        This->do_stuff;
    }
    
    package main;
    
    use warnings; use strict;
    
    This->do_stuff;
    

    【讨论】:

    • Lexical::Alias... 环顾四周时没有找到那个。似乎很有希望。试试看,我会尽快通知您。
    • 有趣的东西。你刚刚让我了解的内部结构比我想的要多一点。就我对问题的表述而言,它是有效的(所以我会赞成并接受)。因为我一直很笨,省略了太多,这不是我想要的,但我现在只是在责备自己。
    • 我省略了:我的local 行实际上是local $pkg::var = f($pkg::var)local 必须在别名之前完成,否则它会破坏它,所以我要写三遍这个巨大的完全限定名称,这有点超出我的目标。
    【解决方案3】:

    如果我说对了,您所需要的就是我们的。如果我不这样做,请原谅我。

    以下是工作示例:

    #!/usr/bin/env perl
    use strict;
    use warnings;
    
    package Some;
    
    our $var = 42;
    sub do_stuff { return $var }
    
    package main;
    
    {
        local $Some::var = 43;
        print "Localized: " . Some->do_stuff() . "\n";
    };
    
    print "Normal: " . Some->do_stuff() . "\n";
    

    但如果你尝试本地化某个包的私有 (my) 变量,你可能做错了什么。

    【讨论】:

    • 它确实有效:我目前已经在使用它的变体,因为缺少更好的东西。这不能满足我自己的要求的地方在于它没有创建新的(更短的)词法绑定:在块内,我们坚持使用变量的完全限定 $Some::var 形式而不是更好的普通 @ 987654324@。我的实际包名比这长得多;这就是我最初提出问题的原因。
    • 旁注:这可能是您在单个代码示例中复制粘贴多个文件的副作用,但您靠近顶部的 our $var = 42 行可能会污染您想要尝试的任何内容稍后的目标块。更好地将包 Some 包装在一个块中。
    【解决方案4】:
    package Other::Package;
    
    $var = 23;
    
    sub say_var {
      my $label = shift;
      print "$label: $Other::Package::var\n";
    }
    
    package Whatever;
    Other::Package::say_var("before");
    {
      local $Other::Package::var = 42;
      local *var = \$Other::Package::var;
      Other::Package::say_var("during");
      print "\$Whatever::var is $var inside the block\n";
    }
    Other::Package::say_var("after");
    print "\$Whatever::var is ",
        defined($var) ? "" : "un", "defined outside the block\n";
    

    输出:

    before: 23
    during: 42
    $Whatever::var is 42 inside the block
    after: 23
    $Whatever::var is undefined outside the block
    

    local $Other::Package::var 在区块期间将原始值临时设为 42,local *var = \$Other::Package::var 在当前包中将 $var 在区块期间临时设为 $Other::Package::var 的别名。

    当然,其中一些不符合strict,但我避免使用our,因为our 混淆了如果两个包都在同一个文件中的问题(如果你复制粘贴这个示例,它们可能会是这样) ) — our 声明可能会从它使用的包中泄漏到同一文件中的以下包中:)

    【讨论】:

    • 感谢您的回答!这并不完全是我想要的,因为它会将别名泄漏到词法范围之外的 *Whatever::var (我在想任何东西中的辅助函数)。感谢您花时间尝试帮助我,非常感谢。
    • @JB。啊,在那种情况下 Lexical::Alias 就是你想要的(正如思南所说)。我试图避免这种情况,因为它是一种棘手的野兽。
    猜你喜欢
    • 1970-01-01
    • 2021-12-17
    • 2021-10-12
    • 1970-01-01
    • 1970-01-01
    • 2017-11-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多