Подтвердить что ты не робот

For loop не изменяет переменную `my`, но изменяет переменную` our`

В Perl 5.20 цикл for, похоже, способен модифицировать переменную с модулем, но не лексическую переменную в родительской области.

#!/usr/bin/env perl
use strict;
use warnings;

our $x;

sub print_func {
    print "$x\n";
}

for $x (1 .. 10) {
    print_func; 
}

печатает от 1 до 10, как и следовало ожидать, но следующее:

#!/usr/bin/env perl
use strict;
use warnings;

my $x;

sub print_func {
    print "$x\n";
}

for $x (1 .. 10) {
    print_func; 
}

выдает следующее предупреждение 10 раз:

Use of uninitialized value $x in concatenation (.) or string at perl-scoping.pl line 8.

Что здесь происходит? Я знаю, что подпрограммы perl не могут быть вложены (и всегда имеют область модуля), и поэтому представляется логичным, что они не смогут закрыть переменные my. Похоже, в этом случае perl в режиме strict должен отклонить вторую программу с сообщением следующего вида:

Global symbol "$x" requires explicit package name at perl-scoping.pl line 6.
Global symbol "$x" requires explicit package name at perl-scoping.pl line 9.

т.е. он должен отклонить подпрограмму, потому что свободная переменная не объявлена ​​нигде и цикл for, потому что переменная не была объявлена.

Почему Perl ведет себя так?

4b9b3361

Ответ 1

Это запутанное, но документированное поведение, по-видимому, связано с плохим решением сделать переменную итератора цикла неявной локализованной глобальной, а не лексической. Из Циклы Foreach в perlsyn.

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

Другими словами, итератор цикла всегда локализуется в цикле. Если он глобальный, то он действует так, как он был объявлен local внутри блока цикла. Если он лексический, то он действует так, как он был объявлен с my внутри блока цикла.

Применение этого к вашим двум примерам поможет понять, что происходит.

our $x;

sub print_func {
    print "$x\n";
}

for $x (1 .. 10) {
    print_func; 
}

В этом цикле есть неявный local $x. local действительно должен был быть назван temp. Он временно переопределяет значение глобальной переменной в течение всего срока ее действия, но она по-прежнему является глобальной. Вот почему print_func может видеть это.

Старое значение восстанавливается, когда его область действия заканчивается. Вы можете увидеть это, если вы добавите print $x после цикла for.

use v5.10;

our $x = 42;

for $x (1 .. 10) {
    say $x;
}

say $x;  # 42

Посмотрите на свой код, включающий лексики (my variables).

my $x;

sub print_func {
    print "$x\n";
}

for $x (1 .. 10) {
    print_func; 
}

Что действительно происходит здесь, у вас есть две лексические переменные, называемые $x. Один из них имеет область видимости, одна из них привязана к циклу. Внутренний $x в цикле for имеет прецедент над внешним $x. Это известно как "затенение".

Лексики не могут быть замечены за пределами их физического охвата. print_func() видит только внешний неинициализированный $x.


Там некоторые стилистические выдержки из этого.

Всегда передавать параметры в свои функции.

В действительности, print_func должен принять аргумент. Тогда вам не придется беспокоиться о сложных правилах определения области.

sub print_func {
    my $arg = shift;
    print "$arg\n";
}

for $x (1..10) {
    print_func($x);
}

Всегда используйте for my $x.

Не полагайтесь на сложные неявные правила проверки цикла t221. Всегда объявляйте итератор цикла с помощью my.

for my $x (1..10) {
    print_func($x);
}

Избегать глобальных привязок.

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

my $Thing = 42;
sub get_thing { return $Thing }
sub set_thing { $Thing = shift; return }

Объявите переменные рядом с тем, где они используются.

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

Если вы сразу объявляете свои переменные, вам сложно понять, для чего они нужны, и трудно понять, что с ними связано или влияет на него. Если вы объявите его близким к его первому использованию, которое ограничивает то, что может повлиять на него, и делает его более очевидным, для чего оно предназначено.