Dynamische Geltungsbereiche: Auswirkungen auf die Mustererkennung

(Auszug aus "Reguläre Ausdrücke" von Jeffrey E. F. Friedl)

Die zwei Arten von Variablen in Perl (globale und private) und das Konzept von dynamischen Geltungsbereichen sind an sich schon interessant genug. Für unser Thema sind sie vor allem für die Variablen wichtig, die nach dem Matching ausgewertet werden können. In den nächsten Abschnitten wird dieses Konzept erläutert, besonders auch im Zusammenhang mit regulären Ausdrücken.

Globale und private Variablen

Vereinfacht gesagt kennt Perl zwei Typen von Variablen: globale und private. Private Variablen werden mit my(...) deklariert. Globale Variablen brauchen nicht deklariert zu werden, sie werden mit dem ersten Gebrauch automatisch erzeugt. Auf globale Variablen kann von überall im Programm zugegriffen werden, private Variablen sind nur innerhalb des sie lexikalisch einschließenden Blocks sichtbar. Nur im Code zwischen der my-Deklaration und dem Ende des Blocks, der das my einschließt, kann auf eine private Variable zugegriffen werden.

Der Gebrauch von globalen Variablen gilt als ein Zeichen von schlechtem Programmierstil, außer bei der Vielzahl der globalen Spezialvariablen wie $1, $_ und @ARGV. Normale, vom Benutzer eingeführte Variablen sind zunächst global, außer sie werden mit my deklariert. Globale Variablen können in Namensräumen namens Packages untergebracht werden, aber es sind noch immer globale Variablen. Eine globale Variable $Debug im Package Acme::Widget hat den vollständig qualifizierten Namen $Acme::Widget::Debug, aber ganz gleich, wie man sie anspricht, es ist in jedem Fall eine globale Variable. Wenn Sie use strict; benutzen, müssen alle globalen Variablen (außer den Spezialvariablen) mit vollständig qualifizierten Namen verwendet oder aber mit our deklariert werden (our deklariert einen Namen, es erzeugt keine neue Variable – siehe dazu die Perl-Dokumentation).

Werte mit dynamischem Geltungsbereich

Dynamisches Scoping ist ein interessantes Konzept, das nur in wenigen Programmiersprachen vorkommt. Wir werden den Bezug zu regulären Ausdrücken bald erkennen. Kurz gesagt speichert Perl dabei den Wert der globalen Variablen, die man modifizieren will. Mit der Variablen kann man tun, was einem beliebt. Wenn der Block verlassen wird, erhält die globale Variable automatisch wieder ihren alten Wert. Das Abspeichern des ursprünglichen Werts nennt man Erzeugen eines neuen dynamischen Geltungsbereichs oder Lokalisieren.

Es gibt einige Gründe, warum man so etwas tun möchte; zum Beispiel wenn man vorübergehend den Zustand des Programms in einer globalen Variablen verändern möchte. Nehmen wir an, das Package Acme::Widget habe eine Variable namens $Acme::Widget::Debug, mit der man Informationen für das Debugging enthält, wenn sie auf einen Wert ungleich null gesetzt wird. Man kann dann dieses Debugging temporär einschalten:

  {
    local($Acme::Widget::Debug) = 1; # Debugging einschalten.
    # Acme::Widget mit Debugging verwenden.
       .
       . 
       .
  }
# $Acme::Widget::Debug hat wieder den alten Wert.

Die Funktion mit dem völlig irreführenden Namen local erzeugt einen neuen dynamischen Geltungsbereich. Ich sage es hier sehr deutlich: local erzeugt keine neue Variable. local bewirkt eine Aktion, es ist keine Variablendeklaration.

Der Aufruf von local mit einer globalen Variable als Argument bewirkt Folgendes:

  1. Der alte Wert der alten Variablen wird abgespeichert.
  2. Die Variable erhält einen neuen Wert (den, der dem local-Operator zugewiesen wird, sonst undef).
  3. Beim Verlassen des Blocks, der die local-Direktive enthält, wird der gespeicherte Wert wieder in der alten globalen Variablen verfügbar.

Das »local« bezieht sich also mehr auf den zeitlichen Ablauf. Der lokalisierte Wert existiert nur so lange, wie der Code im umschließenden Block aktiv ist. Auch von einer Subroutine aus, die aus diesem Block aufgerufen wird, ist nur der neue, lokalisierte Wert der Variablen sichtbar (die Variable ist nämlich nach wie vor eine globale Variable). Der einzige Unterschied zu einer nicht lokalisierten globalen Variablen ist der Umstand, dass beim Verlassen des Blocks automatisch wieder der alte Wert hervorgeholt wird.

Automatisches Abspeichern und Wiederhervorholen – viel mehr macht local tatsächlich nicht. Trotz all der Missverständnisse, die durch local verursacht werden, ist es nicht komplizierter, als in der folgenden Tabelle dargestellt.

