理解 Perl 包命名空间
Understanding Perl packages namespacing
考虑这个脚本(从我拥有的更大的脚本中提炼出来):
#!/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" makes a lexical alias to a package (i.e. global) variable of the same name in the current package for use within the current lexical scope.
但是,这样还是报错:Use of uninitialized value in print
。即使我将初始化包装在 INIT {}
块中。
我不仅想为此找到解决方案,而且了解为什么这不像我预期的那样工作。通过猜测编码感觉很糟糕...
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 );
不管怎么说,你做的都是一坨屎
这里有一些事情需要理解,我在 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";
不会 运行 直到最后,在您尝试使用它之后。
A 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
内部:酒吧
之后:quux
但是,请记住词法掩码只在块内。如果你呼唤不在那个范围内的东西,
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
因此,诀窍是了解您的变量是词法变量(受作用域影响)还是包变量。
我认为您对包命名空间没有任何问题。您似乎正确使用了它们。你真正的问题似乎是可变范围和执行顺序。
请记住,Perl 分为两步,编译和执行。在第一遍编译时发生的动作包括合法化符号和创建基本流程。然而 none 这些现在合法的变量在运行时才会被填充。在脚本末尾定义的变量在运行时到达它们之前不会被填充。您可以使用 INIT
或 BEGIN
积木“立即”实现它。
您有两个主要选择。第一个也是更优雅的 IMO 是有一个子例程,它既填充又 returns 您放在最后但可以从任何地方调用的帮助文本。这使用了很棒的 state
关键字,因为它让您可以在使用数据的地方声明数据。一个警告是它必须是标量。 state
声明仅在运行时执行一次(当您调用子程序时)。
## 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
那样创建新变量。
考虑这个脚本(从我拥有的更大的脚本中提炼出来):
#!/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" makes a lexical alias to a package (i.e. global) variable of the same name in the current package for use within the current lexical scope.
但是,这样还是报错:Use of uninitialized value in print
。即使我将初始化包装在 INIT {}
块中。
我不仅想为此找到解决方案,而且了解为什么这不像我预期的那样工作。通过猜测编码感觉很糟糕...
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 );
不管怎么说,你做的都是一坨屎
这里有一些事情需要理解,我在 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";
不会 运行 直到最后,在您尝试使用它之后。
A 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 内部:酒吧 之后:quux
但是,请记住词法掩码只在块内。如果你呼唤不在那个范围内的东西,
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
因此,诀窍是了解您的变量是词法变量(受作用域影响)还是包变量。
我认为您对包命名空间没有任何问题。您似乎正确使用了它们。你真正的问题似乎是可变范围和执行顺序。
请记住,Perl 分为两步,编译和执行。在第一遍编译时发生的动作包括合法化符号和创建基本流程。然而 none 这些现在合法的变量在运行时才会被填充。在脚本末尾定义的变量在运行时到达它们之前不会被填充。您可以使用 INIT
或 BEGIN
积木“立即”实现它。
您有两个主要选择。第一个也是更优雅的 IMO 是有一个子例程,它既填充又 returns 您放在最后但可以从任何地方调用的帮助文本。这使用了很棒的 state
关键字,因为它让您可以在使用数据的地方声明数据。一个警告是它必须是标量。 state
声明仅在运行时执行一次(当您调用子程序时)。
## 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
那样创建新变量。