Область действия переменной по умолчанию $_ в Perl

У меня есть следующий метод, который принимает переменную, а затем отображает информацию из базы данных:

sub showResult {
    if (@_ == 2) {
        my @results = dbGetResults($_[0]);
        if (@results) {
            foreach (@results) {
                print "$count - $_[1] (ID: $_[0])\n";
            }
        } else {
            print "\n\nNo results found";
        }
   }
}

Все работает нормально, кроме строки печати в цикле foreach. Эта переменная $_ по-прежнему содержит значения, переданные методу.

Можно ли как-то «принудить» новую область значений к $_ или она всегда будет содержать исходные значения?

Если есть какие-нибудь хорошие туториалы, которые объясняют, как работает область видимости $_, это тоже было бы здорово!

Спасибо


person skeniver    schedule 20.09.2011    source источник
comment
Общее эмпирическое правило заключается в том, чтобы никогда не использовать скаляр по умолчанию, если в этом нет крайней необходимости. foreach my $result (@results) бесконечно лучше с точки зрения удобства обслуживания/читабельности кода по сравнению с foreach (@results). Тот факт, что Perl очень снисходителен к компромиссу между типизированными символами и удобочитаемостью, не означает, что вы не должны всегда ошибаться в отношении удобочитаемости. Всегда кодируйте так, как будто следующий разработчик, поддерживающий ваш код, — буйный психотик, который знает, где вы живете :)   -  person DVK    schedule 21.09.2011
comment
На самом деле, этот вопрос не о сфере действия $_, он возникает из-за непонимания соглашений об именах Perl.   -  person Wolf    schedule 24.02.2017


Ответы (3)


В Perl имя _ может относиться к ряду различных переменных:

Общие из них:

$_ the default scalar (set by foreach, map, grep)
@_ the default array  (set by calling a subroutine)

Менее распространенные:

%_ the default hash (not used by anything by default)
 _ the default file handle (used by file test operators)
&_ an unused subroutine name
*_ the glob containing all of the above names

Каждая из этих переменных может использоваться независимо от других. На самом деле, единственный способ, которым они связаны, состоит в том, что все они содержатся в глобусе *_.

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

$_[0]   # element of @_
$_{...} # element of %_

$$_[0]  # first element of the array reference stored in $_
$_->[0] # same

Цикл for/foreach может принимать для использования имя переменной, а не $_, и это может быть понятнее в вашей ситуации:

for my $result (@results) {...}

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


Поскольку ваш вопрос был больше связан с именами переменных, чем с областью действия, я не обсуждал фактическую область действия цикла foreach, но в целом следующий код эквивалентен тому, что у вас есть.

for (my $i = 0; $i < $#results; $i++) {
    local *_ = \$results[$i];
    ...
}

Строка local *_ = \$results[$i] устанавливает $ith элемент @results в скалярный слот глобуса *_, он же $_. На данный момент $_ содержит псевдоним элемента массива. Локализация завершится в конце цикла. local создает динамическую область видимости, поэтому любые подпрограммы, вызываемые из цикла, будут видеть новое значение $_, если они также не локализуют его. Существует гораздо больше подробностей об этих концепциях, но я думаю, что они выходят за рамки вашего вопроса.

person Eric Strom    schedule 20.09.2011
comment
выражение проверки цикла for должно быть $i <= $#results, так как $#results является последним индексом массива @results. - person dividebyzero; 15.03.2013

Проблема здесь в том, что вы действительно используете @_ вместо $_. Цикл foreach изменяет $_, скалярную переменную, а не @_, к которой вы обращаетесь, если индексируете ее на $_[X]. Кроме того, проверьте еще раз код, чтобы увидеть, что находится внутри @results. Если это массив массивов или ссылок, вам может понадобиться косвенное ${$_}[0] или что-то в этом роде.

