Импорт условно скомпилированных функций из модуля Perl - PullRequest
2 голосов
/ 14 апреля 2010

У меня есть набор функций ведения журнала и отладки, которые я хочу использовать для нескольких модулей / объектов. Я хотел бы иметь возможность включать / выключать их глобально с помощью переключателя командной строки.

Следующий код делает это, однако я хотел бы иметь возможность опустить имя пакета и сохранить все в одном файле.

В частности, я хотел бы импортировать имена функций ведения журнала в каждый модуль, чтобы их можно было вызывать без указания имени пакета (аналогично директиве C ++ use namespace;), и я хочу иметь возможность включить / отключить их глобально из скрипта, который использует их, как в моем примере кода ниже.

Это связано с двумя предыдущими вопросами, которые я задавал: здесь и здесь .

Еще одна вещь - я не думаю, что полностью понимаю, почему работает приведенный ниже код.

#! /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();

Ответы [ 3 ]

6 голосов
/ 14 апреля 2010

Если вы хотите сохранить все в одном файле, почему бы не поместить регистратор наверху в лексической области файла.

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;
    ...
}

РЕДАКТИРОВАТЬ: Хотя я не думаю, что это лучшая практика, основываясь на ваших комментариях, вот что вы, вероятно, ищете (прагма):

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';
2 голосов
/ 14 апреля 2010

Ниже приведен код без пакета явного для функций ведения журнала. Я переименовал LogFuncs :: compile в logerr: setup, чтобы быть немного более явным и не загрязнять основное пространство имен. Все в perl имеет пакет, по умолчанию это просто main . Я думаю, что код, который у вас был раньше, с пакетом для функций регистрации, на самом деле был чище, и его можно было легко разделить на отдельный модуль, чтобы его было легче использовать повторно.

Что касается непонимания того, как работает код, я не уверен, как решить эту проблему, но только потому, что вы не определили какие-либо особенности того, что вы не поняли. Я предполагаю, что вы имели в виду пакеты. Пакеты Perl можно рассматривать как пространства имен или классы. В случае пакета LogFuncs вы используете его в качестве пространства имен, и созданные в нем подпрограммы доступны как методы статического класса (чтобы использовать некоторые обозначения из других более традиционных языков OO, которые могут пролить свет на это ). Пакеты FooObj и BarObj более традиционно используются в качестве объектов, предоставляя конструктор new () и ожидая, что подпрограммы внутри них будут вызываться с использованием нотации объекта (-> sub), автоматически передавая сам объект в качестве первого параметра на саб / метод. Единственное, что определяет подпрограмму как метод статического класса или метод объекта, это ожидает ли он получения объекта в качестве первого параметра. Таким образом, небольшое продуманное и тщательное программирование может создать подпрограммы, которые работают для обоих, правильно определив, что является первым переданным параметром, и действуя соответствующим образом.

#! /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();
2 голосов
/ 14 апреля 2010

Если вам нужно пропатчить некоторые методы ведения журнала (или сделать что-то еще условно в среде разработчика или в рабочей среде), вы можете поместить код в метод 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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...