Perl Exporter 在子类中使用来自@EXPORT_OK 的变量

Perl Exporter use variables from @EXPORT_OK in a subclass

我找了最后几个小时,我被难住了。

我确实看到了 How can I share variables between a base class and subclass in Perl? 但它没有回答我的问题

我正在尝试编写一个实用程序模块(此时用于测试目的)以用于内部开发的脚本。但我似乎很难让出口商实际出口我需要的东西。

我有以下内容:

lib/Internal.pm:

package Internal;

use     5.008_008;
use     strict;
use     warnings;
use     Exporter;

our $VERSION = 1;
our @EXPORT_OK = qw($EMPTY $NOW);
our %EXPORT_TAGS = ( ':all' => \@EXPORT_OK );

our $EMPTY = q{};
our $NOW = time;

1;

lib/Internal/Utility.pm:

package Internal::Utility;

use     5.008_008;
use     strict;
use     warnings;
use     FindBin::libs;
use     parent qw(Internal);
use     Data::Dumper;

our $VERSION = 1;
our @EXPORT_OK = qw(run);
our %EXPORT_TAGS = ( ':all' => \@EXPORT_OK );

sub run
{
    print Dumper $NOW;
    print Dumper $EMPTY;
    exit;
}

1;

bin/test.pl:

#!/usr/bin/env perl

use     5.008_008;
use     strict;
use     warnings;
use     FindBin::libs;
use     Internal::Utility qw(:all);

run();
exit;

当我 运行 test.pl 时,出现以下错误:

Global symbol "$NOW" requires explicit package name at lib/Internal/Utility.pm line 8.
Global symbol "$EMPTY" requires explicit package name at lib/Internal/Utility.pm line 9.
Compilation failed in require at ./test.pl line 7.
BEGIN failed--compilation aborted at ./test.pl line 7.

为什么 Internal::Utility 中的 use parent 没有从 Internal.pm 导入变量?有没有办法告诉 use parent 显式导入一些变量?

在 Perl 中,继承仅影响方法调用 的解析方式。其他一切——变量、子程序等——保持不变。

这意味着您应该将这些符号导入您的派生 class,就像您从任何其他模块导入一样:

package Internal::Utility;
...
use Internal ':all';
use parent qw(Internal);
...

Perl 使用包作为其唯一的命名空间机制,但包也可以用作 classes。这可能有点令人困惑。作为一般建议,您永远不应该混合这两者:一个包应该提供一个面向对象的接口或一个基于导出器的过程接口。将两者混合往往会导致误导性设计。

您正试图通过 use parent 在一个模块中从一个模块“继承”。 parent

Establish an ISA relationship with base classes at compile time

(我的重点)。在派生的基础中设置使用基础 class 是一个 pragma,InternalInternal::Utility 不是 class 真的(它们不 bless).见末尾注释。

将它们写成 classes。 (这样你就不需要考虑导出符号了。)这也会给你更多的能力和控制。


至于Exporter,你不得不

use Exporter 'import';

以便子 import 可用于模块。

名为“import”的子程序用于将您在 @EXPORT_OK 等中指定的符号推送给调用者,因此必须存在于模块中。或者,非常好,一个模块可以从 Exporter“借用”它,而不是实现它自己的。但是 Exporter 默认情况下不导出它。


带有 classes 的示例,展示了几种使常量可用的方法,以及其他一些位。当您选择一种方式(并为其他方式删除代码)并删除打印时,它会变得更加简单;最长的 Internal::Utility class 缩小到几行。

基础classInternal.pm

package Internal;

use warnings;
use strict;

# Set up constants for derived classes to use, for demo 
# BOTH via class data and method, and via an attribute

our $NOW = time;
sub get_NOW { return $NOW }

sub new { 
    my ($class, @pars) = @_;
    # Set up object's data ("attributes"), using @pars
    my $self = { _now => $NOW };  # ...
    # Make $self an object and this package a class
    return bless $self, $class;
}

sub other_methods { }

1;

派生class(subclass),Internal/Utility.pm

package Internal::Utility;

use warnings;
use strict;
use feature 'say';

use parent 'Internal';  # now have everything from 'Internal' class

# Retrieve constant from parent, if you *must*
# It is much better to use a method or an attribute 
use mro; 
my $NOW = do {
    my $Parent = (__PACKAGE__)->mro::get_linear_isa->[1];
    my $now = $Parent . '::NOW';
    no strict 'refs';
    $$now;
};  
my $Now = $Internal::NOW;  # or use hard-coded parent name

# Constructor is also inherited (can change or override it altogether)

sub print_start_time {
    my $self = $_[0];
    say "\tIn ", __PACKAGE__, ". Start time:";
    say "\tObject's method (inherited): ", $self->get_NOW;
    say "\tObject's data (inherited):   ", $self->{_now};
    say "\tDirectly, as class method:   ", Internal->get_NOW;
    say "\tClass variable, from parent: $NOW";
    say "\tOther class variable:        $Now";
};
    
1;

Drivermain.pl

use warnings;
use strict;
use feature 'say';

use Internal::Utility;

my $obj = Internal::Utility->new();

say "Created ", ref $obj, " object";

$obj->print_start_time();

say "class constant:   $Internal::NOW";
say "as object method: ", $obj->get_NOW;
say "as class method:  ", Internal->get_NOW;

这会打印

Created Internal::Utility object
        In Internal::Utility. Start time:
        Object's data (inherited):   1513229709
        Object's method (inherited): 1513229709
        Directly, as class method:   1513229709
        Class variable, from parent: 1513229709
        Other class variable:        1513229709
