【问题标题】:Understanding Perl packages namespacing理解 Perl 包的命名空间
【发布时间】:2021-12-16 05:13:27
【问题描述】:

考虑这个脚本(从我拥有的更大的脚本中总结出来):

#!/usr/bin/perl

use strict;
use warnings;

package help {
    my %HELPTEXT;
    $HELPTEXT{ "topic" } = "Some help\n";

    sub get_help( $ ) {
        print $HELPTEXT{ $_[0] };
    }
}

package main {
    sub main() {
        help::get_help( "topic" );
    }
}

main();

这有效,打印Some help

现在,我想将初始化($HELPTEXT{ "topic" } = "Some help\n"; 和其他类似的)放在脚本的末尾 - 同时保留 package help 及其代码。

但是,这不起作用:

# ...
main();

package help {
    $HELPTEXT{ "topic" } = "Some help";
}

错误信息:Global symbol "%HELPTEXT" requires explicit package name (did you forget to declare "my %HELPTEXT"?)

这也不起作用:

main();

$help::HELPTEXT{ "topic" } = "Some help\n";

错误信息:Name "help::HELPTEXT" only used once / Use of uninitialized value in print

显然我缺乏对 Perl 包及其命名空间的理解。

我可以在中间文件包命名空间中有一个变量,并且仍然在文件末尾初始化它吗?

补充:我发现声明问题可以通过our解决:

# ...
main();

package help {
    our %HELPTEXT;
    $HELPTEXT{ "topic" } = "Some help\n";
}

来自 perldoc:

"our" 为当前包中同名的包(即全局)变量创建一个词法别名,以便在当前词法范围内使用。

但是,这仍然会出现错误:Use of uninitialized value in print。即使我将初始化包装在 INIT {} 块中。

我不仅想为此提供解决方案,而且了解为什么这不能按我预期的那样工作。通过猜测编码感觉很糟糕......

【问题讨论】:

  • my %HELPTEXT; 去哪儿了?
  • my 声明的变量超出了它声明的块的范围。
  • @choroba 他正在使用两个 package help 块......显然你可以这样做,根据 perldoc。这也让我很困惑。
  • @TLP,是的,使用同一个包的不同代码段没有问题。 package help { } 之前和之后的代码将是另一个示例。
  • 如果您将main() 函数调用放在最后一个包声明之后,它会按预期工作。它似乎按照它出现的顺序执行代码,如果你仔细想想,这并不奇怪。如果我写my $foo; print $foo; $foo = 'abc';,那么它不会打印abc,我们也不会期望它。

标签: perl package initialization


【解决方案1】:

这里有几件事要理解,我在Learning Perl 中写了很多关于此的内容。其中大部分是意识到词法变量和包变量是两个不同的系统。任何词汇都不关心默认包是什么。

这是您的 strict 失败示例:

use v5.12;
use strict;

package help {
    $HELPTEXT{ "topic" } = "Some help";
}

use strict 适用于您使用它的任何范围(文件或块)。在该范围内,您必须在首次使用时声明变量或使用完整的包规范。由于您都没有这样做,因此您会收到错误消息。更改软件包并不会将其关闭。

package 声明仅更改默认包名称。使用块形式,它只会更改该块中的默认包。任何词汇都不关心,包括词汇变量。这个$foo 不关心默认包是什么,因为它不是包变量。无论默认包是什么,它都会持续到范围(块或文件)结束:

use v5.12;
use strict;
use warnings;

package help;

my $foo = 'bar';

package main;

say $foo;  # outputs bar

使用our 会在您组合同一范围内的包时产生奇怪的效果。您将获得一个具有该名称的词法变量,该变量在范围内的任何位置都可用,并且您将获得一个具有该名称的包变量(在程序中的任何位置都可用)。它们使用相同的数据,因此更改任何一个都会更改数据,并且两个版本都表明:

use v5.12;
use strict;
use warnings;

package help;
our $foo = 'bar';

package main;

say $foo;        # bar
say $help::foo;  # bar

$help::foo = 'baz';

say $foo;        # baz
say $help::foo;  # baz

这在package BLOCK 形式中不起作用,因为our 将词汇别名限制为该块,即使包版本仍然存在(并且不限于该块):

use v5.12;
use strict;
use warnings;

package help {
    our $foo = 'bar';
}

say $help::foo;  # bar

您可以指定完整的包规范,在这种情况下strict 不再关心:

use v5.12;
use strict;

package help {
    $help::HELPTEXT{ "topic" } = "Some help";
}

或者,use vars

use v5.12;
use strict;

package help {
    use vars qw(%HELPTEXT);
    $HELPTEXT{ "topic" } = "Some help";
}

您想在顶部添加一些 help 包装内容,然后在底部添加更多 help 包装内容。由于package 只是更改了默认包,您可以再次使用它。也就是说,您不仅可以使用一次,也不必将所有内容都放在第一个 help 块中:

use v5.12;
use strict;
use warnings;

package help {
    use vars qw(%HELPTEXT);

    sub help { $HELPTEXT{$_[0]} }
}

say "TOPIC: " . help::help("topic");

package help {
    use vars qw(%HELPTEXT);

    $HELPTEXT{ "topic" } = "Some help";
}

这不起作用,因为这些都是运行时语句。 Perl 编译所有这些,但它会按顺序执行这些语句。这意味着$HELPTEXT{ "topic" } = "Some help"; 在您尝试使用它之后直到最后才会运行。

BEGIN 解决了这个问题:

#!/usr/bin/perl
use v5.12;
use strict;
use warnings;

package help {
    use vars qw(%HELPTEXT);

    sub help { $HELPTEXT{$_[0]} }
}

say "TOPIC: " . help::help("topic");

BEGIN {
    package help {
        use vars qw(%HELPTEXT);

        $HELPTEXT{ "topic" } = "Some help";
    }
}

当 Perl 编译它时,它到达 BEGIN 块并编译它,然后立即运行它的块。 %HELPTEXT 的包变量已设置并可在程序中的任何位置使用。到顶部 package help {} 运行时,该哈希已经设置。加入一些消息可能更容易看到:

use v5.12;
use strict;
use warnings;

package help {
    use vars qw(%HELPTEXT);
    say "Setting up rest of help";

    sub help { $HELPTEXT{$_[0]} }
}

say "TOPIC: " . help::help("topic");

BEGIN {
    package help {
        use vars qw(%HELPTEXT);
        say "Setting up messages";
        $HELPTEXT{ "topic" } = "Some help";
    }
}

输出显示顺序。 BEGIN 先运行,然后是脚本的顶部,然后是中间:

Setting up messages
Setting up rest of help
TOPIC: Some help

但是,如果你有一个词法变量,就会发生不同的事情。包变量在那里,但词法版本在其范围内时将其屏蔽:

use v5.12;
use strict;
use warnings;

$help::foo = 'quux';
say "Before: $help::foo";

package help {
    my $foo = 'bar';
    say "Inside: $foo";
}

say "After: $help::foo";

输出显示:

之前:quux 内部:酒吧 之后:qux

但是,请记住词法掩码只在块内。如果你呼唤不在那个范围内的东西,

use v5.12;
use strict;
use warnings;

$help::foo = 'quux';
say "Before: $help::foo";

package help {
    my $foo = 'bar';
    say "Inside: $foo";
    main::show_foo();
}

say "After: $help::foo";

sub show_foo { say "show_foo: $help::foo" }

输出显示,即使在块内调用,show_foo 也会使用包版本,尽管子例程位于不同的包中:

Before: quux
Inside: bar
show_foo: quux
After: quux

因此,诀窍是要知道您的变量是词法(受范围影响)还是包。

【讨论】:

  • 完美。感谢您帮助我理解。
