Re: my & local
From: Anton Berezin <tobez@plab.ku.dk>
Subject: Re: my & local
Newsgroups: fido7.ru.cgi.perl
On Sun, Dec 05, 1999 at 11:18:15PM +0300, Ilya Rubtsov wrote:
> Мож кто вкратце объяснит как всё на самом деле? В чем разница между my
> и local, и каковы у них области действия?
<h2>local</h2>
Я не знаю как в действительности *реализованы* local-переменные, но для
себя всегда представляю некий стек значений, доступ к головному элементу
которого осуществляется по имени переменной, push на который делает
команда? оператор? функция? local, а pop происходит неявно, по выходу за
пределы блока. Например:
#! /usr/bin/perl -w
use strict;
use vars '$var';
$var = 5;
Стек: +---+
$main::var =======> | 5 |
+---+
sub s1 { print "$var\n"; }
sub s2 { local $var = 37; s1; }
s1;
Стек внутри s1: +---+
$main::var =======> | 5 |
+---+
s2;
Стек после local: +----+
$main::var =======> | 37 |
+----+
| 5 |
+----+
В том числе и внутри s1 вызванного из s2.
s1;
После возврата из s2 произошёл неявный pop, поэтому:
+---+
$main::var =======> | 5 |
+---+
То есть локализация переменной это просто удобный способ сохранить
старое значение, поработать с переменной, и автоматически старое
значение восстановить. Очень часто это используется, например, при
закачке файла целиком в скаляр:
{ local $/; open AA, "< aa" or die $!; $aa = <AA>; close AA; }
Вместо гораздо менее изячного:
my $slash = $/; $/ = undef;
open AA, "< aa" or die $!; $aa = <AA>; close AA;
$/ = $slash;
Локализовать можно только переменную, занесенную в таблицу символов, и
поэтому попытка локализовать my-переменную окончится неудачей:
$ perl -e 'my $bla = 6; local $bla = 7;'
Can't localize lexical variable $bla at -e line 1.
Local в perl5 используется очень редко. Обычно нужно хорошо подумать,
прежде чем его употреблять. Практически, кроме приведенного выше
примера с $/, мне приходилось применять local только в случае рекурсии,
когда одни и те же переменные должны быть видимы из нескольких функций:
#! /usr/bin/perl -w
use strict;
use vars '$depth';
$depth = 0;
sub print_current_depth
{
print "$depth ";
}
sub foo
{
local $depth = $depth + 1;
foo() if $depth < 10;
print_current_depth;
}
foo;
print "\n";
__END__
10 9 8 7 6 5 4 3 2 1
Использование local оправдано еще в одном случае, а именно, если
необходимо создать настоящие вложенные функции. Подробнее об этом -
дальше по тексту.
<h2>my</h2>
My - это новинка perl5. My-переменная не попадает в таблицу символов и
имеет лексическую область видимости:
#! /usr/bin/perl -w
use strict;
use vars '$var'; # глобальная
$var = 'global';
sub s1 { print "$var "; }
my $var = 'my-file';
sub s2 { print "$var "; }
sub s3
{
my $var = 'my-function';
print "$var ";
if (1) {
my $var = 'my-block';
print "$var ";
s2;
s1;
}
print "$var ";
s2;
s1;
}
s3;
print "\n";
__END__
my-function my-block my-file global my-function my-file global
My-переменные просты и понятны, доступ к ним - несколько более быстрый,
чем к переменным, входящим в таблицу символов. Единственная область,
в которой поведение my-переменных может быть не вполне очевидным,
связана с closures (покрытия? закрытия? в общем, closures), а также с
`наивными' способами создания вложенных функций a la Pascal.
Эта сложность заключается в том, что при задании новой функции,
именованной либо анонимной, функция будет иметь доступ ко всем
лексическим (my) переменным, которые *существовали во время определения
функции*. Если функция определятся несколько раз, как часто бывает с
анонимными функциями, то разные `экземпляры' функции будут видеть разные
экземпляры лексических переменных:
#! /usr/bin/perl -w
use strict;
my @names = qw(Click Drag Move Push Pop Zapp);
sub make_callback
{
my $name = shift;
return sub { print "I am called on $name\n"; }
}
my %callback = map { $_ => make_callback($_) } @names;
for ( sort keys %callback) {
print "Action: $_;\t";
$callback{$_}->();
}
__END__
Action: Click; I am called on Click
Action: Drag; I am called on Drag
Action: Move; I am called on Move
Action: Pop; I am called on Pop
Action: Push; I am called on Push
Action: Zapp; I am called on Zapp
В этом примере каждая сгенерированная анонимная функция видит свой
собственный экземпляр лексической переменной $name, определенной в
функции make_callback().
Теперь - пример с неправильной реализацией вложенных функций:
#! /usr/bin/perl -w
use strict;
sub level1
{
my $value = shift;
sub level2
{
print "2: $value; ";
}
print "1: $value; ";
level2();
}
level1( 'A');
level1( 'B');
level1( 'C');
print "\n";
__END__
1: A; 2: A; 1: B; 2: A; 1: C; 2: A;
Я надеюсь, читатель уже понял, что происходит в этом случае. Perl не
имеет настоящих вложенных функций, и level2() определяется как
глобальная функция. Но, по правилам областей видимости, level2() должна
иметь доступ к лексической переменной $value. Поскольку my $value
создается заново при каждом вызове level1(), а level2() определяется
только лишь единожды, level2() будет `видеть' только одну конкретную
копию my $value, ту, которая в несколько виртуализованном состоянии
(level1 еще ни разу не вызвана!) существовала в момент определения
функции level2. До первого вызова level1() эта копия имела
неопределённое значение, а после первого вызова стала иметь значение
'A', что мы и видим при прогоне теста. [Замечу в скобках, что эта
`виртуализация' наглядно показывает, что my имеет как эффекты времени
компиляции, так и эффекты времени выполнения. Впрочем, если подумать,
то иначе и быть не может.]
В заключение, правильный способ создания вложенных функций в Perl5:
#! /usr/bin/perl -w
use strict;
sub level1
{
my $value = shift;
local *level2 = sub {
print "2: $value; ";
};
print "1: $value; ";
level2();
}
level1( 'A');
level1( 'B');
level1( 'C');
print "\n";
__END__
1: A; 2: A; 1: B; 2: B; 1: C; 2: C;
HTH & cheers, Anton.
P.S. Архивная копия этой статьи лежит здесь:
http://www.prima.eu.org/tobez/local_my.html
--
C makes it easy to shoot yourself in the foot. C++ makes that harder
-- but if you succeed you take off your whole leg. -- Chip Salzenberg