class constant:   1513229709
as object method: 1513229709
as class method:  1513229709

最好简单地使用parent的属性或它的方法,它们都被它的子class继承。如果你使用一个属性,那么在每个使用它的子class中为那些子classes.

的用户提供一个访问器

这两者之间存在差异,选择哪个取决于设计细节。作为一个基本的方法,我会去。请记住,parent 的 class 变量可由其派生的 classes.

的任何用户访问

一旦开始使用 Perl 中的 objects,我建议您熟悉它的本机系统。然后在某个时候应该研究 Moose and/or 它的 light-weight 兄弟姐妹 Moo

关于 class 与 object(实例)方法的说明

“class 方法”与任何一个 object 都不相关,而是与整个 class 相关(例如,return 一个数字到那时创建了 objects);因此它不需要 object 的数据。当在 class 名称上调用方法时,它是作为第一个参数传递给它的 class 名称,这就是使它成为“class”方法的原因。

“object(实例)方法”旨在为特定的 object 工作,当在 object 上调用时,它是 object(对用于存储传递给它的 class) 数据的 Perl 数据结构的引用。

也许令人困惑的是,我们 可以 在 object 上调用一个旨在作为 class 方法的方法,没有错误(除非该方法使用class 现在没有的名称);不应该这样做,因为它混淆了目的。使用 class 名称调用 instance-meant 方法可能会很糟糕,因为它很可能确实期望传递 object,而它会得到一个带有 [=183= 的纯字符串] 名字.

这个 apparent 的歧义在这个简单的演示中可能更令人困惑,因为 get_NOW 在 class 和 object 上的调用同样好。它的意图显然是 class 方法。但它也可以与 object 一起使用,在继承的 classes 中方法没有 parent 的名称。

有关软件包和 classes

的说明

一个package

Declares the BLOCK or the rest of the compilation unit as being in the given namespace.

然后你在里面定义subs,并提供一种方法将那些符号(名称)导出给调用者,以便他们可以方便地使用它们。你有一个函数库,就是这样。这通常被称为“模块”,这就是我在上面使用该术语的方式。查看更多 in perlmod.

A class 是 code/namespace(因为缺少更好的名词)可以 实例化,这意味着您可以创建它的 实例 object。这个 object“知道”它与什么 class 关联,因此它可以使用那个 class 中提供的子程序,使它们成为 方法 。系统为class提供了一种设置数据(属性)的方法,每个object都有自己的副本。参见 perlootut

在 Perl 中,class 被定义为()一个包。是什么让它成为 class,以便可以为它实例化一个 object,是引用是 bless-ed into it. Normally there is a subroutine in that package in which a reference (typically to a hash) is bless-ed "into" the package (bless REF, PACKAGENAME) and returned. The perlobj 说(我的重点)

Objects are merely Perl data structures (hashes, arrays, scalars, filehandles, etc.) that have been explicitly associated with a particular class.

objectbless-ed 和 returned hashref)有一个 OBJECT 标志(和 STASH),在 hashref 内部具有的其他属性之上。因此,当它被取消引用时,解释器可以判断应该在该包中寻找 sub;因此这是 class.

的 object

因此本题中的模块不是classes(通常意义上的)。那么“继承”呢?object-oriented 任期?由于 Perl 的 OO 系统是极简主义的,所以一些功能适用于通用模块和 classes,术语可能在这里或那里变得模糊。 “继承”一词就是一个这样的例子,因为它可以用于更通用的含义,其中一个单元拾取另一个单元中设置的东西。

我仍然建议不要在不是 class 的模块上使用 parent pragma,即使 it works.


几个语义点,一个在评论中提出

  • 严格来说,一个“class”是一个包--句号。无需在其中包含 bless-ing 子例程。 “object”只是一个包的实例(bless-ed 到其中)。在某种程度上,在 Perl 中没有特殊的“class”和“object”东西(即使使用了这些词)——有包和它们的实例(通过 [=17 与包相关联) =].)

    所有这些都在文中清楚准确地陈述和讨论,但如果有评论,我想澄清一下文中内容。

    采取上述 dead-serious-formally 将 Perl 的有意极简主义 OO-model 推得太远,并且超出预期,我相信。如果一个包不产生 object(没有 bless-ing sub),那么将包称为 class 的目的是什么?我是否会尽可能地在我的代码中选择任何旧参考并 bless 一个包含它的包?不。 (如果有人会说 'yes' 请提供一个来自野外的例子,一个 non-trivial 没有 bless 的包的例子有意义地充当 class。)

    A“class”(包)通常被编写为用于涉及 bless 的特定引用,它具有特定的属性等。因此该引用及其 bless-ing 通常属于该包,实际上 this 是使该包成为 class.

    的原因

    嗯-- 我在这里注意到,可以以更灵活的方式使用bless。例如,在 (main::) 程序中说 bless $a_reference;,然后 $a_reference 与包 main:: 相关联,从而使该包成为“class” ”或者在 Effective Perler article.

    中查看一个相当复杂的示例
  • 所指对象是 bless-ed,真的。看,例如好的旧 Bless My Referents

您不能只是 use Exporter 并期望它起作用。在您的 Internal 包中,您需要使用

inherit
use Exporter;
our @ISA = 'Exporter';

use parent 'Exporter';

或者,对于更新版本的模块,您可以简单地导入它的import子例程

use Exporter 'import';

如果您需要一些特殊的东西,您可以编写自己的import,但在大多数典型情况下不需要