Почему моя подпрограмма Perl не может видеть значение для переменной в цикле foreach, который назвал его?

Я надеюсь, что это - что-то простое, которое я делаю неправильно. Я видел что-то онлайн о "переменном самоубийстве", которое выглядело хорошим, но это было для более старой версии, и я нахожусь на 5.10.1.

Так или иначе - переменная, которую я объявил - $RootDirectory - просто внезапно, теряет свое значение, и я не могу выяснить почему.

Вот сценарий для репродуцирования проблемы. Когда я пробегаю сценарий в режиме отладки (жемчуг-d), я могу заставить его распечатывать $RootDirectory в строке 21 и 26. Но это пошло с методической точностью 30.

use strict;
my $RootDirectory; 
my @RootDirectories; 

@RootDirectories = (
   'c:\\P4\\EDW\\PRODEDW\\EDWDM\\main\\db\\'
   ,'c:\\P4\\EDW\\PRODEDW\\EDWADS\\main\\db\\'
   ,'c:\\P4\\EDW\\PRODEDW\\FJE\\main\\db\\'
   );

foreach $RootDirectory (@RootDirectories) { 
   # $RootDirectory = 'c:\\P4\\EDW\\PRODEDW\\EDWDM\\main\\db\\';
   # print ' In foreach ' . $RootDirectory. "\n";
   RunSchema ();
} 

exit(0);

sub RunSchema() { 
   # print ' In RunSchema ' . $RootDirectory. "\n";
   CreateTables ();
} 

sub CreateTables() { 
   # print ' In CreateTables ' . $RootDirectory. "\n";
   SQLExecFolder ('tbl');
} 

sub SQLExecFolder() { 
   print ' In SQLExecFolder ' . $RootDirectory. "\n";       # Variable $RootDirectory value is gone by now
} 

РЕДАКТИРОВАНИЕ спасибо за все комментарии! Я думаю на данный момент, что я буду использовать "наше" ключевое слово, которое, кажется, работает хорошо - благодарит Nathan. Также спасибо toolic о Предупреждениях Использования - я думаю, что продаюсь на том!

Вещь, которая продолжает смущать меня, состоит в том, почему, когда я сделал режим отладки (жемчуг-d), и ступил через код, делая "p $RootDirectory", я получил ожидаемый вывод в строках 21 и 26, но не строке 30. Как ситуация отличается в строке 30?

Кроме того, я ценю комментарии о лучшей практике быть для передачи $RootDirectory как параметра функции. Я хотел избежать, чтобы, потому что у меня есть столько функций после того - т.е. RunSchema, назвал CreateTables, который называет SQLExecFolder. Всем им нужно было бы передать тот же параметр. Это все еще имеет смысл в этом случае или является там какими-либо лучшими способами структурировать это?

5
задан brian d foy 16 March 2010 в 13:38
поделиться

7 ответов

Вы объявляете $ RootDirectory как переменную цикла в цикле foreach . Насколько я понимаю, это означает, что его значение локально привязано к циклу, и его значение восстанавливается до своего предыдущего значения в конце цикла.

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

Изменить : На самом деле проблема в том, что $ RootDirectory объявлен с my , поэтому он не определен в других областях.В функциях RunSchema , CreateTables и SQLExecFolder переменная не определена, независимо от локализации foreach .

Если вы хотите, чтобы переменная была объявлена ​​для строго , но чтобы она была глобальной, объявите $ RootDirectory с помощью нашего :

our $RootDirectory;

Редактировать : При этом не всегда рекомендуется использовать глобальную переменную. Вам лучше передать переменную в качестве параметра функциям, как предлагали другие.

5
ответ дан 18 December 2019 в 06:34
поделиться

Переменная foreach особенная - она ​​локальная для цикла .

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

Посмотрите здесь

3
ответ дан 18 December 2019 в 06:34
поделиться

То, что сказал Натан, верно. Помимо этого, почему бы вам не передать значение? В любом случае это лучшая практика:

foreach $RootDirectory (@RootDirectories) { 
   # $RootDirectory = 'c:\\P4\\EDW\\PRODEDW\\EDWDM\\main\\db\\';
   # print ' In foreach ' . $RootDirectory. "\n";
   RunSchema ($RootDirectory);
} 

