从perl中的eval-ed字符串动态创建subs

时间:2013-01-09 23:12:10

标签: perl eval perl-data-structures

我需要将数组结构中的数据结构转换为树状结构。在开始处理数据之前,我知道树的深度,但我希望保持灵活性,以便重新使用代码。

因此我着眼于动态生成subref(从基于Moose的模块中)从阵列到树的想法。像这样(以简化的方式):

use Data::Dump qw/dump/;

sub create_tree_builder {
     my $depth = shift;
     return eval join '', 'sub { $_[0]->{$_[', 
                           join(']}->{$_[', (1..$depth)),
                          ']} = $_[',  $depth + 1 , '] }'; 
}


my $s = create_tree_builder(5);
my $tree = {};

$s->($tree, qw/one two three four five/, 'a value');

print dump $tree;

# prints
#  {
#     one => { two => { three => { four => { five => "a value" } } } },
#  }

这为我打开了世界,我发现这个eval过程很有用 - 在一个参数化生成的字符串中到处都是一个函数(显然,这是一个寻找问题的解决方案)。

然而,几乎是真实的感觉有点太好了。

反对这种做法的任何建议?或建议改进?

我可以清楚地看到,评估任意输入可能不是最安全的,但还有什么呢?

跟进

感谢所有答案。我使用了amon的代码并进行了一些基准测试,如下所示:

use Benchmark qw(:all) ;

$\ = "\n";

sub create_tree_builder {
 my $depth = shift;
 return eval join '', 'sub { $_[0]->{$_[', 
               join(']}->{$_[', (1..$depth)),
              ']} = $_[',  $depth + 1 , '] }'; 
}


my $s = create_tree_builder(5);

$t = sub {
$_[0] //= {};

    my ($tree, @keys) = @_;
    my $value = pop @keys;

    $tree = $tree->{shift @keys} //= {} while @keys > 1;
    $tree->{$keys[0]} = $value;
};


cmpthese(900000, {
        'eval'  => sub { $s->($tree, qw/one two three four five/, 'a value') },
    'build' => sub { $t->($tree, qw/one two three four five/, 'a value') },

});

结果显然有利于建造树,而不是评估工厂:

            Rate build  eval
build  326087/s    --  -79%
eval  1525424/s  368%    -- 

我承认我之前可以做到这一点。我将尝试使用更多随机树(而不是一遍又一遍地分配相同的元素),但我认为没有理由说结果应该是不同的。

非常感谢你的帮助。

4 个答案:

答案 0 :(得分:5)

编写通用子例程来构建这样的嵌套哈希非常容易。它比编写一个能为特定数量的哈希级别生成这样一个子程序的工厂简单得多。

use strict;
use warnings;

sub tree_assign {

  # Create an empty tree if one was not given, using an alias to the original argument
  $_[0] //= {};

  my ($tree, @keys) = @_;
  my $value = pop @keys;

  $tree = $tree->{shift @keys} //= {} while @keys > 1;
  $tree->{$keys[0]} = $value;
}

tree_assign(my $tree, qw/one two three four five/, 'a value');

use Data::Dump;
dd $tree;

<强>输出

{
  one => { two => { three => { four => { five => "a value" } } } },
}

答案 1 :(得分:3)

为什么这可能是一个坏主意

  1. 可维护性。

    eval'd的代码必须首先在程序员的头脑中进行评估 - 并不总是一件容易的事。从本质上讲,评估是模糊处理。

  2. 速度

    在正常执行恢复之前,

    eval重新运行perl解析器和编译器。但是,通过推迟子程序的编译直到需要它们,可以使用相同的技术来获得启动时间。这这种情况。

  3. 有多种方法可以做到。

    我喜欢匿名子程序,但您不必使用eval来构造它们。无论如何它们都是封闭的。像

    这样的东西
    ...;
    return sub {
      my ($tree, $keys, $value) = @_;
      $#$keys >= $depth or die "need moar keys";
      $tree = $tree->{$keys->[$_]} for 0 .. $depth - 1;
      $tree->{$keys->[$depth]} = $value;
    };
    

    $s->($tree, [qw(one two three four five)], "a value");
    

    会做一些令人惊讶的类似事情。 (实际上,使用$depth现在看起来像设计错误;完整路径已经由键指定。因此,创建一个正常的,命名的子例程可能是最好的。)

答案 2 :(得分:2)

根据他们的评论了解OP做得更好,并且对Borodin的代码进行了改进,我建议改变界面。我不是编写一个子程序来在树中深入应用值,而是编写一个子程序来创建一个空子树,然后处理该子树。这使您可以在子树上高效工作,而无需在每次操作中遍历树。

package Root;

use Mouse;

has root =>
  is    => 'ro',
  isa   => 'HashRef',
  default => sub { {} };

sub init_subtree {
    my $self = shift;
    my $tree = $self->root;

    for my $key (@_) {
        $tree = $tree->{$key} //= {};
    }

    return $tree;
}

my $root = Root->new;
my $subtree = $root->init_subtree(qw/one two three four/);

# Now you can quickly work with the subtree without having
# to walk down every time.  This loop's performance is only
# dependent on the number of keys you're adding, rather than
# the number of keys TIMES the depth of the subtree.
my $val = 0;
for my $key ("a".."c") {
    $subtree->{$key} = $val++;
}

use Data::Dump;
dd $root;

答案 3 :(得分:2)

Data::Diver是你的朋友:

use Data::Diver 'DiveVal', 'DiveRef';
my $tree = {};

DiveVal( $tree, qw/one two three four five/ ) = 'a value';

# or if you hate lvalue subroutines:
${ DiveRef( $tree, qw/one two three four five/ ) } = 'a value';

use Data::Dump 'dump';
print dump $tree;