如何在Perl中将数组用作对象属性?

7

我希望您能帮忙翻译一下有关Perl中数组的内容。

这是我拥有的构造函数。

BuildPacket.pm

     sub new {
            my $class = shift;    
            my $Packet = {
                _PacketName => shift,
                _Platform  => shift,
                _Version => shift,
                _IncludePath => [@_],
            };

            bless $Packet, $class;
            return $Packet;
        }

        sub SetPacketName {
            my ( $Packet, $PacketName ) = @_;
            $Packet->{_PacketName} = $PacketName if defined($PacketName);
            return $Packet->{_PacketName};
        }

       sub SetIncludePath {
            my ( $Packet, @IncludePath ) = @_;
            $Packet->{_IncludePath} = \@IncludePath;
        }

         sub GetPacketName {
            my( $Packet ) = @_;
            return $Packet->{_PacketName};
        }

        sub GetIncludePath {
           my( $Packet ) = @_;
           @{ $Packet->{_IncludePath} };
        }

这段代码根据 'gbacon' 的建议进行了修改,感谢他的意见。

我以动态的方式将相对路径推送到 'includeobjects' 数组中。包含路径从一个xml文件中读取,并被推送到该数组中。

# PacketInput.pm
if($element eq 'Include')
            {
             while( my( $key, $value ) = each( %attrs ))
                {
                if($key eq 'Path')
                    push(@includeobjects, $value);
                        }
                }

因此,includeobject 将是这样的:
@includeobjects = (
    "./input/myMockPacketName",
    "./input/myPacket/my3/*.txt",
    "./input/myPacket/in.html",
);

我正在使用这行代码来设置包含路径。
 $newPacket->SetIncludePath(@includeobjects);

同样在PacketInput.pm中,我有以下内容。
sub CreateStringPath
{
    my $packet = shift;
    print "printing packet in CreateStringPath".$packet."\n";
    my $append = "";
    my @arr = @{$packet->GetIncludePath()};
    foreach my $inc (@arr)
    {
        $append = $append + $inc;
        print "print append :".$append."\n";
    }
}

我有许多数据包,因此我正在循环遍历每个数据包。
# PacketCreation.pl
my @packets = PacketInput::GetPackets();
foreach my $packet (PacketInput::GetPackets())
{
    print "printing packet in loop packet".$packet."\n";
    PacketInput::CreateStringPath($packet);
    $packet->CreateTar($platform, $input);
    $packet->GetValidateOutputFile($platform);
}

get和set方法对PacketName有效。但是由于IncludePath是一个数组,我无法使其工作,我的意思是相对路径没有被打印出来。


