У меня есть массив хешей в Perl. Я хотел бы пройтись по массиву и выполнить некоторый код, указанный в строке для каждого из хэшей (примечание: я не хотел бы обсуждать этот подход или соображения его безопасности; давайте сосредоточимся на том, как это можно сделать). Пример реализации может выглядеть так:
use 5.014;
use strict;
use warnings;
my $list = [
{foo => "foo1"},
{foo => "foo2"},
{foo => "foo3"},
{foo => "foo4"},
];
my $code = q{say $item->{foo}};
foreach my $item (@$list) {
eval qq{$code};
}
Как и ожидалось, это печатает:
foo1
foo2
foo3
foo4
Все идет нормально. Однако мне бы хотелось, чтобы структура данных массива и хэша была прозрачной для фрагмента кода, т. е. фрагмент кода должен иметь возможность использовать $foo
вместо $item->{foo}
и т. д. Поэтому я пытаюсь импортировать хэш-записи в пространство имен main
. (Опять же, я хотел бы обсудить, как это сделать, а не стоит ли мне это делать.)
use 5.014;
use strict;
use warnings;
my $list = [
{foo => "foo1"},
{foo => "foo2"},
{foo => "foo3"},
{foo => "foo4"},
];
my $code = q{say $foo};
foreach my $item (@$list) {
foreach my $key (keys %$item) {
$main::{$key} = \$item->{$key};
}
{
no strict "vars";
eval qq{$code};
}
}
Я ожидал, что это сработает, но это не так (полностью). Этот код печатает:
Use of uninitialized value $foo in say at (eval 1) line 1.
foo2
foo3
foo4
По какой-то причине первый элемент обрабатывается неправильно.
Я попробовал несколько способов обойти это или узнать больше о том, что не так. Следующие изменения все работают (возможно, с предупреждением о том, что $foo
используется только один раз):
eval qq{$code}
на литерал say $foo
my $code = q{say $foo}
на my $code = q{say ${$main::{foo}}}
(однако $main::foo
недостаточно)$foo
где-то раньше, например. поставьте { no strict "vars"; my $bar = $foo; }
перед внешним циклом foreacheval sprintf('our $%s;', $key);
перед $main::{$key} = \$item->{$key};
Вариант 4 — это то, что я бы сделал сейчас. Это кажется немного хакерским, но делает свою работу. Однако мне все же хотелось бы понять, зачем это необходимо и есть ли лучший способ это исправить.
ОБНОВЛЕНИЕ: Как указывает TLP, более простой способ добиться этого — использовать символическую ссылку вместо манипуляций с таблицей символов. Смотрите мой собственный ответ на вопрос. Однако мне все же хотелось бы понять, почему подход с таблицей символов не работает «из коробки».
Вместо этого попробуйте ${$key}
. С включенными параметрами no strict vars.
Что касается $main::{$key}
: Да, $key
содержит строку foo
и, следовательно, $main::{$key}
относится к записи foo
в таблице символов. Что касается ${$key}
: Хороший вопрос! Я явно думал слишком сложно. (Однако в данном случае это no strict 'refs'
.)
Я до сих пор не могу заставить его работать. Я должен сказать, что, хотя это интересно с академической точки зрения, это хак и очень плохой дизайнерский выбор. Это очень похоже на проблему XY. Т.е. это неправильное решение вашей проблемы.
@TLP Решение с использованием вашего предложения см. в моем собственном ответе на вопрос. Что касается проблемы XY, то я сознательно не стал добавлять к вопросу свои аргументы в пользу выбора такого подхода, поскольку посчитал это слишком большим отступлением. Короче говоря, причина в том, что и фрагмент кода, и данные известны только во время выполнения, а структура данных должна быть прозрачной для человека, пишущего фрагмент кода.
Объявите $foo
перед присвоением ему:
#!/usr/bin/perl
use warnings;
use strict;
use feature qw{ say };
my $list = [ map { +{foo => "foo$_"} } 1 .. 4 ];
our $foo;
my $code = q{say $foo};
for my $item (@$list) {
for my $key (keys %$item) {
$main::{$key} = \$item->{$key};
}
eval $code;
}
Затем вы можете выбросить eval
и вместо этого использовать ссылки на код для повышения безопасности:
#!/usr/bin/perl
use warnings;
use strict;
use feature qw{ say };
my $list = [ map { +{foo => "foo$_"} } 1 .. 4 ];
our $foo;
my $code = sub { say $foo };
for my $item (@$list) {
for my $key (keys %$item) {
$main::{$key} = \$item->{$key};
}
$code->();
}
Спасибо за ваш ответ. Что касается объявления $foo
: Да, это работает, но foo теперь жестко запрограммирован. Я знаю только во время выполнения, какие переменные необходимы. Следовательно, подход eval (также используется our
в моем варианте 4 в вопросе. Что касается ссылки на код: опять же, это работает только в том случае, если код, который нужно выполнить, известен во время компиляции.
Символьная ссылка — более простой способ, чем манипулирование таблицей символов. Вот адаптация кода в вопросе, который решает проблему:
use 5.014;
use strict;
use warnings;
my $list = [
{foo => "foo1"},
{foo => "foo2"},
{foo => "foo3"},
{foo => "foo4"},
];
my $code = q{say $foo};
foreach my $item (@$list) {
foreach my $key (keys %$item) {
no strict 'refs';
${$key} = $item->{$key};
}
{
no strict 'vars';
eval qq{$code};
}
}
Важно отметить, что приведенное выше решение создает копию значений в структуре данных вместо создания псевдонима. Это имеет значение, если код в $code
изменяет значения. Пример модифицированного кода ниже демонстрирует это. Он также содержит (закомментированную) манипуляцию с фиксированной таблицей символов, которая вместо этого создает псевдоним, тем самым позволяя изменить исходную структуру данных с помощью $code
. См. также ответ зажима.
use 5.014;
use strict;
use warnings;
my $list = [
{foo => "foo1"},
{foo => "foo2"},
{foo => "foo3"},
{foo => "foo4"},
];
my $code = q{$foo .= '_bar'; say $foo};
foreach my $item (@$list) {
foreach my $key (keys %$item) {
no strict 'refs';
${$key} = $item->{$key}; # copy
# *{$key} = \$item->{$key}; # alias
}
{
no strict 'vars';
eval qq{$code};
}
say $item->{foo};
}
Не совсем ясно, хотите ли вы иметь псевдоним или копию значения в $main::foo
. Синтаксис псевдонима будет *foo = \$bar
(назначить скалярную ссылку на typeglob), см. perlmod:
use 5.014;
use strict;
use warnings;
my $list = [
{foo => "foo1"},
{foo => "foo2"},
{foo => "foo3"},
{foo => "foo4"},
];
my $code = q{say $foo};
foreach my $item (@$list) {
foreach my $key (keys %$item) {
no strict 'refs';
*{"main::$key"} = \$item->{$key}; # alias
# *{$key} = \$item->{$key}; # alias
# ${$key} = $item->{$key}; # value
}
{
no strict 'vars';
eval qq{$code};
}
}
Вы правы, я не уточнил в вопросе, ищу ли я псевдоним или копию, потому что в моем конкретном случае использования это не имеет большого значения. Однако очевидно, что это может быть очень важно, поэтому спасибо, что указали на это. Спасибо также за исправление назначения typeglob, это то, что я искал изначально. Я также изменил свой ответ, включив в него ваши замечания.
Я бы поместил всю структуру цикла в отдельный блок глобальной области видимости с no strict 'refs';
в качестве первой строки, просто чтобы сделать ее более понятной тем, кому понадобится читать код в будущем. Я бы также заменил foreach()
на for()
, что в наши дни гораздо более идиоматично. Хотя строка no strict 'refs'
выполняется только один раз во время компиляции, в том виде, в каком вы ее написали, она имеет два уровня глубины. Кроме того, у вас есть проблема с отступами, которая еще больше скрывает вещи. И последнее: no strict;
— чрезвычайно опасное предложение.
@stevieb: исправлены отступы и возвращены строгие переменные. Остальные ваши замечания мне кажутся делом вкуса. Если в кодовой базе OPs foreach предпочтительнее for, я не вижу причин это менять. Также для меня имеет смысл иметь nostrict 'refs'
в области, максимально близкой к тому месту, где необходим его эффект.
Ага, понятно. Вы думаете, что
$main::{$key}
должно быть таким же, как$foo
?