Функции Perl, изменяющие $ _

Я пытаюсь расширить использование неявной $_ (глобальной переменной «тема») в моем коде. Perlmonks имеет эту (устаревшую?) Статью о функциях, которые принимают $_ в отсутствие явных переменных.

У меня проблема в том, что я не знаю, какие функции установлены $_. Я знаю, что по крайней мере map, grep и _6 _ / _ 7_ изменят значение $_, но я предполагаю, что их должно быть больше. Мне также неясны какие-либо проблемы с областью действия, связанные с $_, например:

for (@array_of_array_refs)
{
  for (@$_)
  {
    print;
  }
  print;  # what does this print?
}

Есть ли список функций или набор рекомендаций, которым нужно следовать, чтобы я интуитивно знал, как избежать затирания $_?


person Greg Kennedy    schedule 03.12.2015    source источник
comment
Если вы не уверены, то ваш будущий программист по обслуживанию может быть не уверен. Так что делайте это явно, а не неявно.   -  person Sobrique    schedule 03.12.2015
comment
Вы можете сделать то же самое, что и я, когда добавил свою часть в этот пост Perlmonk: прочитать perlfunc и убедиться в этом сам. Вы все равно должны это сделать. Мы также рассмотрим это в Learning Perl.   -  person brian d foy    schedule 03.12.2015
comment
Я не думаю, что это отличная идея - массово пытаться расширить использование $_ во всем вашем коде.   -  person Sinan Ünür    schedule 04.12.2015
comment
Видите ли, я стал рассматривать $ _ как одну из функций языка, которая делает Perl уникальным Perl. Если я избегаю всех перлизмов, я могу просто использовать Python или что-то в этом роде.   -  person Greg Kennedy    schedule 05.12.2015
comment
$_ в правильной ситуации может сделать заявление более четким и менее избыточным. Например, одно из моих любимых применений - запуск функции для каждого элемента массива function($_) for @values;. Однако использование $_ в двойном цикле немедленно напоминает опытным программистам Perl об отладочных кошмарах.   -  person Christopher Bottoms    schedule 05.12.2015
comment
Чтобы ответить на вопрос в вашем коде, что это печатает ?: Он напечатает строку, представляющую текущую ссылку на массив в @array_of_array_refs (что-то вроде ARRAY(0x183ea68)).   -  person Christopher Bottoms    schedule 05.12.2015


Ответы (3)


Ответ Штеффена Ульриха вводит в заблуждение, поэтому мне придется ответить здесь. Возможно, я кое-что пропустил, но уже поздно. И Изучение Perl уже все это объясняет. ;)

Оператор local не работает в лексической области. Несмотря на то, что он говорит, это не ограничивается блоком, в котором находится. У людей обычно возникают проблемы с пониманием, потому что они не пробуют это делать. Такие термины, как «снаружи» и «внутри» вводят в заблуждение и опасны для местных.

Рассмотрим это использование, где есть функция, которая печатает глобальное значение $_:

$_ = 'Outside';
show_it();
inside();
$_ = 'Outside';
show_it();

sub show_it { print "\$_ is $_\n"; }

sub inside {
    local $_;

    $_ = 'Inside';
    show_it();
    }

Когда вы запустите это, вы увидите, что значение $_, установленное внутри блока, доступно за пределами блока:

$_ is Outside
$_ is Inside
$_ is Outside

local работает с переменными пакета. Он временно использует новое значение до конца блока. Однако как переменная пакета она изменяется повсюду в программе до тех пор, пока область local не изменится. заканчивается. У оператора есть лексическая область видимости, но он действует повсюду. Вы даете глобальной переменной временное значение, и эта глобальная переменная по-прежнему остается глобальной. Переменные local имеют глобальный эффект, но лексическое время жизни. Они изменяют значение везде в программе, пока эта область не закончится.

Как я уже писал ранее, неправильно говорить о «внутреннем» и «внешнем» с помощью local. Это «до» и «после». Я покажу еще немного из того, что будет в ближайшее время, где даже время распадается.

