【问题标题】:Importing Conditionally Compiled Functions From a Perl Module从 Perl 模块导入条件编译函数
【发布时间】:2010-04-14 07:57:25
【问题描述】:

我有一组日志记录和调试功能,我想在多个模块/对象中使用它们。我希望能够使用命令行开关全局打开/关闭它们。

以下代码执行此操作,但是,我希望能够省略包名并将所有内容保存在一个文件中。

更具体地说,我想将日志记录函数名称导入每个模块,以便可以在没有任何包名称限定的情况下调用它们(类似于 C++ use namespace; 指令)并且我希望能够启用/ 从使用它们的脚本中全局禁用它们,如下面的示例代码所示。

这与我之前提出的两个问题有关,herehere

另一件事 - 我不认为我完全理解为什么下面的代码有效。

#! /usr/bin/perl -w

use strict;
use Getopt::Long;

{
    package LogFuncs;

    use threads;
    use Time::HiRes qw( gettimeofday );

    # provide tcpdump style time stamp
    sub _gf_time {
        my ( $seconds, $microseconds ) = gettimeofday();
        my @time = localtime($seconds);

        return sprintf( "%02d:%02d:%02d.%06ld",
            $time[2], $time[1], $time[0], $microseconds );
    }

    sub logerr;

    sub compile {
        my %params = @_;

        *logerr = $params{do_logging}
            ? sub {
                my $msg = shift;
                warn _gf_time() . " Thread " . threads->tid() . ": $msg\n";
            }
            : sub { };
    }            
}

{
    package FooObj;

    sub new {
        my $class = shift;
        bless {}, $class; 
    };

    sub foo_work { 
        my $self = shift;
        # do some foo work
        LogFuncs::logerr($self);
    }
}

{
    package BarObj;

    sub new {
        my $class = shift;
        my $data  = { fooObj => FooObj->new() };
        bless $data, $class;
    }

    sub bar_work { 
        my $self = shift;
        $self->{fooObj}->foo_work();
        LogFuncs::logerr($self);
    }
}        

my $do_logging = 0;

GetOptions(
    "do_logging"    => \$do_logging,
);

LogFuncs::compile(do_logging => $do_logging);

my $bar = BarObj->new();
LogFuncs::logerr("Created $bar");
$bar->bar_work();