Tabelle: Die Bedeutung von local.

Normales Perl Äquivalente Bedeutung
{ {
local($Var); # Wert abspeichern my $Temp = $Var;
$Var = undef;
$Var = 'Neuer Wert'; $Var = 'Neuer Wert';
.
.
.
.
.
.
$Var = $Temp;
} # $Var erhält wieder den alten Wert }

Bequemerweise kann man local($Var) direkt einen Wert zuweisen. Das ist exakt dasselbe wie eine separate Zuweisung unmittelbar nach dem local. Die Klammern kann man weglassen, man erzwingt so einen skalaren Kontext.

Ein Beispiel aus der Praxis: Eine Bibliotheksfunktion wurde unsorgfältig geschrieben und erzeugt eine Menge von Warnungen der Art ›Use of uninitialized value...‹. Wie alle guten Perl-Programmierer benutzen Sie die -w-Option, aber der Autor der Bibliotheksfunktion hat das offenbar nicht getan. Das ist sehr störend, aber die Bibliotheksfunktion lässt sich nicht ändern – was kann man tun, außer auf -w verzichten? Nun, man kann für den Aufruf dieser miesen Funktion die Variable $^W mit local auf Null setzen ($^W entspricht der -w-Befehlszeilen-Option; man kann den Bezeichner ^W als zwei Zeichen, Zirkumflex und W, schreiben oder auch als wirkliches Control-W).

{
    local $^W = 0; # Warnungen ausschalten
    UebleFunktion(...);
}
# nach dem Verlassen des Blocks hat  $^W wieder den alten Wert

Mit dem Aufruf von local wird eine interne Kopie des Werts von $^W angelegt. Das gleiche $^W erhält den neuen Wert Null gleich bei der local-Anweisung. Wenn UebleFunktion aufgerufen wird – und auch innerhalb dieser Funktion –, sieht Perl nur diesen Wert und gibt keine Warnungen aus. Nach dem Funktionsaufruf hat $^W noch immer den Wert Null.

Bis hierhin ist das nicht anders, wie wenn wir local nicht benutzt, sondern nur $^W auf den Wert Null gesetzt hätten. Wenn aber der Block verlassen wird, bekommt $^W automatisch wieder den Wert, den es vor der local-Anweisung hatte. Die Änderung war lokal im zeitlichen Sinne, galt also nur während der Lebenszeit des Blocks. Den gleichen Effekt erhielte man mit einer expliziten temporären Variable wie in der obigen Tabelle, aber local macht die Sache einfacher.

Der Vollständigkeit halber untersuchen wir, was passiert, wenn my statt local verwendet wird. (Anmerkung: Eine akademische Übung: Perl lässt die Verwendung von my mit dieser Spezialvariablen gar nicht zu.) Mit my wird eine neue Variable erzeugt, die zunächst den undefinierten Wert besitzt. Diese Variable ist nur im lexikalischen Block sichtbar, in dem sie deklariert wurde (genauer gesagt, nur zwischen der my-Deklaration und dem Ende des Blocks, in dem das my erscheint). Dabei besteht keinerlei Verbindung mit irgendeiner anderen Variablen, auch nicht mit einer globalen Variable gleichen Namens. Die neue Variable ist von außerhalb des Blocks nicht sichtbar, auch nicht von einer daraus aufgerufenen Funktion wie UebleFunktion. In unserem Beispiel würde also eine Variable $^W erzeugt, ihr würde der Wert Null zugewiesen, aber später wird sie überhaupt nie gebraucht. (Innerhalb von UebleFunktion verwendet Perl die globale Variable $^W, die nicht mit der eben erzeugten my-Variablen zusammenhängt. Warnungen werden also nach wie vor ausgegeben.)

Eine Analogie: Klarsichtfolien

Eine nützliche Analogie zu local ist eine Klarsichtfolie, wie man sie bei Overhead-Projektoren benutzt, auf der man über dem Text darunter etwas hinkritzeln und eine Variable im Wortsinne überschreiben kann. Sie (und jeder, der hinschaut, also auch Unterprogramme und Interrupt-Handler) sehen nur den neuen Text. Das Original wird überdeckt – bis zu dem Zeitpunkt, da der Block verlassen wird; dann wird die Klarsichtfolie automatisch entfernt, mit allen Änderungen, die seit dem local, dem Auflegen der Klarsichtfolie, vorgenommen wurden.

Diese Analogie ist auch zutreffender als die Umschreibung »der ursprüngliche Wert wird abgespeichert«. local macht nicht eigentlich eine Kopie, sondern fügt den neuen Wert weiter vorne in einer Liste von Werten ein, die jedes Mal konsultiert wird, wenn die Variable benutzt wird. Der ursprüngliche Wert wird verdeckt. Beim Verlassen eines Blocks werden alle Werte aus der Liste entfernt, die seit dem Anfang des Blocks dazugekommen sind. Werte können mit local zu dieser Liste hinzugefügt werden, aber jetzt kommen wir zu dem Grund, warum wir dieses Lokalisieren von Variablen so genau betrachtet haben: Die Nebeneffekt-Variablen von regulären Ausdrücken erhalten automatisch einen dynamischen Geltungsbereich.

Regex-Nebeneffekte und dynamische Geltungsbereiche

Was hat denn ein dynamischer Geltungsbereich mit regulären Ausdrücken zu tun? Eine ganze Menge. Einige Variablen werden bei jedem erfolgreichen Matching gesetzt, das sind Variablen wie $& (der Text des ganzen Treffers) und $1 (der Text aus dem ersten Klammerausdruck); diese werden im nächsten Abschnitt genauer behandelt. Wenn diese Variablen zum ersten Mal in einem Block gesetzt werden (durch eine erfolgreich passende Regex), dann wird ihnen automatisch ein neuer dynamischer Geltungsbereich zugeteilt.

Um darin einen Vorteil zu erkennen, muss man sehen, dass zum Beispiel auch jedes Unterprogramm einen neuen Block darstellt. Wenn in der Funktion eine Regex verwendet wird, wird für die Nebeneffekt-Variablen, die sich daraus ergeben, automatisch ein neuer dynamischer Geltungsbereich geschaffen. Weil die Werte nach dem Verlassen des Blocks (das heißt, wenn aus dem Funktionsaufruf zurückgekehrt wird) wieder dieselben sind wie vorher, kann ein Unterprogramm die Werte nicht verändern.

Ein Beispiel:

if (m/(...)/) {
    MachIrgendwasAnderes();
    print "Gefundener Text: $1.\n";
}

Weil $1 in jedem Block, in dem es gesetzt wird, seinen neuen dynamischen Geltungsbereich erhält, braucht sich das Programmstück nicht darum zu kümmern, ob die Routine MachIrgendwasAnderes eventuell eine Regex enthält, die $1 verändern könnte. Jede Änderung an $1 im Unterprogramm ist auf den durch die Routine gebildeten Block (oder einen Unterblock) beschränkt. Also kann der Funktionsaufruf dem $1 in der print-Anweisung nichts anhaben.

Das automatische dynamische Scoping kann auch da von Nutzen sein, wo man zunächst gar kein Problem erkennen kann:

if ($resultat =~ m/ERROR=(.*)/) {
   warn "He, melde $Config{perladmin} den Fehler: $1!\n";
}

Das Modul Config aus der Standardbibliothek definiert das assoziative Array %Config; das Element $Config{perladmin} enthält die E-Mail-Adresse des lokalen Perl-Administrators. Dieses Programmbeispiel wäre sehr verwirrend, wenn kein automatisches dynamisches Scoping für $1 durchgeführt würde. %Config ist nämlich kein simpler Hash, sondern es ist eine an ein Package gebundene Variable (tied variable), bei deren Gebrauch hinter den Kulissen ein Unterprogrammaufruf abläuft. Die hierbei aufgerufene Funktion benutzt tatsächlich reguläre Ausdrücke, die zwischen der Regex in der if-Anweisung und dem Gebrauch von $1 auftreten. Wenn die Variable in der Funktion nicht ihren eigenen dynamischen Geltungsbereich hätte, überschriebe sie »unser« $1. Aber so sind irgendwelche Änderungen durch $Config{...}-Routinen nicht möglich.

Dynamischer und lexikalischer Geltungsbereich

Das dynamische Scoping hat viele Vorteile, wenn es richtig genutzt wird, aber die unbedachte Anwendung von local kann zu sehr undurchsichtigen Programmen führen. Beim Lesen eines Programms ist die local-Anweisung oft sehr weit vom Gebrauch dieser Variablen in einem Block in einer Subroutine entfernt.

Wie erwähnt, erzeugt my(...) eine neue private Variable mit lexikalischem Geltungsbereich. Das ist ungefähr das Gegenstück zu einer globalen Variable, hat aber fast nichts mit dynamischem Scoping zu tun (außer dass man mit my erzeugte Variablen nicht localisieren kann). local löst eine Aktion aus, my dagegen beinhaltet eine Aktion und eine Deklaration.

  

<< zurück vor >>

 

 

 

Tipp der data2type-Redaktion:
Zum Thema Reguläre Ausdrücke bieten wir auch folgende Schulungen zur Vertiefung und professionellen Fortbildung an:
   

Copyright der deutschen Ausgabe © 2008 by O’Reilly Verlag GmbH & Co. KG
Für Ihren privaten Gebrauch dürfen Sie die Online-Version ausdrucken.
Ansonsten unterliegt dieses Kapitel aus dem Buch "Reguläre Ausdrücke" denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

O’Reilly Verlag GmbH & Co. KG, Balthasarstr. 81, 50670 Köln