my совершенно другой. Он вообще не работает с пакетными переменными. Также называемые «лексическими переменными», они вообще не существуют вне своей области видимости (даже несмотря на то, что обратные магические модули, такие как PadWalker посмотри на них). Никакая другая часть программы не может их увидеть. Они видны только в своей области и под-областях, созданных в этой области.

Perl v5.10 позволяет нам создавать лексическую версию $_ (а также фиксированной и сделано экспериментально в v5.16 - не используйте его. См. также Хорошие, плохие и уродливые лексические $ _ в Perl 5.10+). В моем предыдущем примере я могу использовать это:

use v5.10;

$_ = 'Outside';
show_it();
inside();
$_ = 'Outside';
show_it();

sub show_it { print "\$_ is $_\n"; }


sub inside {
    my $_;
    $_ = 'Inside';
    show_it();
    }

Теперь вывод другой. Лексическая $_ действует так же, как и любая другая лексическая переменная. Это не влияет ни на что, выходящее за пределы его области видимости, опять же, потому что эти переменные существуют только в своей лексической области видимости:

$_ is Outside
$_ is Outside
$_ is Outside

Но, чтобы ответить на исходный вопрос. Сообщение Perlmonks Встроенные функции со значением по умолчанию $ _ все еще хороши, но я не думаю это актуально здесь. Эти функции используют $_, а не устанавливают его.

Главное, что нужно знать о Perl, - это то, что на него нет короткого ответа. Perl делает то, что имеет смысл, а не то, что делает его последовательным. В конце концов, это язык постмодерна.

Способ не беспокоиться об изменении $_ - это не изменять $_. Избегайте его использования. У нас есть много подобных советов по эффективному программированию на Perl.

для каждого

Конструкции цикла foreach и его for синоним используют локализованную версию $_ для ссылки на текущую тему. . Внутри цикла, включая все, что вызывает цикл, используется текущая тема:

use v5.10;

$_ = 'Outside';
show_it();
sub show_it { say "\$_ is $_"; }

my @array = 'a' .. 'c';
foreach ( @array ) {
    show_it();
    $_++
    }

say "array: @array";

Обратите внимание на массив после цикла foreach. Несмотря на то, что foreach локализует $_, Perl присваивает этому значению псевдоним, а не копирует его. Изменение управляющей переменной изменяет исходное значение, даже если это значение находится во внешней лексической области:

$_ is Outside
$_ is a
$_ is b
$_ is c
array: b c d

Не используйте $_ в качестве управляющей переменной. Я использую значение по умолчанию только в очень коротких программах, в основном потому, что я хочу, чтобы управляющая переменная имела значимое имя в больших программах.

карта и grep

Подобно foreach, map и grep используйте $_ для управляющей переменной. Для них нельзя использовать другую переменную. Вы по-прежнему можете влиять на переменные за пределами области действия с помощью псевдонима, повышающего производительность, который я показал в предыдущем разделе.

Опять же, это означает, что есть некоторая утечка прицела. Если вы измените $_ внутри блока и $_ был одним из элементов во входном списке, внешний $_ изменится:

use v5.10;
$_ = 'Outside';
my @transformed = map { $_ = 'From map' } ( $_ );
say $_;

Для умеренно сложных встроенных блоков я присваиваю $_ лексической переменной:

my @output = map { my $s = $_; ... } @input;

И если вы действительно нервничаете по поводу $_, не проделывайте злую шутку с map внутри map:

my @words = map {
    map { split } $_
    } <>;

Это глупый пример, но я делал такие вещи в прошлом, когда мне нужно было превратить тему в список.

пока (‹>)

В Perl есть удобная небольшая идиома, которая присваивает следующей строке дескриптора файла $_. Это означает, что вместо этого:

while( defined( $_ = <> ) )

Вы можете получить то же самое с:

while( <> )

Но какое бы значение ни попало в $_, остается в $_.

$_ = "Outside\n";
show_it();
sub show_it { print "\$_ is $_" }

while( <DATA> ) {
    show_it();
    }

show_it();

__DATA__
first line
second line
third line

Результат выглядит немного странно, потому что последняя строка не имеет значения, но это последнее значение, присвоенное $_: undef, которое оператор ввода строки назначил до того, как тест defined остановил цикл:

$_ is Outside
$_ is first line
$_ is second line
$_ is third line
$_ is

