Как избежать объявления глобальной переменной при использовании динамического определения объема Perl?
Я пытаюсь написать Perl-скрипт, который вызывает функцию, написанную где-то еще (кем-то еще), которая манипулирует некоторыми переменными в области видимости моего скрипта. Допустим, сценарий main.pl
и функция есть в funcs.pm
, мой main.pl
выглядит так:
use warnings;
use strict;
package plshelp;
use funcs;
my $var = 3;
print "$var\n"; # <--- prints 3
{ # New scope somehow prevents visibility of $pointer outside
local our $pointer = \$var;
change();
}
print "$var\n"; # <--- Ideally should print whatever funcs.pm wanted
По какой-то причине, используя local our $pointer;
предотвращает видимость $pointer
вне рамок. Но если я просто использую our $pointer;
переменную можно увидеть вне области видимости в main.pl
с помощью $plshelp::pointer
(но не в funcs.pm
так что все равно было бы бесполезно). Как примечание, может кто-нибудь объяснить это?
funcs.pm
выглядит примерно так:
use warnings;
use strict;
package plshelp;
sub change
{
${$pointer} = 4;
}
Я ожидал, что это изменит значение $var
и распечатать 4
когда основной скрипт был запущен. Но я получаю сообщение об ошибке компиляции $pointer
не был объявлен Эту ошибку можно удалить, добавив our $pointer;
на вершине change
в funcs.pm
, но это создало бы ненужную глобальную переменную, которая видна везде. Мы также можем удалить эту ошибку, удалив use strict;
, но это кажется плохой идеей. Мы также можем заставить его работать, используя $plshelp::pointer
в funcs.pm
, но человек пишет funcs.pm
не хочет этого делать
Есть ли хороший способ для достижения этой функциональности funcs.pm
манипулировать переменными в моей области, не объявляя глобальные переменные? В любом случае, если бы мы собирались использовать глобальные переменные, я думаю, мне вообще не нужно использовать динамическую область видимости.
Скажем так: невозможно передать аргументы функции по какой-то причине.
Обновить
Кажется, что local our
не делает ничего особенного в том, что касается предотвращения видимости. Из perldoc:
Это означает, что когда
use strict 'vars'
в действительности,our
позволяет вам использовать переменную пакета без указания его имени пакета, но только в рамках лексической области нашего объявления. Это применяется немедленно - даже в пределах одного заявления.
а также
Это работает, даже если переменная пакета не использовалась ранее, так как переменные пакета возникают при первом использовании.
Так что это означает, что $pointer
"существует" даже после того, как мы оставим фигурные скобки. Просто, что мы должны ссылаться на это с помощью $plshelp::pointer
вместо просто $pointer
, Но так как мы использовали local
перед инициализацией $pointer
, он все еще не определен вне области действия (хотя он все еще "объявлен", что бы это ни значило). Более понятный способ написать это (local (our $pointer)) = \$var;
, Вот, our $pointer
"Объявляет" $pointer
и возвращается $pointer
также. Теперь мы применяем local
на это возвращаемое значение, и эта операция возвращает $pointer
еще раз, который мы назначаем \$var
,
Но это все еще оставляет основной вопрос о том, существует ли хороший способ достижения требуемой функциональности, без ответа.
2 ответа
Давайте выясним, как глобальные переменные с our
работа и почему они должны быть объявлены: есть разница между хранением глобальной переменной и видимостью ее неквалифицированного имени. Под use strict
неопределенные имена переменных не будут косвенно ссылаться на глобальную переменную.
Мы всегда можем получить доступ к глобальной переменной с ее полным именем, например
$Foo::bar
,Если глобальная переменная в текущем пакете уже существует во время компиляции и помечена как импортированная переменная, мы можем получить к ней доступ без указания имени, например
$bar
, ЕслиFoo
пакет написан соответствующим образом, можно сказатьuse Foo qw($bar); say $bar
где$bar
теперь является глобальной переменной в нашем пакете.С
our $foo
мы создаем глобальную переменную в текущем пакете, если эта переменная еще не существует. Имя переменной также доступно в текущей лексической области, как и переменнаяmy
декларация.
local
Оператор не создает переменную. Вместо этого он сохраняет текущее значение глобальной переменной и очищает эту переменную. В конце текущей области старое значение восстанавливается. Вы можете интерпретировать каждое имя глобальной переменной как стек значений. С local
Вы можете добавлять (и удалять) значения в стеке. Так что пока local
может динамически ограничивать значение, оно не создает динамически изменяемое имя переменной.
Тщательно продумав, какой код компилируется, когда становится ясно, почему ваш пример в настоящее время не работает:
В вашем основном скрипте вы загружаете модуль
funcs
,use
Оператор выполняется в фазе BEGIN, т.е. во время синтаксического анализа.use warnings; use strict; package plshelp; use funcs;
funcs
Модуль скомпилирован:use warnings; use strict; package plshelp; sub change { ${$pointer} = 4; }
На данный момент нет
$pointer
переменная находится в лексической области и не импортируется глобально$pointer
переменная существует. Поэтому вы получаете ошибку. Это наблюдение во время компиляции не связано с существованием$pointer
переменная во время выполнения.
Канонический способ исправить эту ошибку - объявить our $pointer
имя переменной в области действия sub change
:
sub change {
our $pointer;
${$pointer} = 4;
}
Обратите внимание, что глобальная переменная будет существовать в любом случае, это просто вводит имя в область для использования в качестве неквалифицированного имени переменной.
То, что вы можете использовать глобальные переменные, не означает, что вы должны это делать. Есть две проблемы с ними:
На уровне разработки глобальные переменные не объявляют понятный интерфейс. Используя полное имя, вы можете просто получить доступ к переменной без каких-либо проверок. Они не обеспечивают инкапсуляцию. Это делает для хрупкого программного обеспечения и странных действий на расстоянии.
На уровне реализации глобальные переменные просто менее эффективны, чем лексические переменные. Я никогда не видел этот вопрос, но подумайте о циклах!
Кроме того, глобальные переменные являются глобальными переменными: они могут иметь только одно значение за раз! Определение значения с local
может помочь избежать этого в некоторых случаях, но в сложных системах все еще могут возникать конфликты, когда два модуля хотят установить одну и ту же глобальную переменную в разные значения, и эти модули вызывают друг друга.
Единственное хорошее применение для глобальных переменных, которые я видел, - это предоставление дополнительного контекста для обратного вызова, который не может принимать дополнительные параметры, что примерно соответствует вашему подходу. Но там, где это возможно, всегда лучше передавать контекст в качестве параметра. Аргументы подпрограммы уже эффективно динамически ограничены:
sub change {
my ($pointer) = @_;
${$pointer} = 4;
}
...
my $var = 3;
change(\$var);
Если есть много контекста, может быть трудно передать все эти ссылки: change(\$foo, \$bar, \$baz, \@something_else, \%even_more, ...)
, Тогда может иметь смысл объединить этот контекст в объект, которым затем можно манипулировать более контролируемым образом. Управление локальными или глобальными переменными не всегда лучший дизайн.
Слишком много проблем с вашим кодом, чтобы просто исправить
Вы использовали package plshelp
как в основном скрипте, так и в модуле, даже если главная точка входа находится в main.pl
и ваш модуль находится в funcs.pm
, Это просто безответственно. Вы представляли, что package
Заявление было исключительно для рекламы за помощь, и не имеет значения, что вы положили туда?
Ваше сообщение не говорит, что не так с тем, что вы написали, но удивительно, что оно не выдает ошибку.
Вот что-то близкое, что делает то, что вы ожидаете. Я не могу ничего объяснить, так как ваш собственный код так далек от работы
Functions.pm
package Functions;
use strict;
use warnings;
use Exporter 'import';
our @EXPORT_OK = 'change';
sub change {
my ($ref) = @_;
$$ref = 4;
}
main.pl
use strict;
use warnings 'all';
use Functions 'change';
my $var = 44;
print "$var\n";
change(\$var);
print "$var\n";
выход
44
4