pagina - ¿Por qué mi subprograma Perl no puede ver el valor de la variable en el bucle foreach que lo llamó?
perl pagina oficial (7)
La variable del iterador en el bucle foreach siempre se localiza en el bucle. Ver la sección foreach en perlsyn . Puede pasarlo a una subrutina como parámetro.
Espero que esto sea algo directo que estoy haciendo mal. Vi algo en línea sobre "suicidio variable" que se veía bien, pero era para una versión anterior y estoy en 5.10.1.
De todos modos, una variable que he declarado, $ RootDirectory, de repente pierde su valor, y no puedo entender por qué.
Aquí hay un script para reproducir el problema. Cuando ejecuto el script en modo de depuración (perl -d) puedo hacer que imprima $ RootDirectory en la línea 21 y 26. Pero ya no está en la línea 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
}
EDITAR ¡Gracias por todos los comentarios! Creo que por ahora usaré la palabra clave "nuestro" que parece funcionar bien, gracias Nathan. También gracias a la herramienta sobre las advertencias de uso. ¡Creo que me he vendido!
Lo que continúa confundiéndome es por qué, cuando hice el modo de depuración (perl -d), y puse el código, haciendo "p $ RootDirectory" obtuve el resultado esperado en las líneas 21 y 26, pero no en la línea 30. Cómo es la situación diferente en la línea 30?
Además, agradezco los comentarios sobre la mejor práctica de pasar $ RootDirectory como un parámetro de función. Quería evitar eso porque tengo muchas funciones a continuación, es decir, RunSchema llama a CreateTables que llama a SQLExecFolder. Todos ellos deberían tener el mismo parámetro aprobado. ¿Sigue teniendo sentido en este caso, o hay mejores formas de estructurar esto?
Lo que Nathan dijo es correcto. Aparte de eso, ¿por qué no pasas el valor? Es una mejor práctica de todos modos:
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";
}
Otros han contestado su pregunta correctamente. Solo quiero enfatizar que debes agregar use warnings;
a tu código Le habría dado una pista sobre su problema y lo alertaría sobre otro peligro potencial.
foreach
variable foreach
es especial: es local para el ciclo.
Si la variable está precedida por la palabra clave my, tiene un alcance léxico y, por lo tanto, es visible solo dentro del ciclo. De lo contrario, la variable es implícitamente local para el ciclo y recupera su valor anterior al salir del ciclo. Si la variable fue declarada previamente con mi, usa esa variable en lugar de la global, pero aún está localizada en el bucle. Esta localización implícita ocurre solo en un ciclo foreach.
Por favor, eche un vistazo aquí
RE: ¿Cuándo usar una variable global?
Las variables globales son riesgosas porque pueden cambiarse en cualquier momento por cualquier parte del código que acceda a ellas. Además, es difícil rastrear cuándo y dónde ocurre un cambio, lo que hace que sea más difícil rastrear las consecuencias involuntarias de la modificación. En resumen, cada variable global aumenta el acoplamiento entre las subrutinas que lo utilizan.
¿Cuándo tiene sentido usar un global? Cuando los beneficios superan los riesgos.
Si tiene muchos valores diferentes que necesitan la mayoría o todas sus subrutinas, parece un buen momento para usar variables globales. Puede simplificar cada invocación de subrutina y hacer que el código sea más claro, ¿verdad?
INCORRECTO. En este caso, el enfoque correcto es agregar todas esas variables distintas en una estructura de datos de contenedor. Entonces, en lugar de foo( $frob, $grizzle, $cheese, $omg, $wtf );
tienes foo( $state, $frob );
Donde $state = { grizzle => $grizzle, cheese => $cheese, omg => $omg, wtf => $wtf };
.
Entonces ahora tenemos una variable para pasar. Todas esas llamadas secundarias son mucho más simples. Sin embargo, aun así, esto es oneroso y aún desea limpiar el argumento adicional de cada rutina.
En este punto, tienes varias opciones:
- Haga que
$state
global y solo acceda a él directamente. - Convierta
$state
en un objeto de configuración y use métodos para controlar el acceso a los atributos. - Convierta todo el módulo en una clase y almacene toda la información de estado en un objeto.
La opción 1 es aceptable para pequeños scripts con pocas rutinas. El riesgo de errores difíciles de depurar es pequeño.
La opción 2 tiene sentido cuando no hay una relación obvia entre las diferentes rutinas en el módulo. Usar un objeto de estado global ayuda porque disminuye el acoplamiento entre el código que accede a él. También es más fácil agregar el registro para rastrear los cambios en los datos globales.
La opción 3 funciona bien si tiene un grupo de funciones estrechamente relacionadas que operan con los mismos datos.
Su código de muestra parece ser un buen candidato para la opción 3. MySchema
una clase llamada MySchema
y todos los métodos que operan en un directorio específico ahora son métodos. El objeto invocado lleva consigo los datos que necesita.
Ahora tenemos un código agradable y limpio y no globales.
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;
}
Como beneficio adicional, el código en el bloque BEGIN se puede quitar y colocar en un archivo separado para su reutilización por otro script. Todo lo que necesita para ser un módulo completo es su propio archivo llamado MySchema.pm
.
Está declarando $RootDirectory
como la variable de bucle en un bucle foreach
. Por lo que yo entiendo, eso significa que su valor se localiza en el bucle, y su valor se restablece a su valor anterior al final del bucle.
En su caso, la variable nunca fue asignada, por lo que al final del ciclo regresa a su valor anterior de undef
.
Editar : En realidad, el problema es que $RootDirectory
se declara con my
, por lo que no está definido en otros ámbitos. En las funciones RunSchema
, CreateTables
y SQLExecFolder
la variable no está definida, independientemente de la localización de foreach
.
Si desea que la variable se declare para strict
ness, pero quiere que sea global, declare $RootDirectory
con our
:
our $RootDirectory;
Editar : Una vez dicho esto, no siempre es una buena idea usar una variable global. Es mejor que pase la variable como un parámetro a las funciones que otros han sugerido.
No es un mal esfuerzo. Aquí hay un par de pequeñas mejoras, y una "corrección" que es pasar la variable a las subrutinas, como un parámetro de función porque la variable $RootDirectory
tiene un ámbito (es decir, restringido) dentro del bucle foreach
. En general, también se considera una buena práctica para hacer explícitas qué variables están siendo aprobadas y / o accedidas por varias subrutinas.
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);