我该如何在Perl中动态创建软件包?

4

我有一个情况,如果我能够动态创建包(package),那将会很有帮助。以下代码不能运行,但是说明了我的意图。

#!/usr/bin/perl
use strict;
use warnings;

use Data::Dumper;

my $basename    = "Xma";
my $len     = 7;
my $newClass    = sprintf("%s%d", $basename, $len);

printf("New class is %s\n", $newClass);

package $newClass {
    # Early modules.
    our @enum; BEGIN { @enum = qw( I_VAL I_SLOPE ); }

    use parent qw(Exporter);
    use enum::fields @enum;
    our @EXPORT = (@enum);

    our $classLen = $len;
    our $classBasename = $basename;

    sub new {
    my $invocant = shift;
    my $self = bless ([], ref $invocant || $invocant);
    return($self);
    }
}

1;

我在我的新类中添加了“额外”的内容,以说明这个新类是一个复杂的类,并且将出现在继承链中。
我知道我可以使用以下代码创建简单的包:
*{ "${class}::new" } = sub { return bless { }, $class };

但是我的新类/包将会非常大,因此我希望有些更容易维护的东西。
你们当中好奇的人可能会问:“为什么呢?”好奇心是一个重要因素。我需要在加载/编译时尽可能处理多的内容,并尝试避免几百万次运行时查找。我需要经常更改$basename和$len,但仅在编译时更改。而最重要的原因是好奇心。

2
eval($code) 你可以使用模板来生成 $code - ikegami
1
或者使用 require($package_file)(或 require Package::Name;),如果您在运行时构建包(并将其转储到文件 $package_file)。 - zdim
1
如果你想要一个漂亮而完整但是比较重的API:Moose::Meta::Class->create - daxim
1
eval会执行您的代码,所以它肯定会做您想要的事情...但请记住,如果您的代码有错,您将面临重大的调试之旅。如果您有一个可用的模板,并仅更改一些参数并包括已知的工作方法,那么您将最小化使用动态代码方法时可能出现的问题。这种方法也可以用于代码加密-您可以读入一个加密文件,解密它并对其进行评估。(前提是您能够保护解密过程)。 - Rob Lambden
所以,我希望有某种魔法,而你们都提供了它。1)生成动态代码,2)决定是否将其保留在磁盘上,3)执行它,所有这些都是为了避免干净但缓慢(在这种情况下)的Moose。第一个编写它的人得到积分。 :) - Erik Bennett
1个回答

4

我很遗憾地说,可变代码(?!)是相当糟糕的反模式,并且对于我所能想到的大多数用例来说都是一个巨大的红旗,除了显而易见的代码生成工具或转换器。

更好的方法是只创建一个类或类层次结构,然后使用非元编程处理其中的任何差异。

事实上,这正是面向对象编程的思想。您创建的对象是从“模板”即您的类中实例化或复制出来的:

  package MyClass {
       sub new {
           my ( $class, %args ) = @_;
           bless { name => sprintf('%s%d', $args{basename}, $args{len}) }, $class;
       }
  }

  my $obj = MyClass->new( basename => 'Xma', len => 7 );
  printf "New class is %s\n", $obj->{ name };

如果你真的需要动态创建包/类,我认为最明智的做法是动态创建继承自一个或多个基类的类。
package MyClass {  
    sub new {
        my ( $class, %args ) = @_;
        bless \%args, $class;
    }

    sub foo {
        my $self = shift;
        return "instance of $self";
    }
}

sub build_class {
    my ( %args ) = @_;

    my $classname = sprintf( '%s::%s%d', $args{ parent }, $args{ basename }, $args{ len } );
    @{ "${classname}::ISA" } = ( $args{ parent } );

    return $classname;
}

my $classname = build_class( parent => 'MyClass', basename => 'Xma', len => 7 );
my $obj = $classname->new();

printf "New class is %s\n", ref $obj;  # is MyClass::Xma7
printf "Object is an %s\n", $obj->foo; # a MyClass::Xma7=HASH(...) object instance

如果您想为面向对象编程增加更多的元编程功能,请看一下 Moose::Meta::ClassClass::MOP

use Class::MOP;

my $basename = 'Xma';
my $len = 7;
my $classname = "MyClass::${basename}${len}";

Class::MOP::Class->create(
    $classname,
    attributes => [
        Class::MOP::Attribute->new( 'foo', is => 'rw', isa => 'Str' )
    ],
    methods => {}
);

$classname->new( foo => 'bar' );

另一方面,如果您真的需要在包中具有可变代码和/或您不需要/不想在每次程序运行时将所有包加载到内存中,我会建议您使用一组文件模板和一些模板语言来将您的新包输出到文件中(即“ MyClass / Xma7.pm”,“ MyClass / Xma8.pm”),然后使用use MyClasses :: Xma7;像任何其他包一样加载它们。这样做的好处是,考虑到元编程的错误倾向性,您的代码将更容易进行调试和测试。

一般来说,这是正确的。这个特别奇怪的情况涉及数百万次数学变换,并将它们相互比较。如果运行时速度不是问题,我会使用一个简洁、干净的 compute("Xma",[0..1000]) 这样的函数。分析表明,在我使用的硬件上,哈希查找成为了一个问题。但主要还是出于好奇心。如果/当速度变得非常重要时,我可能会切换到 C。如果那还不够快,我就放弃了,因为我写 ForTran 的时间还不够长,我还没有忘记它留下的伤疤。 - Erik Bennett
有人曾经聪明地说过:“既理解又欣赏英特尔CPU是不可能的。”,所以汇编语言就被排除了。 - Erik Bennett
我给@ojosilva的帖子点了赞,因为它是完全正确的,而我的评论可能没有反映出这一点。它增加了价值。 - Erik Bennett

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