Поместите last туда, и результат изменится

$_ = "Outside\n";
show_it();
sub show_it { print "\$_ is $_" }

while( <DATA> ) {
    show_it();
    last;
    }

show_it();

__DATA__
first line
second line
third line

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

$_ is Outside
$_ is first line
$_ is first line

Если вам это не нравится, не используйте идиому:

while( defined( my $line = <> ) )

Сопоставление с образцом

Оператор подстановки s/// по умолчанию привязывается к $_ и может его изменить (в этом вся суть). Но с v5.14 вы можете использовать флаг /r, который оставляет исходный и возвращает измененную версию.

Оператор сопоставления m// также может изменять $_. Он не меняет значение, но может установить флаг позиции. Вот как Perl может выполнять глобальные сопоставления в скалярном контексте:

use v5.10;

$_ = 'Outside';
show_it();
sub show_it { say "\$_ is $_ with pos ", pos(); }

foreach my $time ( 1 .. 5 ) {
    my $scalar = m/./g;
    show_it();
    }

show_it();

Некоторые из скалярных настроек в $_ изменяются, даже если значение остается тем же:

$_ is Outside with pos
$_ is Outside with pos 1
$_ is Outside with pos 2
$_ is Outside with pos 3
$_ is Outside with pos 4
$_ is Outside with pos 5
$_ is Outside with pos 5

У вас, вероятно, не возникнет проблем с этим. Вы можете сбросить позицию при неудачном совпадении с $_. То есть, если вы не используете флаг /c. Несмотря на то, что скалярное значение не изменилось, часть его бухгалтерского учета изменилась. Это была одна из проблем с лексическим $_.

С сопоставлением происходит еще одна любопытная вещь. Переменные для каждого матча имеют динамическую область видимости. Они не меняют значения, которые у них были во внешней области:

use v5.10;

my $string = 'The quick brown fox';

OUTER: {
    $string =~ /\A(\w+)/;
    say  "\$1 is $1";

    INNER: {
        $string =~ /(\w{5})/;
        say  "\$1 is $1";
        }

    say  "\$1 is $1";
    }

Значение $1 в области OUTER не заменяется $1 в INNER:

$1 is The
$1 is quick
$1 is The

Если у вас болит голова, не используйте переменные для каждого совпадения. Назначьте их сразу (и только после успешного совпадения):

my $string = 'The quick brown fox';

OUTER: {
    my( @captures ) = $string =~ /\A(\w)/;

    INNER: {
        my $second_word;
        if( $string =~ /(\w{5})/ ) {
            $second_word = $1
            }
        }
    }
person brian d foy    schedule 04.12.2015

Пожалуйста, обратитесь к ответу Брайана для более подробного объяснения. Но я оставляю этот ответ, потому что некоторые проблемы в контексте вопроса могут быть сложными для понимания, и другое описание в этом ответе и комментарии могут быть полезны в дополнение к ответу Брайана, чтобы лучше понять проблему.

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


map, grep, for / foreach и т. д. "локализовать" $_. Это означает, что они привязывают новую переменную к $_, а исходная переменная привязывается к $_ только при выходе из лексической области видимости. См. Более подробное описание этой «локализации» в конце ответа. Например:

for(qw(1 2)) {
    for(qw(a b)) {
        print map { uc($_) } ($_,'x');
        print $_
    }
    print $_
}

даст вам AXaBXb1AXaBXb2, который показывает, что каждое использование for / map связывает $_ с другой переменной и связывает ее обратно с предыдущей переменной после выхода из блока.

