Warum kann mein Perlfunktionen sieht den Wert für die Variable in der foreach-Schleife, die sie aufgerufen?
Frage
Ich hoffe, das etwas einfach ist, dass ich falsch mache. Ich sehe etwas online über „Variable Selbstmord“, die gut aussahen, aber es war für eine ältere Version und ich bin auf 5.10.1.
Wie auch immer - eine Variable, dass ich erklärte - $ RootDirectory - nur plötzlich seinen Wert verliert, und ich kann nicht herausfinden, warum.
Hier ist ein Skript, das Problem zu reproduzieren. Als ich durch das Skript im Debug-Modus laufen (perl -d) Ich kann es die $ RootDirectory in Zeile auszudrucken bekommen bis 21 und 26 Aber es ist durch die Linie 30.
gegangenuse 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
}
EDIT Vielen Dank für die Kommentare! Ich denke, jetzt werde ich das „unser“ Schlüsselwort verwenden, das gut zu funktionieren scheint - dank Nathan. Auch dank toolic über die Verwendung Warnungen - ich glaube, ich bin auf, dass man verkauft
!Die Sache, die mich verwirren weiter ist, warum, wenn ich Debug-Modus tat (perl -d) und trat den Code durch „p $ RootDirectory“ tue ich die erwartete Ausgabe in den Zeilen 21 und 26 bekommen, aber nicht Linie 30. Wie ist die Situation anders in Zeile 30?
Auch schätze ich die Kommentare über Best-Practice-$ RootDirectory als Funktionsparameter übergeben wird. Ich wollte vermeiden, dass, weil ich so viele Funktionen haben folgende dass - das heißt RunSchema ruft create die SQLExecFolder nennt. Alle von ihnen haben die gleichen Parameter übergeben haben. Macht es noch Sinn, in diesem Fall machen, oder gibt es bessere Möglichkeiten, dies zu strukturieren?
Lösung
Sie erklären $RootDirectory
als Schleifenvariable in einem foreach
Schleife. Soweit ich verstehe, dass bedeutet, dass Sie den Wert auf der Schleife local
ized wird, und dessen Wert auf den vorherigen Wert am Ende der Schleife wiederhergestellt wird.
In Ihrem Fall die Variable nie zugewiesen wurde, so dass am Ende der Schleife es auf den vorherigen Wert von undef
zurück.
Bearbeiten : Eigentlich ist das Problem, dass $RootDirectory
mit my
erklärt, so dass es in anderen Bereichen nicht definiert ist. In den Funktionen RunSchema
, CreateTables
und SQLExecFolder
die Variable nicht definiert ist, unabhängig von der Lokalisation der foreach
.
Wenn Sie die Variable wollen für strict
ness deklariert werden, sondern wollen, dass es global, declare $RootDirectory
mit our
sein:
our $RootDirectory;
Bearbeiten : Das wird gesagt, es ist nicht immer eine gute Idee, eine globale Variable zu verwenden. Du bist besser dran, die Variable als Parameter an die Funktionen vorbei wie andere vorgeschlagen haben.
Andere Tipps
Was sagte Nathan korrekt ist. Abgesehen davon, warum passieren Sie nicht in dem Wert? Es ist besser Praxis sowieso:
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";
}
Andere haben Ihre Frage richtig beantwortet. Ich möchte nur betonen, dass Sie use warnings;
, um Ihren Code hinzufügen sollten. Es würde einen Hinweis auf Ihr Problem gegeben haben, und es würden Sie auf eine andere mögliche Gefahr aufmerksam zu machen.
foreach
Variable ist etwas Besonderes -. Es auf die Schleife lokal ist
Wenn die Variable mit dem vorangestellt ist Stichwort meint, dann ist es lexikalisch nur scoped und ist daher sichtbar innerhalb der Schleife. Ansonsten der Variable ist implizit lokal für die Schleife und wieder seinen früheren Wert auf Austritt aus der Schleife. Wenn die Variable war zuvor erklärt mit meinem, verwendet es diese Variable anstelle der globalen ein, aber es ist nach wie vor auf die lokalisierte Schleife. Diese implizite Lokalisierungs nur in einer foreach-Schleife auftritt.
Bitte schauen Sie hier
Die Iteratorvariable in foreach Schleife ist immer mit der Schleife lokalisiert. Siehe die foreach Abschnitt in perlsyn . Sie können es zu einem Unterprogramm als Parameter übergeben.
RE: Wenn eine globale Variable verwenden
Globale Variablen sind riskant, weil sie jederzeit von jedem Teil des Codes, die sie zugreift geändert werden können. Darüber hinaus ist es schwierig, wann und wo eine Änderung auftritt, zu verfolgen, was es schwieriger macht unbeabsichtigte Folgen von Modifikation aufzuspüren. Kurz gesagt, jede globale Variable erhöht Kopplung zwischen den Subroutinen, die es verwenden.
Wann ist es sinnvoll, eine globale zu benutzen? Wenn die Vorteile überwiegen die Risiken.
Wenn Sie von den meisten oder alle Ihre Subroutinen benötigt viele unterschiedliche Werte haben, so scheint es wie eine gute Zeit globale Variablen zu verwenden. Sie können jeden Unterprogramm-Aufruf vereinfachen, und den Code klarer, richtig machen?
FALSCH. In diesem Fall ist der richtige Ansatz, all diese verschiedenen Variablen in einer Container-Datenstruktur zu aggregieren. Anstatt also foo( $frob, $grizzle, $cheese, $omg, $wtf );
haben Sie foo( $state, $frob );
Wo $state = { grizzle => $grizzle, cheese => $cheese, omg => $omg, wtf => $wtf };
.
So, jetzt haben wir eine Variable um zu bestehen. Alle diese Unter Anrufe sind viel einfacher. Doch auch so, das ist belastend und Sie wollen immer noch das zusätzliche Argument von jeder Routine aufzuräumen.
An diesem Punkt haben Sie mehrere Möglichkeiten:
- Make
$state
global und nur Zugang direkt. - Make
$state
in ein Konfigurationsobjekt und Methoden anwenden, um den Zugriff auf Attribute. - Machen Sie das gesamte Modul in eine Klasse, und speichern alle die Zustandsinformationen in einem Objekt.
Option 1 ist akzeptabel für kleinen Skripte mit wenigen Routinen. Das Risiko von schwer zu debuggen Fehler klein ist.
Option 2 macht Sinn, wenn es keine offensichtliche Beziehung zwischen den verschiedenen Routinen im Modul ist. einen globalen Statusobjekt unter Verwendung hilft, weil es zwischen Code verringert Kopplung, die es zugreift. Es ist auch einfacher Protokollierung hinzufügen Änderungen an den globalen Daten zu verfolgen.
Option 3 funktioniert gut, wenn Sie eine Gruppe von eng verwandten Funktionen, die auf den gleichen Daten arbeiten.
Ihr Beispielcode scheint wie ein guter Kandidat für die Option 3. Ich eine Klasse namens MySchema
erstellt und alle Methoden, die auf einem bestimmten Verzeichnis arbeiten, sind jetzt Methoden. Das aufrufende Objekt trägt die Daten, die sie mit ihm muss.
Jetzt haben wir schön, sauber Code und keine Globals.
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;
}
Als Bonus den der Code in dem BEGIN Block entfernt und durch ein anderes Skript in einer separaten Datei zur Wiederverwendung platziert werden kann. Alles was es braucht ein vollwertiges Modul zu sein, ist eine eigene Datei MySchema.pm
benannt.
ohne Mühe. Hier sind ein paar kleine Verbesserungen und ein „fix“, die die Variable zu den Subroutinen passieren soll, als Funktionsparameter, da die $RootDirectory
Variable scoped (d beschränkt) innerhalb des foreach
Schleife. Im Allgemeinen ist es auch eine gute Praxis, um explizit zu machen, welche Variablen werden weitergegeben und / oder abgerufen durch verschiedene Subroutinen betrachtet.
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);