2
哎呀,我的眼睛。你为什么不使用 use strict; use warnings; - Ether
1
@includeobjects 的赋值语句存在语法错误,这会导致程序无法运行(您可以通过将其更改为 @includeobjects = qw[ ./input/myMockPacketName ./input/myPacket/my3/*.txt ./input/myPacket/in.html ]; 来解决此问题)。请复制并粘贴代码,以便我们帮助您修复代码,而不是猜测可能的错误。 - Greg Bacon
感谢您回复我。我修改了代码,类似于我在includeobjects数组方面的做法,该数组是动态推送相对路径的。我想在CreateStringPath方法中添加路径。 - superstar
1
您添加的代码还包含语法错误:缺少push(@includeobjects,$value)周围的大括号。重要的是您复制并粘贴代码而不是重新输入。我修改了我的答案,使用与您相同的@includeobjects,输出看起来很好。您告诉我们您没有得到的输出,那么您得到了什么?这也是重要信息! - Greg Bacon
1
我想我找到了问题所在。PacketInput.pm 是否启用了 strict 严格模式?GetIncludePath 返回一个数组,但是 CreateStringPath 使用了 @{$packet->GetIncludePath()},这将其视为对数组的引用。我会修改我的答案,让 GetIncludePath 对其返回值更加智能化,使用 wantarray - Greg Bacon
显示剩余13条评论
3个回答

9

如果启用了 strict pragma,代码甚至无法编译:

Global symbol "@_IncludePath" requires explicit package name at Packet.pm line 15.
Global symbol "@_IncludePath" requires explicit package name at Packet.pm line 29.
Global symbol "@_IncludePath" requires explicit package name at Packet.pm line 30.
Global symbol "@_IncludePath" requires explicit package name at Packet.pm line 40.

不要在键中未引用的情况下使用 @,因为这会使解析器混淆。我建议完全删除它们以避免让人类读者困惑您的代码。

您似乎想从构造函数的参数中提取所有属性值,因此请继续使用shift剥离标量值,然后剩下的必须是包含路径。

我假设包含路径的组成部分将是简单的标量而不是引用;如果后者是真的,那么您需要进行深度副本以确保安全。

sub new {
  my $class = shift;

  my $Packet = {
    _PacketName  => shift,
    _Platform    => shift,
    _Version     => shift,
    _IncludePath => [ @_ ],
  };

  bless $Packet, $class;
}

请注意,由于Perl子例程的语义,无需将blessed对象存储在临时变量中,然后立即返回它:(参见链接)
如果未找到 return ,并且如果最后一个语句是表达式,则返回其值。下面的方法也将利用此功能。
给定上面的构造函数, GetIncludePath 变为:
sub GetIncludePath {
  my( $Packet ) = @_;
  my @path = @{ $Packet->{_IncludePath} };
  wantarray ? @path : \@path;
}

这里有几个要点。首先,请注意我们小心地返回包含路径的副本而不是对内部数组的直接引用。这样,用户可以修改从GetIncludePath返回的值,而不必担心破坏数据包的状态。 wantarray运算符允许子程序确定其调用的上下文并相应地作出响应。在列表上下文中,GetIncludePath将返回数组中的值列表。否则,它将返回对数组副本的引用。这样,客户端代码可以像这样调用它:
foreach my $path (@{ $packet->GetIncludePath }) { ... }

或者

foreach my $path ($packet->GetIncludePath) { ... }

SetIncludePath是一个函数。

sub SetIncludePath {
  my ( $Packet, @IncludePath ) = @_;
  $Packet->{_IncludePath} = \@IncludePath;
}

请注意,您可以在构造函数中使用类似的代码,而不是使用shift逐个删除一个参数。
您可以按如下方式使用上面定义的类:
#! /usr/bin/perl

use strict;
use warnings;

use Packet;

sub print_packet {
  my($p) = @_;
  print $p->GetPacketName, "\n",
        map("  - [$_]\n", $p->GetIncludePath),
        "\n";
}

my $p = Packet->new("MyName", "platform", "v1.0", qw/ foo bar baz /);
print_packet $p;

my @includeobjects = (
    "./input/myMockPacketName",
    "./input/myPacket/my3/*.txt",
    "./input/myPacket/in.html",
);
$p->SetIncludePath(@includeobjects);
print_packet $p;

print "In scalar context:\n";
foreach my $path (@{ $p->GetIncludePath }) {
  print $path, "\n";
}

输出:

MyName
  - [foo]
  - [bar]
  - [baz]
MyName - [./input/myMockPacketName] - [./input/myPacket/my3/*.txt] - [./input/myPacket/in.html]
在标量上下文中: ./input/myMockPacketName ./input/myPacket/my3/*.txt ./input/myPacket/in.html

我尝试实现更改,并尝试从GetIncludePath打印数组@{ $Packet->{_IncludePath} },但它没有显示任何内容。 - superstar
1
@superstar 数组最初将为空,因为构造函数中的 my @includeobjects = () 创建了一个新的空数组。类似 $packet->SetIncludePath(qw/ foo bar baz /) 的代码将给它一个有趣的值。你在测试中这样做了吗? - Greg Bacon
@brian d foyï¼ڑه½“وˆ‘调用$packet->GetIncludePathو—¶ï¼Œوˆ‘ه؛”该能ه¤ںèژ·هڈ–ن»ژSetIncludePath设置çڑ„و•°ç»„ه€¼...ه¯¹هگ—ï¼ں - superstar
1
@superstar,请看一下更新答案中的工作示例,它可以存储和检索包含路径。你的代码有什么不同吗? - Greg Bacon
@superstar 你说的“不工作”,是指它没有在第二个路径中展开通配符,还是其他什么情况?请编辑你的问题,展示你如何使用这个类以及你想要看到的输出,这样我们才能给你更有帮助的答案。 - Greg Bacon
显示剩余3条评论

4

另一种减少打字的方法是使用 Moose。

package Packet;
use Moose::Policy 'Moose::Policy::JavaAccessors';
use Moose;

has 'PacketName' => (
    is       => 'rw',
    isa      => 'Str',
    required => 1,
);

has 'Platform' => (
    is       => 'rw',
    isa      => 'Str',
    required => 1,
);

has 'Version' => (
    is       => 'rw',
    isa      => 'Int',
    required => 1,
);

has 'IncludePath' => (
    is       => 'ro',
    isa      => 'ArrayRef[Str]',
    default  => sub {[]},
    traits => [ 'Array' ],
    handles => {
        getIncludePath       => 'elements',
        getIncludePathMember => 'get',
        setIncludePathMember => 'set',
    },
);

__PACKAGE__->meta->make_immutable;
no Moose;
1;

请查看Moose::Manual::Unsweetened,了解Moose如何节省时间的另一个示例。
如果您坚定地希望学习经典的Perl OOP,请阅读以下perldoc文章:perlbootperltootperlfreftutperldsc
关于经典的Perl OO,有一本很好的书是Damian Conway's Object Oriented Perl。它将让您感受到Perl对象的可能性。

1
如果完全不能使用Moose,Class::Accessor是一个合理的替代选择。但是Moose非常棒。 - Ether
感谢提供文档和推荐的书籍。 - superstar
2
没有任何对Moose的偏见,但我不会说它的好处之一是减少打字。我认为它太啰嗦了,期待有一天它可以从类的文本描述中生成Moose代码。 - brian d foy
@brian,那是一个有趣的想法。根据我的经验,Moose 可能比较冗长,但它比编写经典的 Perl OOP 类更为紧凑。主要的节省来自于类型、强制转换和方法生成。但我欢迎一种更简洁、标点化的语法来声明对象。 - daotoad

3

一旦你理解了@gbacon的回答,你可以通过使用Class::Accessor::Fast来节省输入量:

#!/usr/bin/perl

package My::Class;
use strict; use warnings;
use base 'Class::Accessor::Fast';

__PACKAGE__->follow_best_practice;
__PACKAGE__->mk_accessors( qw(
    IncludePath
    PacketName
    Platform
    Version
));

use overload '""' => 'to_string';

sub to_string {
    my $self = shift;
    sprintf(
        "%s [ %s:%s ]: %s",
        $self->get_PacketName,
        $self->get_Platform,
        $self->get_Version,
        join(':', @{ $self->get_IncludePath })
    );
}

my $obj = My::Class->new({
        PacketName => 'dummy', Platform => 'Linux'
});
$obj->set_IncludePath([ qw( /home/include /opt/include )]);
$obj->set_Version( '1.05b' );
print "$obj\n";

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接