【问题讨论】:

    标签: perl


    【解决方案1】:

    如果您想将所有内容保存在同一个文件中,为什么不将记录器放在文件范围词法的顶部。

    GetOptions( do_logging => \$do_logging );
    
    my $logerr = $do_logging ? sub {logging_code_here()} : sub {};
    

    $logerr 现在可以在同一文件中该点之后定义的任何包中使用。

    但是,按如下方式构建日志调用通常会更快:

    my $logerr = sub { logging_code_here() };
    
    $logerr->("some string $some_var") if $do_logging;
    

    这样可以避免子例程调用,并且如果日志记录关闭,则不需要计算 $logerr 的字符串参数。

    您还可以设置日志记录级别:

    $logerr->("starting loop")     if $do_logging;
    
    for (@big_array) {
        $logerr->("processing $_") if $do_logging > 1;
        ...
    }
    

    编辑:虽然我认为这不是最佳实践,但根据您的 cmets,这可能是您正在寻找的(编译指示):

    use 5.010;
    use warnings;
    use strict;
    BEGIN {  # compile time
        $INC{'log.pm'}++;  # block 'require log;'
        package log;
        sub is_active {(caller 1)[10]{log}}  # test the hints hash
        sub import {
            $^H{log} = 1;  # set the hints hash for 'log'
            my $logmsg = (caller).'::logmsg';  # name of caller's sub
            no strict 'refs';
            *$logmsg = sub {print "logging: @_\n" if is_active}  # install sub
                unless *{$logmsg}{CODE}; # unless we did already
        }
        sub unimport {
            $^H{log} = 0;  # unset the hints hash
        }
    }
    
    package MyPkg;
    
    use log;
    logmsg 'hello, world!';
    
    {
      no log;
      logmsg 'nope, not here';
    }
    
    logmsg 'back again';
    

    【讨论】:

    • @Eric Strom:但是我不需要引用包 main 来将它们拉入我的对象包中吗?如main::$logerr->("processing $_") if $do_logging > 1;。重点是我试图简洁并减少代码中的打字量和混乱。对于不同的日志记录/调试级别,我只是有不同的功能,即loginfo1loginfo2 等。我认为 Perl 足够聪明,可以在关闭日志记录时优化对空函数的调用。谢谢,但我认为您的解决方案不符合我的要求。
    • @Robert S. Barnes => 不,这不是my 变量的作用域。词法不是包的成员,而是范围级别的成员,因此,如果在文件的根范围内定义了词法变量,则它将在同一文件中它下面的每个包/块中可见。此外,我认为 perl 不会优化子调用,并且在子调用之前计算消息文本可能会很慢(例如,如果日志记录代码处于紧密的内部循环中)。通过将检查移出 sub 并进入调用代码,您可以在调试级别 0 防止大量不必要的计算。
    • @Eric Strom:感谢您对可见性的澄清。但是,当我尝试将 log 函数放在 main 的全局范围内时,它在它下面的包中不可见。您是说 my 变量是可见的,而没有 my 声明的变量则不可见?关于一个与另一个的性能,唯一真正了解的方法是对其进行测试。
    • @Eric Strom:哇,那是一些看起来很可怕的代码。这可能是我正在寻找的东西,尽管我尝试运行它并且它在第 6 行出现语法错误。我认为您无法解释代码中发生了什么,以及为什么您认为这是不好的做法?
    • @Eric Strom:我通过循环调用 $bar->bar_work() 1000 万次测试了这两种方法的速度,发现在关闭日志记录的情况下,“编译”方法需要 26.9 个用户秒,而您的 @ 987654332@ 方法耗时 20.07 秒。
    【解决方案2】:

    下面是您的代码,没有用于日志记录功能的 explicit 包。我已将 LogFuncs::compile 重命名为 loggerr:setup 更明确一点,并且不会污染主命名空间。 perl 中的所有内容都有一个包,默认情况下它只是 ma​​in。我认为你之前的代码,带有一个用于日志记录功能的包实际上更干净,并且可以轻松地将其拆分为一个单独的模块,因此可以更轻松地重用它。

    至于不了解代码的工作原理,我不确定如何解决,只是因为您没有确定任何您不了解的细节。我假设你的意思是包裹。 Perl 包可以被视为命名空间或类。在 LogFuncs 包的情况下,您将它用作命名空间,并且在其中创建的 subs 以 静态类方法 的形式访问(使用其他更传统的 OO 语言中可能会发光的符号这)。 FooObj 和 BarObj 包更传统地用作对象,通过提供构造函数 new(),并期望使用对象表示法 (->sub) 调用其中的子对象,自动将对象本身作为第一个传递子/方法的参数。唯一将 sub 定义为静态类方法或对象方法的是它是否希望接收对象作为第一个参数。因此,通过正确确定第一个传递的参数是什么并采取相应的行动,稍加深思熟虑和仔细的编程就可以产生对两者都适用的 subs。

    #! /usr/bin/perl -w
    
    use strict;
    use Getopt::Long;
    
    use threads;
    use Time::HiRes qw( gettimeofday );
    
    # provide tcpdump style time stamp
    sub _gf_time {
    my ( $seconds, $microseconds ) = gettimeofday();
    my @time = localtime($seconds);
    
    return sprintf( "%02d:%02d:%02d.%06ld",
        $time[2], $time[1], $time[0], $microseconds );
    }
    
    sub logerr;
    
    sub logerr_setup {
        my %params = @_;
    
        *logerr = $params{do_logging}
            ? sub {
                my $msg = shift;
                warn _gf_time() . " Thread " . threads->tid() . ": $msg\n";
            }
            : sub { };
        }
    
    {
        package FooObj;
    
        sub new {
            my $class = shift;
            bless {}, $class;
        };
    
        sub foo_work {
            my $self = shift;
            # do some foo work
            main::logerr($self);
        }
    }
    
    {
        package BarObj;
    
        sub new {
            my $class = shift;
            my $data  = { fooObj => FooObj->new() };
            bless $data, $class;
        }
    
        sub bar_work {
            my $self = shift;
            $self->{fooObj}->foo_work();
            main::logerr($self);
        }
    }
    
    my $do_logging = 0;
    
    GetOptions(
        "do_logging"    => \$do_logging,
    );
    
    logerr_setup(do_logging => $do_logging);
    
    my $bar = BarObj->new();
    logerr("Created $bar");
    $bar->bar_work();
    

    【讨论】:

    • @kbenson:+1 解释。实际上,我想要的是相当于 C++ use namespace; 指令。我想将日志记录功能保留在它们自己的包中,能够在没有 any 包名称的情况下直接引用它们,并从主脚本全局打开/关闭它们。现在我得到了三分之二。
    • @Robert S. Barnes:查看 Exporter 的 perldoc,我认为这就是您想要的。它允许您定义导出部分或全部包名称空间的关键字,或者自动导出某些部分。当您看到使用 Time::HiRes 'time' 之类的使用语法来覆盖内置的 time() 以返回高分辨率时间时,这就是正在使用的内容。它将 Time::HiRes 命名空间中定义的新 time() 子导出到 main.
    【解决方案3】:

    如果您需要修补一些日志记录方法(或在开发人员环境与生产环境中有条件地执行其他操作),您可以将代码放入包的 import() 方法中,并在 use 行中使用参数启用/禁用它:

    # this is in MyLogger.pm:
    package MyLogger;
    
    use strict; use warnings;
    use Exporter;
    
    our @EXPORT_OK = qw(logerr);
    
    sub import
    {
         my ($class, $debug) = @_;
    
         if ($debug)
         {
              # do whatever you need here - patch methods, turn up the log level to "everything", etc
    
              # imports logerr method
              MyLogger->export_to_level(1, 'logger');
         }
    }
    
    sub logerr
    {
        # your patched method here..
    }
    
    1;
    
    # (now in your application code...)
    use MyLogger "debug";    # enables debugging mode
    # or:
    use MyLogger MyConfig::DEBUG;    # uses a debugging constant that you defined elsewhere
    

    【讨论】:

    • @Ether:这听起来很有希望。这是否允许我在使用各种模块的脚本中全局打开/关闭登录?如果所有模块都定义在一个文件中,这将如何工作?您能否根据我在帖子中提供的代码给出更完整的示例?
    • 不得不说,这比 Eric Strom 的想法好多了。
    • @Robert:如果没有更多上下文,我不太确定您的代码示例试图做什么,但您可以使用 CPAN 上的 Exporter 包将函数导入您的命名空间——我会更新我的代码以显示示例。
    • @Kinopiko => 想解释一下为什么?
    • 我在一个项目中非常成功地使用了类似的东西。效果很好。
    猜你喜欢
    • 2022-11-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-06
    • 2016-12-15
    • 1970-01-01
    • 1970-01-01
    • 2011-04-06
    相关资源
    最近更新 更多