Как мне получить доступ к константе в Perl, имя которой содержится в переменной? - PullRequest
4 голосов
/ 03 февраля 2010

У меня есть набор констант, объявленных в Perl:

   use constant C1 => 111;
   use constant C2 => 222;
   ..
   use constant C9 => 999;
   my $which_constant = "C2";

Как создать выражение Perl, которое на основе $which_constant получает значение константы, названной значением этой переменной - например, "222".

Обратите внимание, что я не могу изменить ни одно из указанных выше условий - это упрощение реального сценария: у меня есть модуль (который я не контролирую), из которого импортируются эти константы. Имя одной из констант предоставляется пользователем из командной строки. Мне нужно получить доступ к значению соответствующих констант.

Я бью головой об стену (в основном вокруг всяких странных шаровых конструкций), но ни одна из них не работает.

P.S. Если решение обращается к константам внутри их собственного модуля - скажем, My::Constants::C2 (без необходимости их импорта), даже лучше, но не обязательно - я могу легко импортировать правильные константы в main::, используя My::Constants->import($which_constant). и да, в довершение всего, константы по умолчанию НЕ экспортируются, поэтому требуется явный вызов import ().

Некоторые вещи, которые я пробовал:

  • main::$which_constant - синтаксическая ошибка

  • main::${which_constant} - синтаксическая ошибка

  • ${*$which_constant} - возвращает пустое значение

  • *$which_constant - возвращает "* main :: C2"

  • ${*${*which_constant}} - пусто

Ответы [ 3 ]

13 голосов
/ 03 февраля 2010

Константы, определенные как constant.pm, являются просто подпрограммами. Вы можете использовать синтаксис вызова метода, если у вас есть имя константы в строке:

#!/usr/bin/perl -l

use strict; use warnings;
use constant C1 => 111;
use constant C2 => 222;

print __PACKAGE__->$_ for qw( C1 C2 );
# or print main->$_ for qw( C1 C2 );

Таким образом, если вы попытаетесь использовать константу, которая не определена, вы получите ошибку.

8 голосов
/ 03 февраля 2010

Perl "константы" на самом деле являются подпрограммами, которые возвращают постоянное значение. Компилятор perl может заменить их соответствующим значением во время компиляции. Однако, так как вы хотите получить значение на основе поиска имени среды выполнения, вы должны сделать:

&{$which_constant}();

(И, конечно, вам нужно no strict 'refs' где-то.)

4 голосов
/ 03 февраля 2010

Предложение Синан использовать семантику вызова метода, чтобы обойти ограничения strict 'refs', является самым чистым и легким для чтения решением.

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

Поэтому я решил запустить тест (код и результаты следуют).

Результаты показывают, что нормальные встроенные константы работают примерно в два раза быстрее, чем вызовы методов с буквальным именем подпрограммы, и почти в три раза быстрее, чем вызовы методов с именами переменных подпрограмм. Самый медленный подход - это стандартное разыменование и вызов no strict "refs";.

Но даже самый медленный подход довольно быстр в моей системе со скоростью более 1,4 миллиона раз в секунду.

Эти тесты стирают мою единственную оговорку об использовании метода вызова метода для решения этой проблемы.

use strict;
use warnings;

use Benchmark qw(cmpthese);

my $class = 'MyConstant';
my $name  = 'VALUE';
my $full_name = $class.'::'.$name;


cmpthese( 10_000_000, {
    'Normal'      => \&normal_constant,
    'Deref'       => \&direct_deref,
    'Deref_Amp'   => \&direct_deref_with_amp,
    'Lit_P_Lit_N' => \&method_lit_pkg_lit_name,
    'Lit_P_Var_N' => \&method_lit_pkg_var_name,
    'Var_P_Lit_N' => \&method_var_pkg_lit_name,
    'Var_P_Var_N' => \&method_var_pkg_var_name,
});

sub method_lit_pkg_lit_name {
    return 7 + MyConstant->VALUE;
}

sub method_lit_pkg_var_name {
    return 7 + MyConstant->$name;
}

sub method_var_pkg_lit_name {
    return 7 + $class->VALUE;
}

sub method_var_pkg_var_name {
    return 7 + $class->$name;
}

sub direct_deref {
    no strict 'refs';
    return 7 + $full_name->();
}

sub direct_deref_with_amp {
    no strict 'refs';
    return 7 + &$full_name;
}

sub normal_constant {
    return 7 + MyConstant::VALUE();
}

BEGIN {
    package MyConstant;

    use constant VALUE => 32;
}

И результаты:

                 Rate Deref_Amp Deref Var_P_Var_N Lit_P_Var_N Lit_P_Lit_N Var_P_Lit_N Normal
Deref_Amp   1431639/s        --   -0%         -9%        -10%        -29%        -35%   -67%
Deref       1438435/s        0%    --         -9%        -10%        -28%        -35%   -67%
Var_P_Var_N 1572574/s       10%    9%          --         -1%        -22%        -29%   -64%
Lit_P_Var_N 1592103/s       11%   11%          1%          --        -21%        -28%   -63%
Lit_P_Lit_N 2006421/s       40%   39%         28%         26%          --         -9%   -54%
Var_P_Lit_N 2214349/s       55%   54%         41%         39%         10%          --   -49%
Normal      4353505/s      204%  203%        177%        173%        117%         97%     --

Результаты, полученные с помощью ActivePerl 826 в Windows XP, YMMV.

...