А для функции, которая принимает $_ в качестве аргумента по умолчанию: у них нет никаких побочных эффектов, кроме ожидаемых (т. Е. Замены s///), и это задокументировано в perldoc, когда функция или операция будут использовать $_ в качестве аргумента по умолчанию.

Однако вы должны быть осторожны, если используете $_ сами и хотите убедиться, что это не повлияет на предыдущее значение. В этом случае самостоятельная локализация $_ поможет избежать случайного изменения предыдущего $_:

sub myfunction {
    local $_;
    # from now on until the functions gets left changes to $_ 
    # will not affect the previous $_
    ...
}

Это также возможно с блоком

{
    local $_;
    # from now on until the block gets left changes to $_
    # will not affect the previous $_
    ...
}

Но учтите, что часто используемый while (<>) не будет локализовать $_:

$_ = 'foo';
say $_;
while (<>) {
    say $_;
}
say $_;

В этом случае say $_ после цикла не будет отображать значение до цикла ('foo'), а будет последнее неявное присвоение из цикла (undef).


Что именно локализируется? Большинство из них привыкли к лексической области видимости, которую в Perl можно сделать с помощью «my». Но «локализация» переменной отличается, независимо от того, выполняется ли она с явным local или неявным внутри for, map ...

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

sub foo { say $_}

$_ = 1;
foo(); # 1

{
    local $_;  # bind symbol $_ to new variable
    $_ = 2;
    foo();     # 2 - because $_ inside foo() is the same as in this block
}

foo(); # 1     # leaving the block restored original binding of $_
person Steffen Ullrich    schedule 03.12.2015
comment
While () и s /// тоже. Также не рекомендуется изменять $ _ внутри grep и map, поскольку он имеет псевдоним для перечисления членов, как в случае с foreach. - person mpapec; 03.12.2015
comment
Большая часть этого сообщения неверна. local формирует динамическую область видимости, которая изменяет значение своей переменной во времени, а не лексическую область видимости. Пока не будет выполнен блок, объявляющий локальную переменную, значение вашего $ _ изменяется повсюду в программе. Вы неправильно поняли local и должны удалить этот пост. - person brian d foy; 03.12.2015
comment
@briandfoy: Я ценю ваш комментарий, но я говорил о сфере действия, а не о лексической области. Думаю, я знаю, как работает local, и, возможно, мое описание было слишком банальным. Почему бы просто не дать свой собственный ответ и лучше его описать, чтобы мы все могли извлечь из него уроки? - person Steffen Ullrich; 03.12.2015
comment
@briandfoy: Спасибо за отличный и подробный ответ на этот вопрос. Я считаю, что существенной частью вашей жалобы на мой ответ является иное понимание внутренней, внешней и масштабной составляющих. В своем ответе я пояснил, что я имею в виду не лексическую область видимости, а то, что вы называете динамической областью. Т.е. внутри для меня после входа в блокировку (область действия) и снаружи после выхода из блока (области действия) - во времени, а не в коде. Но вы правы в том, что это трудно понять тем, кто использует только языки, в которых нет этой концепции, и что слова легко интерпретировать неверно. - person Steffen Ullrich; 04.12.2015
comment
Однако, как я показал, внутри и снаружи не имеет значения, потому что есть утечка прицела. Вы не можете сказать, что local не будет влиять за пределы своей области ни в пространстве, ни во времени, потому что может. - person brian d foy; 04.12.2015
comment
@briandfoy: Спасибо за ваш вклад. Я удалил из ответа все следы внутренней или внешней области, потому что эти фразы явно сбивают с толку. Я по-прежнему оставляю (отредактированный) ответ, потому что он объясняет вещи другими словами, чем вы, поэтому может быть полезно в дополнение к вашему ответу, чтобы понять сложности, связанные с local. - person Steffen Ullrich; 05.12.2015

Помимо изучения всех встроенных функций (которые вы должны делать по крайней мере для тех, которые вы используете), вот что я считаю лучшим руководством для работы с $_:

  • Используйте $_ только тогда, когда это яснее и очевиднее, чем использование явных переменных.

Благодарю $_ как «это». «Это» - местоимение. Вы не говорите «Я пошел в магазин и купил», когда имеете в виду «Я пошел в магазин и купил мороженое». Местоимения следует использовать только тогда, когда очевидно, что они имеют в виду.

Небольшое изменение исходного кода дает нам полезный пример. Обратите внимание на использование именованной (явной) переменной во внешней области и использование $_ в наименьшей возможной области (где она сокращает весь внутренний цикл до одной строки):

# Process student records one year at a time
for my $student_records_aref (@student_records_by_year_array_refs)
{
    # Print a report on each student for the current year
    print_report_on($_) for @{$student_records_aref};
}
person Christopher Bottoms    schedule 05.12.2015