首页 > 解决方案 > 覆盖在模块中定义但在其运行时阶段使用之前的函数?

问题描述

让我们来看一些非常简单的事情,

# Foo.pm
package Foo {
  my $baz = bar();
  sub bar { 42 };  ## Overwrite this
  print $baz;      ## Before this is executed
}

无论如何,我是否可以test.pl运行代码来更改$baz设置并导致Foo.pm在屏幕上打印其他内容?

# maybe something here.
use Foo;
# maybe something here

编译器阶段是否可以强制打印以上内容7

标签: perlcompilation

解决方案


需要 hack,因为require(因此use)在返回之前编译和执行模块。

也一样evaleval不能在不执行代码的情况下用于编译代码。

我发现的侵入性最小的解决方案是覆盖DB::postponed. 在评估编译的所需文件之前调用它。不幸的是,它只在调试时调用(perl -d)。

另一种解决方案是读取文件,修改它并评估修改后的文件,有点像下面这样:

use File::Slurper qw( read_binary );

eval(read_binary("Foo.pm") . <<'__EOS__')  or die $@;
package Foo {
   no warnings qw( redefine );
   sub bar { 7 }
}
__EOS__

上面没有正确设置%INC,它弄乱了警告使用的文件名等,它不调用DB::postponed等等。以下是一个更强大的解决方案:

use IO::Unread  qw( unread );
use Path::Class qw( dir );

BEGIN {     
   my $preamble = '
      UNITCHECK {
         no warnings qw( redefine );
         *Foo::bar = sub { 7 };
      }
   ';    

   my @libs = @INC;
   unshift @INC, sub {
      my (undef, $fn) = @_;
      return undef if $_[1] ne 'Foo.pm';

      for my $qfn (map dir($_)->file($fn), @libs) {
         open(my $fh, '<', $qfn)
            or do {
               next if $!{ENOENT};
               die $!;
            };

         unread $fh, "$preamble\n#line 1 $qfn\n";
         return $fh;
      }

      return undef;
   };
}

use Foo;

我使用UNITCHECK了(在编译之后但在执行之前调用),因为我在覆盖之前(使用unread)而不是读入整个文件并附加新定义。如果您想使用该方法,您可以使用返回的文件句柄

open(my $fh_for_perl, '<', \$modified_code);
return $fh_for_perl;

感谢@Grinnz 提到@INC钩子。


推荐阅读