Warum kann mein Perlfunktionen sieht den Wert für die Variable in der foreach-Schleife, die sie aufgerufen?

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

  •  20-09-2019
  •  | 
  •  

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.

gegangen
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
} 

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?

War es hilfreich?

Lösung

Sie erklären $RootDirectory als Schleifenvariable in einem foreach Schleife. Soweit ich verstehe, dass bedeutet, dass Sie den Wert auf der Schleife localized 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 strictness 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:

  1. Make $state global und nur Zugang direkt.
  2. Make $state in ein Konfigurationsobjekt und Methoden anwenden, um den Zugriff auf Attribute.
  3. 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);
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top