sub SQLExecFolder { 
   my $RootDirectory = shift;
   print ' In SQLExecFolder ' . $RootDirectory. "\n";
} 
8
ответ дан 18 December 2019 в 06:34
поделиться

Другие ответили на ваш вопрос правильно. Я просто хочу подчеркнуть, что вам следовало бы добавить use warnings; в ваш код. Это дало бы подсказку к вашей проблеме и предупредило бы вас о другой потенциальной опасности.

4
ответ дан 18 December 2019 в 06:34
поделиться

Переменная итератора в цикле foreach всегда локализована для цикла. См. Раздел foreach в perlsyn . Вы можете передать его подпрограмме в качестве параметра.

2
ответ дан 18 December 2019 в 06:34
поделиться

Неплохое усилие. Вот пара небольших улучшений и одно «исправление», которое заключается в передаче переменной в подпрограммы в качестве параметра функции, поскольку переменная $ RootDirectory имеет область видимости (т.е. ограничена) с точностью до цикла foreach . В целом также считается хорошей практикой указывать, какие переменные передаются и / или к которым осуществляется доступ для различных подпрограмм.

use strict;
use warnings;

sub RunSchema() {
   my $root_dir = shift;
   CreateTables($root_dir);
}

sub CreateTables() {
   my $root_dir = shift;
   SQLExecFolder('tbl', $root_dir);
}

sub SQLExecFolder() {
   my ($name, $root_dir) = @_;
}
######################################################


my @RootDirectories = qw(
   c:\\P4\\EDW\\PRODEDW\\EDWDM\\main\\db\\
   c:\\P4\\EDW\\PRODEDW\\EDWADS\\main\\db\\
   c:\\P4\\EDW\\PRODEDW\\FJE\\main\\db\\
);

foreach my $RootDirectory (@RootDirectories) {
   # print ' In foreach ' . $RootDirectory. "\n";
   RunSchema($RootDirectory);
}

exit(0);
0
ответ дан 18 December 2019 в 06:34
поделиться

RE: Когда использовать глобальную переменную?

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

Когда имеет смысл использовать глобал? Когда преимущества перевешивают риски.

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

НЕПРАВИЛЬНО. В этом случае правильный подход - объединить все эти различные переменные в одну структуру данных контейнера. Итак, вместо foo ($ frob, $ grizzle, $ cheese, $ omg, $ wtf); у вас есть foo ($ state, $ frob); Где $ state = {grizzle => $ grizzle, cheese => $ cheese, omg => $ omg, wtf => $ wtf}; .

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

На этом этапе у вас есть несколько вариантов:

  1. Сделать $ state глобальным и просто получить к нему прямой доступ.
  2. Превратите $ state в объект конфигурации и используйте методы для управления доступом к атрибутам.
  3. Превратите весь модуль в класс и сохраните всю информацию о состоянии в объекте.

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

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

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

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

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

use strict;
use warnings;

my @directories = (
   'c:\\P4\\EDW\\PRODEDW\\EDWDM\\main\\db\\',
   'c:\\P4\\EDW\\PRODEDW\\EDWADS\\main\\db\\',
   'c:\\P4\\EDW\\PRODEDW\\FJE\\main\\db\\',
);

for my $schema ( make_schemata(@directories) ) {

    $schema->run;

}

sub make_schemata {
    my @schemata = map { MySchema->new( directory => $_ } @_;

    return @schemata;
}


BEGIN {
    package MySchema;

    use Moose;

    has 'directory' => (
        is => 'ro',
        isa => 'Str',
        required => 1,
    );

    sub run { 
       my $self = shift;

       $self->create_tables;
    } 

    sub create_tables { 
       my $self = shift;

       $self->sql_exec_folder('tbl');
    }

    sub sql_exec_folder {
        my $self = shift;

        my $dir = $self->directory;

        print "In SQLExecFolder $dir\n";
    }

    1;
} 

В качестве бонуса код в блоке BEGIN может быть удален и помещен в отдельный файл для повторного использования другим сценарием. Все, что ему нужно, чтобы быть полноценным модулем, - это собственный файл с именем MySchema.pm .

2
ответ дан 18 December 2019 в 06:34
поделиться
Другие вопросы по тегам:

Похожие вопросы: