Обхват на променливата по подразбиране $_ в 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

Всяка от тези променливи може да се използва независимо от другите. Всъщност, единственият начин, по който те са свързани е, че всички те се съдържат в *_ glob.

Тъй като сигилите варират в зависимост от масиви и хешове, когато осъществявате достъп до елемент, вие използвате знаците в скоби, за да определите коя променлива имате достъп:

$_[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] инсталира $iтия елемент от @results в скаларния слот на *_ globus, известен още като $_. В този момент $_ съдържа псевдоним на елемента от масива. Локализацията ще се развие в края на цикъла. local създава динамичен обхват, така че всички подпрограми, извикани от цикъла, ще видят новата стойност на $_, освен ако не я локализират. Има много повече подробности за тези концепции, но мисля, че те са извън обхвата на вашия въпрос.

person Eric Strom    schedule 20.09.2011
comment
тестовият израз за цикъл трябва да бъде $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
@Eric - Вие спорите за грешната точка. Съгласен съм на 100% от това, което казахте - знам, че от гледна точка на езиковия дизайн/ефективност $* двойният sygiling е добро и логично нещо. Не го прави по-четлив, когато незабавната четливост/яснота е на първо място. Не мисля, че някой е правил проучване, но се обзалагам, че ПОВЕЧЕ хора допускат различни грешки при четене, когато бързо сканират през $$_[0]{this}[0]{that} срещу $arg->{this}->[0]->{that} в състояние на когнитивни увреждания. Просто погледнете кода за отговор на David W. срещу 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