Почему Programming Perl использует local (не my) для дескрипторов файлов?
-
03-07-2019 - |
Вопрос
Когда я дочитал до конца Программирование на Perl, 2-е издание, Страница 51, что-то меня смущает :
sub newopen {
my $path = shift;
local *FH; #not my!
open (FH, $path) || return undef;
return *FH;
}
$fh = newopen('/etc/passwd');
Мой Я знаю, почему мы не возобновляем использование моего?Пока я не вижу, что что-то пойдет не так, если мы используем my().
Спасибо!
Решение
Банальный ответ заключается в том, что вы должны использовать local
потому что my *FH
это синтаксическая ошибка.
"Правильный" (но не очень поучительный) ответ заключается в том, что вы делаете это неправильно.Вы должны использовать лексические дескрипторы файлов и форму с тремя аргументами open
вместо этого.
sub newopen {
my $path = shift;
my $fh;
open($fh, '<', $path) or do {
warn "Can't read file '$path' [$!]\n";
return;
}
return $fh;
}
Чтобы действительно ответить почему требуется объяснение разницы между лексическими и глобальными переменными, а также между областью действия переменной и ее длительностью.
Область видимости переменной - это та часть программы, где ее имя допустимо.Область видимости - это статическое свойство.С другой стороны, длительность переменной является динамическим свойством.Длительность - это время во время выполнения программы, в течение которого переменная существует и содержит значение.
my
объявляет лексическую переменную.Лексические переменные имеют область видимости от точки объявления до конца заключающего блока (или файла).Вы можете иметь другие переменные с тем же именем в разных областях без конфликта.(Вы также можете повторно использовать имя в перекрывающихся областях, но не делайте этого.) Длительностью лексических переменных управляют с помощью подсчета ссылок.Пока существует хотя бы одна ссылка на переменную, значение существует, даже если имя недопустимо в определенной области! my
также имеет эффект времени выполнения - он выделяет новое переменная с заданным именем.
local
это немного по-другому.Он оперирует глобальными переменными.Глобальные переменные имеют глобальную область видимости (имя допустимо везде) и продолжительность всего срока службы программы.Что local
вносит ли это временное изменение в значение глобальной переменной.Это иногда называют "динамическим определением области". Изменение начинается в точке local
объявление и сохраняется до конца заключающего блока, после чего восстанавливается старое значение.Важно отметить, что новое значение не ограничено блоком - оно видно везде (включая вызываемые подпрограммы).Правила подсчета ссылок по-прежнему применяются, поэтому вы можете взять и сохранить ссылку на локализованное значение после истечения срока действия изменения.
Вернемся к примеру: *FH
является глобальной переменной.Точнее, это "typeglob" - контейнер для набора глобальных переменных.typeglob содержит слот для каждого из основных типов переменных (scalar, array, hash) плюс несколько других параметров.Исторически сложилось так, что Perl использовал typeglobs для хранения дескрипторов файлов и local
- знакомство с ними помогло убедиться, что они не ударили друг друга.Лексические переменные не имеют typeglobs, поэтому говорят my *FH
это синтаксическая ошибка.
В современных версиях Perl лексические переменные могут и должны использоваться вместо них в качестве дескрипторов файлов.И это возвращает нас к "правильному" ответу.
Другие советы
В вашем примере кода вызов встроенной подпрограммы open
использует простое слово в качестве дескриптора файла, которое является эквивалентом глобальной переменной.Как Ответ Натана Феллмана объяснил, используя local
локализует это простое слово в текущем блоке кода в том случае, если другая глобальная переменная с тем же именем определена в другом месте скрипта или модуля.Это предотвратит удаление ранее определенной глобальной переменной при новом объявлении.
Это было очень распространенной практикой в старые времена Perl, но начиная с Perl 5.6 , гораздо лучше использовать скаляр (с my
объявление, на которое вы намекали в своем вопросе) для определения вашего дескриптора файла и, кроме того, используйте вызов с тремя аргументами для open
.
use Carp;
open my $error_log, '>>', 'error.log' or croak "Can't open error.log: $OS_ERROR";
В качестве отступления, пожалуйста, обратите внимание, что для стандартного чтения и записи ввода / вывода все же лучше использовать аргумент two open
:
use Carp;
open my $stdin, '<-' or croak "Can't open stdin: $OS_ERROR";
В качестве альтернативы, вы можете использовать IO::File
модуль для привязки дескриптора файла к классу:
use IO::File;
my $error_log = IO::File->new('error.log', '>>') or croak "Can't open error.log: $OS_ERROR");
Большая часть заслуг здесь принадлежит Дэмиан Конвей, автор превосходной книги Perl Best Practices (Лучшие практики Perl).Если вы серьезно относитесь к разработке на Perl, вы обязаны ради себя приобрести эту книгу.
Почему вы читаете устаревшую книгу?3-е издание вышло уже давно!Какую версию Perl вы используете?2-е издание описывает Perl 5.004 (5.4.x) или что-то около того.
В наши дни вам не следует использовать обозначение typeglob для дескрипторов файлов;используйте "лексические дескрипторы файлов" (см. Открыть, я думаю) или Дескриптор файла модуль или вместо него один из его родственников.
Спасибо Майклу Шверну и Ysth за комментарии, включенные здесь.
Я верю, что это потому, что my
выделяет новую копию переменной в стеке, и она теряется при выходе из блока. local
сохраняет существующий *FH
в другом месте и переопределяет существующий *FH
.Он восстанавливает старый, когда вы выходите из стека.С my
в *FH
typeglob выходит из области видимости, когда вы выходите из блока.С local
он продолжает существовать, так что вы можете продолжать использовать его после того, как вернете.
Я не уверен в этом на 100%, но, возможно, это может указать вам правильное направление.
Смотрите Локализованные Дескрипторы файлов здесь, Я думаю, это все объясняет.