Как мне получить доступ к константе в Perl, имя которой содержится в переменной?

У меня есть набор констант, объявленных в 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}} - пусто


person DVK    schedule 02.02.2010    source источник
comment
Если вам когда-нибудь понадобится изменить этот модуль, search.cpan.org/perldoc?Readonly или < href="http://search.cpan.org/perldoc?Attribute::Constant" rel="nofollow noreferrer">search.cpan.org/perldoc?Attribute::Constant кажется гораздо лучше подходящим для этого использование.   -  person ephemient    schedule 03.02.2010
comment
Модуль находится под контролем (точнее, автоматически генерируется из бэкенда БД, содержащего список констант) команды разработчиков программного обеспечения. У них нет пропускной способности, чтобы возиться с чем-то таким мелким/тривиальным, и они не позволят кому-то извне вмешиваться в их код без серьезной деловой необходимости, оправдывающей риск изменения. Это перерывы при работе в крупном финансовом учреждении.   -  person DVK    schedule 03.02.2010


Ответы (3)


Константы, определенные 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 );

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

person Sinan Ünür    schedule 02.02.2010
comment
Это вероятный кандидат на принятый ответ, если кто-то не придумает что-то еще более хитрое (маловероятно, но мы говорим о Perl :)) - person DVK; 03.02.2010
comment
Да, использование -> для обозначения оценки подпрограммы довольно изящно. Определенно красивее, чем мой метод. - person JSBձոգչ; 03.02.2010
comment
В этом сценарии вы можете сказать My::Constants->$which_constant и полностью пропустить шаг import. Для лучшей проверки ошибок вы также можете проверить My::Constants->can($which_constant), если хотите убедиться, что константа существует (хотя это не будет различать константы и другие подпрограммы. - person cjm; 03.02.2010

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

&{$which_constant}();

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

person JSBձոգչ    schedule 02.02.2010
comment
или $which_constant->() или просто &$which_constant - person mob; 03.02.2010
comment
Фигурные скобки не нужны. &$which_constant() работает, если вы хотите пропустить магическую @_-обработку этого &$which_constant (не то чтобы это имело значение). - person ephemient; 03.02.2010
comment
JS - +1 за напоминание мне о константах - это просто подпрограммы, которые упустили из виду (ваш ответ был раньше, чем у Синана, так что это заслуженно, даже если его решение мне больше понравилось и поэтому было принято. - person DVK; 03.02.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.

person daotoad    schedule 03.02.2010
comment
Для моей текущей задачи это не имело значения (однократный вызов конфигурации), но в целом это серьезная проблема. У меня были ОСНОВНЫЕ замедления в проектах Perl, которые были исправлены путем редизайна, удаляющего вызовы методов в узких циклах после сравнения двух тестов. - person DVK; 03.02.2010