Por que o Programming Perl usa local (não meu) para identificadores de arquivos?
-
03-07-2019 - |
Pergunta
Quando eu li Programação Perl, 2ª edição, página 51, algo me confunde:
sub newopen {
my $path = shift;
local *FH; #not my!
open (FH, $path) || return undef;
return *FH;
}
$fh = newopen('/etc/passwd');
Meu eu sei, por que não recomeçamos a usar o meu?Até agora, não vejo nada de errado se usarmos my().
Obrigado!
Solução
A resposta banal é que você tem que usar local
porque my *FH
é um erro de sintaxe.
A resposta “certa” (mas não muito esclarecedora) é que você está fazendo errado.Você deve usar identificadores de arquivo lexicais e a forma de três argumentos de open
em vez de.
sub newopen {
my $path = shift;
my $fh;
open($fh, '<', $path) or do {
warn "Can't read file '$path' [$!]\n";
return;
}
return $fh;
}
Para realmente responder por que requer uma explicação da diferença entre variáveis lexicais e globais e entre o escopo de uma variável e sua duração.
O escopo de uma variável é a parte do programa onde seu nome é válido.O escopo é uma propriedade estática.A duração de uma variável, por outro lado, é uma propriedade dinâmica.Duração é o tempo durante a execução de um programa em que a variável existe e mantém um valor.
my
declara uma variável lexical.Variáveis lexicais têm um escopo desde o ponto de declaração até o final do bloco (ou arquivo) envolvente.Você pode ter outras variáveis com o mesmo nome em escopos diferentes sem conflito.(Você também pode reutilizar um nome em escopos sobrepostos, mas não faça isso.) A duração das variáveis lexicais é gerenciada por meio da contagem de referências.Desde que haja pelo menos uma referência a uma variável, o valor existe, mesmo que o nome não seja válido dentro de um escopo específico! my
também tem um efeito de tempo de execução - ele aloca um novo variável com o nome fornecido.
local
é um pouco diferente.Ele opera em variáveis globais.As variáveis globais têm um escopo global (o nome é válido em todos os lugares) e uma duração de toda a vida do programa.O que local
faz é fazer uma mudança temporária no valor de uma variável global.Isso às vezes é chamado de "escopo dinâmico". A mudança começa no ponto do local
declaração e persiste até o final do bloco envolvente, após o qual o valor antigo é restaurado.É importante notar que o novo valor não está restrito ao bloco - ele é visível em todos os lugares (inclusive nas chamadas sub-rotinas).As regras de contagem de referência ainda se aplicam, portanto você pode obter e manter uma referência a um valor localizado após a alteração expirar.
De volta ao exemplo: *FH
é uma variável global.Mais precisamente, é um “typeglob” – um contêiner para um conjunto de variáveis globais.Um typeglob contém um slot para cada um dos tipos básicos de variáveis (escalar, array, hash) além de algumas outras coisas.Historicamente, Perl usou typeglobs para armazenar filehandles e local
-izá-los ajudou a garantir que eles não se derrotassem.Variáveis lexicais não possuem typeglobs, por isso dizer my *FH
é um erro de sintaxe.
Nas versões modernas do Perl, variáveis lexicais podem e devem ser usadas como manipuladores de arquivos.E isso nos traz de volta à resposta “certa”.
Outras dicas
No seu código de exemplo, a chamada para a sub -rotina incorporada open
está usando uma palavra nu como identificador de arquivo, que equivale a uma variável global. Como Resposta de Nathan Fellman explicado, usando local
Localizará essa palavra nu no bloco de código atual, caso outra variável global com o mesmo nome seja definida em outras partes do script ou módulo. Isso impedirá que a variável global previamente definida seja eliminada pela nova declaração.
Essa era uma prática muito comum nos velhos dias, mas A partir de perl 5.6, é muito melhor usar um escalar (com o my
Declaração que você sugeriu em sua pergunta) para definir o seu identificador de arquivo e, além disso, use o argumento de três argumentos para open
.
use Carp;
open my $error_log, '>>', 'error.log' or croak "Can't open error.log: $OS_ERROR";
Como um aparte, observe que, para leitura e escrita padrão de entrada/saída, ainda é melhor usar os dois argumentos open
:
use Carp;
open my $stdin, '<-' or croak "Can't open stdin: $OS_ERROR";
Como alternativa, você pode usar o IO::File
Módulo para abençoar o identificador de arquivo para a classe:
use IO::File;
my $error_log = IO::File->new('error.log', '>>') or croak "Can't open error.log: $OS_ERROR");
A maioria do crédito aqui vai para Damian Conway, autor do excelente livro Perl Best Practices. Se você é sério sobre o desenvolvimento da Perl, deve a si mesmo comprar este livro.
Por que você está lendo um livro desatualizado. A 3ª edição está fora há muito tempo! Qual versão do Perl você está usando? A 2ª edição descreve o Perl 5.004 (5.4.x) ou o que há.
Hoje em dia, você não deve usar a notação do TypeGlob para alças de arquivo; Use as 'linhas de arquivo lexical' (veja abrir, Eu acho) ou o FileHandle módulo, ou um de seus parentes.
Agradecemos a Michael Schwern e Ysth pelos comentários incorporados aqui.
Eu acredito que é porque my
aloca uma nova cópia da variável na pilha e está perdida quando você sai do bloco. local
salva os existentes *FH
em outros lugares e substitui o existente *FH
. Ele restaura o antigo quando você sai da pilha. Com my
a *FH
O TypeGlob sai do escopo quando você sai do bloco. Com local
Ele continua existente, para que você possa continuar usando -o depois de devolvê -lo.
Não tenho 100% de certeza disso, mas talvez possa apontar na direção certa.
Consulte FileHandles localizados aqui, Acho que isso explica isso.