2009-03-05 14 views
11

Cuando leo a través de programación Perl, 2ª edición, página 51, algo me confunde:¿Por qué Programming Perl usa local (no my) para filehandles?

sub newopen { 
    my $path = shift; 
    local *FH; #not my! 
    open (FH, $path) || return undef; 
    return *FH; 
} 

$fh = newopen('/etc/passwd'); 

Mi sé, por los cuales no se reanudó a usar mi? Hasta ahora, no puedo ver que algo vaya a salir mal si usamos mi().

Gracias!

+7

Programming Perl, 3ra Edición fue escrita hace mucho tiempo, en una galaxia muy, muy lejana. ¡Y tienes una versión incluso más antigua que eso! –

+0

... y ahora estamos en la 6ª edición, creo que – osirisgothra

Respuesta

25

La respuesta trillada es que tiene que usar local porque my *FH es un error de sintaxis.

La respuesta "correcta" (pero no muy instructiva) es que lo estás haciendo mal. Debería usar filehandles léxicos y la forma de tres argumentos de open.

sub newopen { 
    my $path = shift; 
    my $fh; 
    open($fh, '<', $path) or do { 
     warn "Can't read file '$path' [$!]\n"; 
     return; 
    } 
    return $fh; 
} 

Para responder realmente qué requiere una explicación de la diferencia entre las variables léxicas y mundiales y entre el alcance de una variable y su duración.

El alcance de una variable es la parte del programa donde su nombre es válido. El alcance es una propiedad estática. La duración de una variable, por otro lado, es una propiedad dinámica. La duración es el tiempo durante la ejecución de un programa en el que la variable existe y contiene un valor.

my declara una variable léxica. Las variables léxicas tienen un alcance desde el punto de declaración hasta el final del bloque adjunto (o archivo). Puede tener otras variables con el mismo nombre en diferentes ámbitos sin conflicto. (También puede volver a utilizar un nombre en ámbitos superpuestos, pero no lo haga). La duración de las variables léxicas se gestiona mediante el recuento de referencias. ¡Siempre y cuando haya al menos una referencia a una variable, el valor existe, incluso si el nombre no es válido dentro de un alcance particular! my también tiene un efecto de tiempo de ejecución: asigna una nueva variable con el nombre dado.

local es un poco diferente. Opera en variables globales. Las variables globales tienen un alcance global (el nombre es válido en todas partes) y una duración de toda la vida del programa. Lo que hace local es realizar un cambio temporal en el valor de una variable global. Esto a veces se conoce como "alcance dinámico". El cambio comienza en el punto de la declaración local y persiste hasta el final del bloque delimitador después del cual se restaura el valor anterior. Es importante tener en cuenta que el nuevo valor no está restringido al bloque; es visible en todas partes (incluidas las subrutinas llamadas). Las reglas de recuento de referencias aún se aplican, por lo que puede tomar y mantener una referencia a un valor localizado después de que el cambio haya expirado.

Volver al ejemplo: *FH es una variable global. Más exactamente, es un "typeglob": un contenedor para un conjunto de variables globales. Un typeglob contiene una ranura para cada uno de los tipos de variables básicas (escalar, matriz, hash) más algunas otras cosas. Históricamente, Perl utilizaba typeglobs para almacenar filehandles y local -izing them ayudó a asegurar que no se peleaban entre sí. Las variables léxicas no tienen typeglobs por lo que decir my *FH es un error de sintaxis.

En las versiones modernas de Perl, las variables léxicas pueden y deben usarse como manejadores de archivos en su lugar. Y eso nos lleva de vuelta a la respuesta "correcta".

+1

Gracias. Me gusta este "* FH es una variable global. Más exactamente, es un" typeglob ", un contenedor para un conjunto de variables globales". y me hace entender por qué se necesita local(). –

+0

Bueno, tienes que usar local porque un typeglob como * FH es una cosa de tabla de símbolos, y las variables léxicas no tratan con tablas de símbolos. Creo que confunde las cosas para hablar sobre el alcance para explicarlo. –

12

¿Por qué está leyendo un libro desactualizado? ¡La 3ra Edición ha estado fuera por mucho tiempo! ¿Qué versión de Perl estás usando? La 2da Edición describe Perl 5.004 (5.4.x) o más o menos.

En estos días, no debe usar la notación typeglob para identificadores de archivos; utilice los 'manejadores de archivos léxicos' (vea open, creo) o el módulo FileHandle, o uno de sus parientes en su lugar.


Gracias a Michael Schwern y Ysth para comentarios incorporados aquí.

+0

En la vida real, es necesario lidiar con una gran cantidad de código heredado. –

+0

La 2da edición es 5.004ish, creo (5.4.x en el lenguaje moderno). – ysth

+0

@Yan En el mundo real, pocas personas usan nada anterior a 5.6. @Jonathan FileHandle es excesivo para simplemente abrir y leer un archivo. Manejadores de archivos léxicos ++ – Schwern

0

Creo que es porque my asigna una nueva copia de la variable en la pila, y se pierde al salir del bloque. local guarda el *FH existente en otro lugar y anula el *FH existente. Restaura el antiguo cuando salgas de la pila. Con my, *FH typeglob sale del alcance cuando sale del bloque. Con local sigue existiendo, por lo que puede seguir usándolo después de devolverlo.

No estoy 100% seguro de esto, pero tal vez pueda indicarle la dirección correcta.

+3

Tiene parcialmente razón, está parcialmente equivocado y muy borroso. : D La explicación completa es demasiado larga para caber en un comentario, así que agregué una nueva respuesta (e incluso esa es una versión abreviada). –

0

Ver Filehandles localizados here, supongo que eso lo explica.

20

En su código de ejemplo, la llamada a la subrutina integrada open está utilizando una palabra vacía como el identificador de archivo, que es el equivalente de una variable global. Como se explicó en Nathan Fellman's answer, al usar local, se localizará esta palabra vacía en el bloque de código actual, en caso de que se defina otra variable global con el mismo nombre en cualquier parte del script o módulo. Esto evitará que la nueva declaración elimine la variable global previamente definida.

Esta era una práctica muy común en los viejos tiempos de Perl, pero como de Perl 5.6 es mucho mejor usar un escalar (con la declaración my que se insinuó en su pregunta) para definir el identificador de archivo y , adicionalmente, use la llamada de tres argumentos al open.

use Carp; 
open my $error_log, '>>', 'error.log' or croak "Can't open error.log: $OS_ERROR"; 

Como acotación al margen, tenga en cuenta que para la entrada de lectura estándar/salida y la escritura, todavía es mejor utilizar los dos argumento open:

use Carp; 
open my $stdin, '<-' or croak "Can't open stdin: $OS_ERROR"; 

Como alternativa, puede utilizar el módulo IO::File a bendiga el identificador de archivo a la clase:

use IO::File; 
my $error_log = IO::File->new('error.log', '>>') or croak "Can't open error.log: $OS_ERROR"); 

la mayoría de crédito aquí va a Damian Conway, author of the excellent book Perl Best Practices. Si se toma en serio el desarrollo de Perl, debe comprar este libro.

+2

+1. Básicamente, antes de 5.6, los manejadores de archivos no eran del todo de primera clase en Perl, lo que requería todo tipo de hacks desagradables para pasarlos dentro y fuera de las funciones. También * por favor * use la nueva sintaxis de 3-arg open(); de lo contrario, los nombres de archivo inusuales causarán todo tipo de caos. –

+0

Afortunadamente es obvio que mi súplica está dirigida hacia el que pregunta :) –

Cuestiones relacionadas