person Diego Sevilla    schedule 20.09.2011
comment
$$_[0] в полном порядке. И легче печатать. :) - person tchrist; 21.09.2011
comment
@ikegami, @tchrist, мило, отметили. Однако до сих пор не ясно, что находится в @results. - person Diego Sevilla; 21.09.2011
comment
@tchrist - извините, но я не согласен. $_-> с первого взгляда ясно показывает, что вы разыменовываете (и это универсальное обозначение для вложенного разыменования x-of-x-of-x). $$_[0] НАМНОГО сложнее читать, например. вы легко можете неправильно понять его как $_[0]; И не работает в качестве нотации для дальнейшего разыменования (мои обычные лакмусовые бумажки просматривают незнакомый код в полусне в 2 часа ночи, когда генеральный директор кричит на вас, чтобы вы исправили производственную проблему ВЧЕРА; и поддержка кода младшим разработчиком ). С точки зрения удобочитаемости кода синтаксис $$ не так хорош, как стрелка. - person DVK; 21.09.2011
comment
@DVK =› каждый раз, когда у вас есть сигилы подряд ($$, @$, %$), это с первого взгляда показывает, что вы разыменовываете. синтаксис $$_[0] отлично подходит для дальнейшего разыменования: $$_[0]{this}[0]{that}. синтаксис $$_[0] также аналогичен расширенному синтаксису ${$_}[0] и синтаксису фрагмента @$_[1,2]. при обучении разработчика все, что вам нужно сказать ему, это то, что для любого рабочего синтаксиса, использующего переменную @array, вы можете выполнить текстовую замену array на $array, и новый код будет правильно использовать ссылки. другими словами, '$array' — это полный идентификатор. - person Eric Strom; 21.09.2011
comment
@ Эрик, ты споришь не о том. Я согласен с тем, что вы сказали на 100% - я знаю, что с точки зрения языкового дизайна / эффективности $* двойная сигилинг - это хорошо и логично. Не делает его более удобочитаемым, когда мгновенная читаемость/четкость на высоте. Я не думаю, что кто-то проводил исследование, но я держу пари, что БОЛЬШЕ людей делают различные ошибки чтения при быстром просмотре $$_[0]{this}[0]{that} по сравнению с $arg->{this}->[0]->{that} в состоянии когнитивных нарушений. Просто посмотрите на код ответа Дэвида В. по сравнению с OP. - person DVK; 21.09.2011

Как отмечали другие:

  • Вы действительно используете @_, а не $_ в своем операторе печати.
  • Нехорошо хранить данные в этих переменных, поскольку они используются где-то еще.

Официально $_ и @_ являются глобальными переменными и не являются членами какого-либо пакета. Вы можете локализовать область с помощью my $_, хотя это, вероятно, очень, очень плохая идея. Проблема в том, что Perl может использовать их без вашего ведома. Плохая практика — полагаться на их значения более чем на несколько строк.

Вот небольшая переработка вашей программы, чтобы максимально избавиться от зависимости от @_ и $_:

sub showResults {
    my $foo = shift;    #Or some meaningful name
    my $bar = shift;    #Or some meaningful name

    if (not defined $foo) {
       print "didn't pass two parameters\n";
       return;  #No need to hang around
    }
    if (my @results = dbGetResults($foo)) {
        foreach my $item (@results) {
        ...
    }
}

Некоторые модификации:

  • Я использовал shift, чтобы дать вашим двум параметрам фактические имена. foo и bar не очень хорошие имена, но я не мог выяснить, откуда dbGetResults, поэтому я не мог понять, какие параметры вы искали. @_ все еще используется при передаче параметров, а мой shift зависит от значения @_, но после первых двух строк я свободен.
  • Поскольку ваши два параметра имеют настоящие имена, я могу использовать if (not defined $bar), чтобы увидеть, были ли переданы оба параметра. Я также изменил это на отрицательное. Таким образом, если они не передали оба параметра, вы можете выйти раньше. Таким образом, в вашем коде будет на один отступ меньше, и у вас не будет структуры if, занимающей всю вашу подпрограмму. Это облегчает понимание вашего кода.
  • Я использовал foreach my $item (@results) вместо foreach (@results) и зависел от $_. Опять же, так понятнее, что делает ваша программа, и вы бы не перепутали $_->[0] с $_[0] (думаю, вы именно это и делали). Было бы очевидно, что вы хотели $item->[0].
person David W.    schedule 20.09.2011