尝试通过 Perl 中的词法绑定本地化外部包变量

发布于 2024-12-10 10:27:54 字数 1215 浏览 0 评论 0原文

标题很长,但我恐怕一个字都删不掉,又不失问题的真正含义。我将首先快速描述我想要实现的目标,然后长篇大论地阐述为什么我希望以这种方式完成它。如果您打算直接回答问题标题,请跳过漫无目的的内容:-)

快速描述

假设存在一个 lexicalize 内置函数可以满足我的要求,这里是:

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

为什么和为什么

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

我想要变量本地化,因为我希望它仅在有限的时间内发生变化,然后再进行其他测试。 local 是我发现执行此操作的最佳方法,无需依赖于了解模块对初始值的选择。

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

  1. 我不想污染测试文件的更高级别的命名空间。
  2. 我想要比完全限定名称更短的名称。为了简洁起见,它没有出现在示例代码中,但我使用的标识符比显示的要多得多,并进行了相对于其先前值的计算和更新。

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

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

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

我想不出为什么我想要的东西不可能实现的原因,但我找不到正确的咒语。我是否要求一个不幸的未实现的边缘情况?

It's a long title, but I'm afraid I can't take a single word out without losing the true meaning of the question. I'll give a quick description of what I'm trying to achieve first, then a long rambling on why I want it done this way. Skip the rambling if you intend to directly answer abiding with the question title :-)

Quick description

Assuming the existence of a lexicalize builtin that does just what I want, here goes:

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

Whys and whynots

For a bit of context, I'm whitebox-testing a third-party module.

I want the variable localized because I want it changed for a limited time only, before the tests move on to something else. local is the best way I found to do this without depending on knowing the module's choice for an initial value.

I want a lexical binding for two main reasons:

  1. I don't want to pollute the test file's higher-level namespace.
  2. I want something shorter than the fully-qualified name. It's not in the sample code for brevity, but I use the identifier a lot more than what's shown, with calculations and updates relative to its previous value.

I couldn't decently use our because it won't grab a variable from another package: “No package name allowed for variable $Other::Package::var in "our".” Cheating to temporarily enter Other::Package's scope doesn't cut it: if I use a block ({ package Other::Package; our $var }) then the binding doesn't last long enough to be useful; and if I don't (package Other::Package; our @var; package main) then I need to know and copy the previous package's name, which prevents moving that piece of code around too much.

While doing my homework before asking a previous form of this question, I discovered Lexical::Var, which seemed like it would be exactly what I needed. Alas: “Can't localize through a reference.”

I've also tried my best on my gut feeling of my *var-based forms, but kept bumping into syntax errors. I've learned more than I cared to about scoping and binding today :-)

I can't think of a reason why what I want shouldn't be possible, but I can't find the right incantation. Am I asking for an unfortunate unimplemented edge case?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

沧笙踏歌 2024-12-17 10:27:54

如果我没听错的话,你所需要的就是我们的。如果我不这样做,请原谅我。

这是工作示例:

#!/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)变量,您可能做错了什么。

If I get you right, all you need is our. Forgive me if I dont.

Here's working example:

#!/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";

But if you try to localize private (my) variable of some package, you probably doing something wrong.

时光是把杀猪刀 2024-12-17 10:27:54

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

  • 给出:

    {包测试::Pkg;
        我们的 $var = 'init';
        子方法 {print "Test::Pkg::var = $var\n"}
    }
    
  • 将当前包的包变量别名为目标包。

    <前><代码>{
    包主;
    我们的 $var;

    测试::Pkg->方法; # 测试::Pkg::var = init

    本地 *var = \local $Test::Pkg::var;
    # 将 $var 别名为本地化的 $Test::Pkg::var

    $var = '新值';

    测试::Pkg->方法; # Test::Pkg::var = 新值
    }

    测试::Pkg->方法; # 测试::Pkg::var = init

  • 通过全局引用进行本地化:

    <前><代码>{
    包主;

    我的 $var = \*Test::Pkg::var; # globref 以本地为目标

    测试::Pkg->方法; # 测试::Pkg::var = init

    local *$var = \'新值'; # 填充 glob 的标量槽

    测试::Pkg->方法; # Test::Pkg::var = 新值
    }

    测试::Pkg->方法; # 测试::Pkg::var = init

  • 绑定目标包中的词法,并让它溢出到当前包中:

    <前><代码>{
    包装测试::Pkg; # 目标包中
    我们的 $var; # 为 $Test::Pkg::var 创建词法名称 $var

    包主; # 进入主包,词法仍在范围内

    测试::Pkg->方法; # 测试::Pkg::var = init

    local $var = '新值';

    测试::Pkg->方法; # Test::Pkg::var = 新值
    }

    测试::Pkg->方法; # 测试::Pkg::var = init

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

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

there are several ways to do this:

  • given:

    {package Test::Pkg;
        our $var = 'init';
        sub method {print "Test::Pkg::var = $var\n"}
    }
    
  • alias a package variable from the current package to the target package.

    {
        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
    
  • localize through a glob reference:

    {
        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
    
  • bind a the lexical in the target package, and let it spill into the current package:

    {
        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
    

the last example uses a slightly awkward double package declaration to create a lexical in one package and get it into another. This trick is useful with local in other scenarios:

 no strict 'refs';
 local ${'Some::Pkg::'.$name} = 'something';
 use strict 'refs';
荆棘i 2024-12-17 10:27:54

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

#!/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;

I am not quite sure if I understand you correctly. Does this help?

#!/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;
昵称有卵用 2024-12-17 10:27:54
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 code> 使当前包中的 $var 在块的持续时间内临时成为 $Other::Package::var 的别名。

当然,其中一些不符合严格标准,但我避免使用our,因为如果两个包都在同一个文件中,our会混淆问题(如果您复制粘贴此示例,可能会出现这种情况)— our 声明可能会从它所使用的包中泄漏到同一文件中的以下包中:)

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";

Output:

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

local $Other::Package::var makes the original value temporarily 42 for the duration of the block, and local *var = \$Other::Package::var makes $var in the current package an alias for $Other::Package::var temporarily for the duration of the block.

Naturally some of this isn't strict-compliant, but I avoided using our because our obfuscates the issue if both packages are in the same file (as they might be if you copy-pasted this sample) — the our declaration can leak out of the package it's used in, into a following package in the same file :)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文