Por que o Programming Perl usa local (não meu) para identificadores de arquivos?

StackOverflow https://stackoverflow.com/questions/613906

  •  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!

Foi útil?

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.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top