【问题标题】:How can I redefine Perl class methods?如何重新定义 Perl 类方法?
【发布时间】:2009-03-11 16:03:59
【问题描述】:

"How can I monkey-patch an instance method in Perl?" 的问题让我开始思考。我可以动态地重新定义 Perl 方法吗?假设我有这样的课程:

package MyClass;
sub new {
  my $class = shift;
  my $val = shift;
  my $self = { val=> $val};

  bless($self, $class);
  return $self;
};

sub get_val {
  my $self = shift;
  return $self->{val}+10;
}
1;

假设两个数字相加真的很昂贵。

我想修改该类,以便仅在我第一次调用该对象的方法时才计算 $val+10。对该方法的后续调用将返回一个缓存值。

我可以轻松地修改方法以包含缓存,但是:

  • 我有一堆这样的方法。
  • 我不想弄脏这个方法。

我真正想做的是指定一个方法列表,我知道这些方法对于给定实例总是返回相同的值。然后我想获取这个列表并将其传递给一个函数,以便为这些方法添加缓存支持

有没有有效的方法来做到这一点?

跟进。下面的代码有效,但是因为 use strict 不允许按字符串引用,所以我不是 100% 想要的。

sub myfn {
  printf("computing\n");
  return 10;
}
sub cache_fn {
  my $fnref = shift;

  my $orig = $fnref;
  my $cacheval;

  return sub {
    if (defined($cacheval)) { return $cacheval; }
    $cacheval = &$orig();
    return $cacheval;
  }
}

*{myfn} = cache_fn(\&myfn);

如何修改才能做到这一点?:

cache_fn(&myfn);

【问题讨论】:

  • { 没有警告“重新定义”; *myfn = cache_fn(\&myfn); } - 这消除了模棱两可和重新定义的警告。请参阅我关于 cache_fn(&myfn); 的新答案
  • 您可能想研究记忆化(有一些很好的 CPAN 模块),或者使用 Moose 触发器在属性中缓存昂贵的计算,并根据需要使用触发器重新计算它们。

标签: perl


【解决方案1】:

您可以像这样从另一个包中覆盖 get_val 之类的方法:

*{MyClass::get_val} = sub { return $some_cached_value };

如果你有一个方法名列表,你可以这样做:

my @methods = qw/ foo bar get_val /;
foreach my $meth ( @methods ) {
    my $method_name = 'MyClass::' . $meth;
    no strict 'refs';
    *{$method_name} = sub { return $some_cached_value };
}

这是你想象的吗?

【讨论】:

  • 使用严格;不允许我按字符串引用函数。有没有办法安全地解决这个问题?
  • 你的意思是不像我的第二个例子那样使用“no strict 'refs'”?据我所知没有。
【解决方案2】:

我在Mastering Perl 的“动态子例程”一章中写了一些您可能想做的不同事情。根据您正在做的事情,您可能想要包装子例程,或重新定义它,或子类,或各种其他的东西。

Perl 是一种动态语言,因此您可以使用很多黑魔法。明智地使用它是诀窍。

【讨论】:

  • 感谢您的链接。我在网上阅读了几页,他们已经得到了我需要回答这个问题的内容。看起来我的购物清单上有另一本书。
【解决方案3】:

我从未尝试过使用方法,但Memoize 可能是您正在寻找的。但请务必阅读caveats

【讨论】:

  • 它适用于方法,但如果您想在所有实例中缓存,您可能必须使用 NORMALIZER 选项。如果您想缓存每个实例,那么将值粘贴在对象中可能更容易,例如返回 $self->{foo} 如果存在 $self->{foo}
  • 如果您正在编写课程,那就更容易了。这个问题暗示他正在尝试动态修改他没有编写的类。
【解决方案4】:

在您的情况下没有用,但如果您的类是用Moose 编写的,那么您可以使用它的Class::MOP 基础动态覆盖方法......

{
    package MyClass;
    use Moose;

    has 'val' => ( is => 'rw' );

    sub get_val {
        my $self = shift;
        return $self->val + 10;
    }

}

my $A = MyClass->new( val => 100 );
say 'A: before: ', $A->get_val;

$A->meta->remove_method( 'get_val' );
$A->meta->add_method( 'get_val', sub { $_[0]->val } );

say 'A: after: ', $A->get_val;

my $B = MyClass->new( val => 100 );
say 'B: after: ', $B->get_val;

# gives u...
# => A: before: 110
# => A: after: 100
# => B: after: 100

【讨论】:

    【解决方案5】:

    如何修改才能做到这一点?:

    cache_fn(\&myfn);

    根据你当前的例子,你可以做这样的事情......

    sub cache_fn2 {
        my $fn_name = shift;
        no strict 'refs';
        no warnings 'redefine';
    
        my $cache_value = &{ $fn_name };
        *{ $fn_name } = sub { $cache_value };
    }
    
    cache_fn2( 'myfn' );
    

    但是看着这个例子,我不禁想到你可以用 Memoize 代替?

    【讨论】:

      猜你喜欢
      • 2015-01-28
      • 2010-09-13
      • 2021-09-06
      • 2012-04-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-10-13
      • 2020-03-05
      相关资源
      最近更新 更多