【解决方案2】:

INIT 确实有效(尽管BEGIN 更常见)。

#!/usr/bin/perl

use strict;
use warnings;

package help {
    our %HELPTEXT;
    $HELPTEXT{ "topic" } = "Some help\n";

    sub get_help( $ ) {
        print $HELPTEXT{ $_[0] };
    }
}

package main {
    sub main() {
        help::get_help( "topic" );
    }
}

main::main();

INIT {
    package help;
    our %HELPTEXT;
    $HELPTEXT{ "topic" } = "Some help\n";
}
$ perl a.pl
Some help

我认为你在第一个区块中还有my %HELPTEXT;


你可以使用 our 两次,而不是使用

use vars qw( %HELPTEXT );

无论如何,你所做的都是一团糟。

【讨论】:

  • “你做的事情一团糟。” -- 在这个沙盒中,是的。在几千行的脚本中,将帮助文本放在末尾而不是代码中间与在单个包中保留帮助、本地化等想法一样有用...感谢您让我重回正轨。
  • 之后您仍然可以拥有main::main()。或者文本可能在配置文件中。或在数据部分。这甚至没有进入同一个文件中的多个包
  • 您真的希望所有这些都是配置,以便普通人无需接触代码即可更新消息。你做了很多工作来重新发明人们几十年来一直在做的很多东西。调查人们为同一任务所做的其他事情的实践。
  • @briandfoy:你能相信我,我不会不顾一切地这样做吗?我已经准备好(并经过测试)从配置文件加载英语到 映射,但是脚本必须在没有这些配置文件的情况下工作,即我需要脚本本身中的英语后备文本。我知道 Locale::Maketext。实际上,这就是我的灵感来源。 (那个,还有 AmigaOS 目录文件......)一些目标对 Perl 的支持相当严重,我希望在不需要用户设置任何东西的情况下让它工作。
  • @DevSolar - 我不认识你,也不认识所有其他会读到这篇文章的人。如果您希望人们了解您在做什么以及您遵循什么模式,请将该上下文添加到您的问题中。
【解决方案3】:

我认为您对包命名空间没有任何问题。您似乎正确地使用它们。你真正的问题似乎是变量范围和执行顺序。

请记住,Perl 分为两个步骤,编译和执行。在第一遍编译时发生的操作包括合法化符号和创建基本流程。但是,直到运行时才填充这些现在合法的变量。在运行时到达它们之前,不会填充脚本末尾定义的变量。正如您所发现的那样,您可以使用 INITBEGIN 块“立即”实现它。

您有两个主要选择。第一个也是更优雅的 IMO 是有一个子例程,它填充并返回您放在最后但从任何地方调用的帮助文本。这使用了 state 关键字,这很棒,因为它让您可以在使用数据的地方声明数据。一个警告是它必须是一个标量。 state 声明仅在运行时执行一次(当您调用 sub 时)。

## the top

## thousands of lines of code

## the bottom

package help {
    sub get_help( $ ) {
        use feature qw/state/;
        state $HELPTEXT = { topic1 => "You need more help", topic2 => "RTFM", };
        print $HELPTEXT->{ $_[0] };
    }
}

## Or
sub help::get_help( $ ) { ...

另一种选择是在底部放置一个 BEGIN 块,用于填充数据。

## the top

package help {
    sub get_help( $ ) {
        our %HELPTEXT;
        print $HELPTEXT{ $_[0] };
    }
}

## thousands of lines of code

## the bottom

package help {
    BEGIN {
        our %HELPTEXT = ( topic1 => "You need more help", topic2 => "RTFM", );
    }
}


请记住,our 只是使作用域中的全局变量名合法化。它不会像 my 那样创建新变量。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-08-26
    • 2011-04-17
    • 2015-01-01
    • 2012-02-25
    • 1970-01-01
    • 2023-03-25
    • 1970-01-01
    相关资源
    最近更新 更多