You are here: Perldoc Web>Perlglobtut (2007-09-06)
perlglobtut Dokumentation | Download als POD | Wie kann ich hier etwas ändern?

NAME

perlglobtut - Eine Beschreibung von Perls globalen Variablen und deren Implementierung mittels Stashes und Typeglobs

BESCHREIBUNG

Deses Dokument beschreibt Perls globale (Paket-) Variable und deren Implementierung. Die Implementierung lokaler (lexikalischer) Variable und damit einhergehend die Beschreibung von Scopes , der Direktiven my und our und des Befehls local ist in perlscopetut zu finden.

HINWEIS: Dieser Text enthält Fußnoten, die wie folgt gekennzeichnet sind: L<[1]|/item__5b1_5d>. Mit Browsern, die ein Navigieren im Text mittels Verweise unterstützen, kann man direkt durch Klicken auf die Zahl zur Fußnote gelangen; dort findet sich dann ein Verweis, der zum Haupttext zurückführt.

Globale Variable

Globale Variable gibt es, seitdem es Perl gibt - wird eine Variable nicht deklariert, so wird sie automatisch als global eingerichtet. Globale Variable sind im gesamten Programm sichtbar, sogar aus Code, der aus anderen Dateien hinzugeladen wurde (manche Module verwenden solche Variable zu Konfigurations- oder Debuggingzwecken). Es gibt keine "eingeschränkte Sicht" - eine globale Variable ist immer sichtbar; allerdings kann es notwendig werden, zum Variablennamen selbst auch den Namensraum ( Package ) anzugeben, in dem die Variable angelegt wurde, um sie ansprechen zu können.

Der local Befehl ermöglicht das temporäre Wegsichern und Ersetzen eines Variablenwertes (der automatisch zu einem späteren Zeitpunkt wieder hergestellt wird), aber die Variable selbst bleibt dieselbe und kann jederzeit von überall angesprochen werden.

Stashes und Packages

Ganz allgemein repräsentiert eine Variable ein Stück benannten Speichers. Daher geht es beim Zugriff darauf zunächst immer um die Umsetzung eines Namens in einen Adresswert.

Perl besitzt bereits von Haus aus eine Einrichtung zum Umsetzen von Namen in anderen Werte - den Hash (assoziatives Array). Es wird daher wenig verwundern, dass auch globale Variable in Form von Hashes implementiert sind. Jeder Hash repräsentiert einen Namensraum (package); die Keys der Hashes sind die Namen aller Symbole, die in diesem Package existieren (Variable-, Funktions- und Handlenamen). Solche Hashes werden als Symboltabellen verwendet und man bezeichnet sie daher auch als Symbol Table Hashes oder kurz Stashes .

Eine globale Variable gehört immer genau einem Package an, kann aber auch aus allen anderen Packages durch Vorsetzen des Stashnamens angesprochen werden. Stash- und Packagenamen sind identisch, allerdings muss man Stashnamen, so man sie direkt verwendet, zwei Doppelpunkte nachsetzen, damit sie der Parser als solche erkennt. Der Name des Stashes, der das package hugo bereitstellt, lautet somit %hugo:: (beachte das führende "%", es handelt sich ja um einen Hash). Der Name des Stashes jenes Packages, das von Perl verwendet wird, wenn nichts anderes ausgewählt wurde ( Default Package ), lautet %main:: .

Die package Direktive definiert zum Kompilierungs-Zeitpunkt ein neues Package. Sie sorgt dafür, dass der zugehörige Stash angelegt wird, falls er noch nicht existiert, und dass ab dann alle neuen Symbole, die nicht voll qualifiziert (d.h., mit expliziter Angabe eines Packagenamens versehen) oder ausdrücklich als lexikalisch gekennzeichnet sind, in den Stash als neue Elemente aufgenommen werden L<[2]|/item__5b2_5d>.

Zur Laufzeit weiß dann der Interpreter, in welchem Package nach den jeweiligen Variablen Ausschau zu halten ist L<[3]|/item__5b3_5d>.

Genauso, wie ein Hash andere Hashes (in Form von Referenzen) enthalten kann, so kann ein Stash weitere Stashes enthalten, wenn - vereinfacht ausgedrückt - in seinen Elementen keine Symbole, sondern wiederum Stashes abgelegt sind. Dadurch lassen sich Packages in der bekannten Weise

  Alpha::Beta::Gamma

verschachteln. Dennoch bleibt jeder Stash eigenständig und Variablen innerhalb der Stashes stehen in keinerlei Beziehung zueinander. Die Stashes selbst sind miteinander derart verbunden, dass der übergeordnete Stash einen Verweis auf den untergeordneten enthält (in obigem Beispiel enthält also %Alpha:: ein Element, das auf %Beta:: zeigt, und dieser Stash wiederum ein Element auf %Gamma:: ). Damit wird auch das rekursive Durchsuchen von Symboltabellen möglich (siehe das Beispiel im Abschnitt Bearbeiten von Symboltabellen?).

Stashelemente

Die Elemente eines Stashes benennen Symbole , d.h., die Namen von Variablen, Funktionen, Formaten oder Handles, die in dem durch den Stash repräsentierten Package angelegt wurden. Diese Elemente kann man genauso wie die Elemente eines jeden anderen Hashes ansprechen, so z.B. das Element test im Package hugo :

  $hugo::{test}

Beachte, dass die Doppelpunkte hier nicht als Trenner zwischen Package- und Variablennamen verwendet werden; sie sind vielmehr Teil des Namens des Stashes %hugo:: . Im Unterschied dazu steht

  $hugo{test}

für das Element test des "normalen" Hashes %hugo .

Symbolelemente im Default Package %main:: werden genauso angesprochen, so wird das Element test mit

  $main::{test}

bezeichnet.

Beachte, dass die Doppelpunkte am Namensende nur für Hashes eine besondere Bedeutung haben, sie werden hierdurch als Stash gekennzeichnet. Für alle anderen Wertetypen von Perl sind sie bedeutungslos; der Skalar $hallo:: und das Array @hallo:: unterscheiden sich durch nichts von anderen Variablen (außer vielleicht durch den auffälligen Namen). Die Doppelpunkte sind aber dennoch Teil des Namens und daher sind $hallo:: und $hallo zwei unterschiedliche Variable.

Perl versteckt den Zugriff auf Stashes und deren Elemente nicht, sondern behandelt sie wie jeden anderen Hash, d.h., Hash-Operatoren wie each , exists , keys , values und delete können wie gewohnt verwendet werden, wodurch das Durchsuchen und Bearbeiten von Symboltabellen aus Perl-Code heraus möglich wird. Hiervon machen auch etliche Module Gebrauch (Beispiele dazu finden sich im Abschnitt Bearbeiten von Symboltabellen?).

Dieser Ansatz birgt natürlich auch gewisse Gefahren - so sollte man sich darüber im Klaren sein, dass in einem Skript nach Ausführen von

  undef %main::;

oder

  %main:: = (ALLES => 'WEG');

dessen Laufruhe möglicherweise etwas leiden wird. Auch das Entfernen von Stashelementen mit delete sollte mit Bedacht geschehen. Generell ist beim modifizierenden Zugriff auf Stashes durchaus Vorsicht angebracht; Perl ist wie immer mit seinen Möglichkeiten freizügig, erwartet aber auch hier, dass der Programmierer weiß, was er tut.

Typeglobs

Im Unterschied zu dem meisten Sprachen erlaubt Perl das Benennen einer Variable und einer Funktion mit demselben Namen. So können eine Variable test , eine Funktion test und sogar ein IO Handle test gleichzeitig nebeneinander existieren, ohne sich im geringsten zu beeinflussen.

Bei globalen Variablen wird dies durch eine Komponente namens Typeglob ermöglicht. Darunter kann man sich eine Datenstruktur mit sechs Feldern ( Slots ) vorstellen, die Platz für alle Wertetypen vorsieht, die Perl kennt:

* Skalare (und Referenzen)

* Arrays

* Hashes (und Stashes)

* Funktionen (benannte Subroutinen und Methoden)

* IO Handles (Dateien, Verzeichnisse, Pipes, Sockets)

* Format Handles

Oft ist pro Typeglob nur ein Slot belegt, die anderen sind leer L<[4]|/item__5b4_5d>. Werden aber, wie oben beschrieben, in einem Skript z.B. ein Skalar und ein Array gleich benannt, so werden diese beiden Variablen in dem zugehörigen Typeglob in den jeweiligen Slots abgelegt. Davon abgesehen, dass sie über denselben Namen angesprochen werden, haben diese beiden Variablen aber nichts gemeinsam und könnten genausogut unter verschiedenen Namen angesprochen werden.

Es ist letztlich Sache des Programmieres, ob er für Variable und Funktionen dieselben Namen verwendet L<[5]|/item__5b5_5d>.

Was haben nun Stashelemente und Typeglobs miteinander zu tun? Nun, die Antwort ist naheliegend: Die Werte von Stashelementen sind Typeglobs . Daher geschieht z.B. beim Zugriff auf die Variable $hugo::test vereinfacht gesagt folgendes:

  • Im Stash %hugo:: wird nach dem Element test gesucht. Der Wert des Elements ist in ein Typeglob L<[6]|/item__5b6_5d>.

  • Da dem Namen ein $-Zeichen vorangestellt ist, wird auf den Skalar-Slot des Typeglobs zugegriffen.

Tatsächlich kann man sich das Sigil (Vorzeichen) in Variablenamen als Selektor für den auszuwählenden Typeglob-Slot vorstellen. Bei den Namen von Handles, für die Perl keine Sigils vorsieht, ergibt sich der anzusprechende Slot aus dem Umfeld, in dem der jeweilige Name vorkommt. Für das Aufrufen von Funktionen gibt es außerdem noch die Notation name() als häufigere (und verständlichere) Alternative zu &name .

Perl erlaubt das Ansprechen von Typeglobs auch direkt mit dem *-Sigil, und somit sprechen die folgenden Ausdrücke denselben Typeglob (im Package main ) an:

   $main::{hugo}                *hugo

Ersterer wird allerdings erst zur Laufzeit ausgewertet (da ja auf ein Hashelement, dessen Inhalt beim Kompilieren noch nicht bekannt ist, zugegriffen wird), letzterer schon beim Kompilieren, da hier der Typeglob direkt adressiert werden kann. Daher ist die zweite Notation schneller, aber weniger flexibel.

Typeglobs und Referenzen

Im vorigen Abschnitt wurde festgestellt, dass Typeglobs Felder ( Slots ) für die sechs möglichen Wertetypen von Perl enthalten. Das ist aber nur die halbe Wahrheit; tatsächlich enthalten die Slots, so sie belegt sind, Referenzen , die auf die eigentlichen Werte zeigen. Und da Perl seit der Version 5 den direkten Umgang mit Referenzen gestattet, kann man so auch die Slots von Typeglobs auslesen oder ihnen neue Werte zuweisen. Das wird in den folgenden Abschnitten näher betrachtet.

Zuweisungen an Typeglobs

Einem Typeglob kann man, ähnlich wie Skalaren, Referenzen auf Werte zuweisen. Eine solche Zuweisung ist gleichbedeutend mit der direkten Zuweisung des Wertes an den entsprechenden Variablentyp:

  $x = 3;            *x = \do {my $x = 3};   ${*x} = 3;
  @y = (1, 2);       *y = [1, 2];            @{*y} = (1, 2);
  %z = (s => 3);     *z = {s => 3};          %{*z} = (s => 3);
  sub a {...};       *a = sub {...};

Der Code in den drei Spalten ist von der Wirkung her identisch, wenngleich die linke Schreibweise die Geläufigste ist (links unten findet sich eine bereits beim Kompilieren verarbeitete Deklaration ; die Zuweisungen finden hingegen erst zur Laufzeit statt). In einigen, später gezeigten Fällen führt allerdings an der Zuweisung über den Typeglob kein Weg vorbei.

Die rechte Spalte zeigt die Zuweisung ebenfalls über den Typeglob, wobei hier durch temporäre Dereferenzierung wiederum Werte und keine Referenzen zugewiesen werden. Da es in Perl keine Funktionsliterale gibt (sondern nur Referenzen darauf), ist die Zuweisung einer Funktion auf die in dieser Tabelle gezeigte Art nicht möglich.

Beachte die Verwendung von \do {...} für die Zuweisung einer anonymen Skalarreferenz, für die Perl keine eigene Syntax vorsieht. Achtung: die Verwendung von \3 ergäbe eine Referenz auf einen schreibgeschützten und daher im Vergleich zur linken Spalte nicht identischen Wert.

Besonderes Augenmerk verdient das Konstrukt in der letzten Zeile - es zeigt den Einsatz von sub in einer Deklaration (die vom Compiler ausgewertet wird) in der linken Spalte, und den Einsatz als Operator mit einer zur Laufzeit durchgeführten Zuweisung in der mittleren Spalte. In letzterem Fall liefert sub eine Referenz auf eine anonyme Subroutine, die durch Zuweisung an einen Typeglob einen Eintrag in der Symboltabelle erhält und damit ihre Anonymität verliert. Tatsächlich kann man eine vom Compiler verarbeitete Deklaration

  sub mysub {
    ...
  }

genauso gut auch als

  BEGIN {
    *mysub = sub { ... };
  }

schreiben (allerdings ist erstere klarerweise schneller, weil sie direkt vom Compiler abgehandelt wird und kein Perl-Interpreter dafür gestartet werden muss) L<[7]|/item__5b7_5d>. Diese Technik - das Generieren von Funktionen zur Laufzeit durch Zuweisung von Codereferenzen an Typeglobs - ist recht beliebt und wird z.B. von Wrapper Funktionen? verwendet.

Beachte, dass bei den obigen Globzuweisungen auf der linken Seite stets dieselbe Angabe - eben der Typeglob - zu finden ist. Perl erkennt selbst anhand des zuzuweisenden Referenztyps , welcher Slot des Typeglobs durch die Zuweisung zu belegen ist.

Ein weiterer Punkt soll nicht unerwähnt bleiben: die Zuweisung eines Typeglobs an einen anderen Typeglob in der Form

  *alpha = *beta;

Sie bewirkt, dass beide Typeglobs nun dieselben Slots gemeinsam verwenden. Wird daher an $alpha ein Wert zugewiesen, so kann dieser nun auch mit $beta angesprochen werden. Das gilt sinngemäß auch für @beta , %beta , &beta und die Handles beta . Diese Technik bezeichnet man als Aliasing und sie bildet die Basis für Module wie Exporter, die dadurch Symbole (meistens Funktionsnamen) eines Packages in einem anderen Package abbilden und es so ermöglichen, fremde (importierte) Funktion wie lokale Funktionen ohne Package-Prefix anzusprechen (siehe hierzu auch den Abschnitt über Aliasing und Importing?).

Wird versucht, an einen Typeglob etwas anderes als eine Referenz zuzuweisen:

  *hugo = 'test';

so nimmt Perl an, dass eine Alias-Zuweisung stattfinden soll und macht daraus

  *hugo = *test;

Da dies erst zur Laufzeit passiert (der Compiler generiert ungeniert den Code für die Zuweisung des Stringliterals an den Typeglob), gibt es in diesem Fall auch mit use warnings keinerlei Hinweise auf dieses nicht ganz saubere Konstrukt.

Da man Typeglobs nicht mit our deklarieren kann, darf man sie auch mit use strict 'vars' unqualifiziert, d.h., ohne Packagenamen ansprechen L<[8]|/item__5b8_5d>.

Abschliessend sei noch kurz der Befehl

  undef *hugo;

erwähnt, der alle Slots des Typeglobs löscht, also einem

  undef $hugo;
  undef @hugo;
  undef %hugo;
  ...

entspricht. Hier kann man sich das "*" tatsächlich als Wildcard ("Lösche alles mit dem Namen hugo ") vorstellen.

Es ist zu beachten, dass das Löschen eines Stashelements nicht dieselbe Wirkung hat wie das undef -Setzen eines Typeglobs. Wird z.B. mit

  delete $main::{hugo};

das Stashelement gelöscht, so wird ein darauf folgender Zugriff

  print $hugo;

dennoch funktionieren, da die Umsetzung des Elementkeys in eine Typeglob-Adresse in der print -Anweisung bereits beim Kompilieren geschieht. Daher wird der Stash zur Laufzeit hierfür nicht mehr benötigt und das Löschen bleibt wirkungslos. Es wird in diesem Fall auch nur das Stashelement gelöscht, nicht aber der in ihm enthaltene Typeglob.

Geschieht ein derartiges Löschen allerdings in einem BEGIN -Block, dann kann es zu einem Fehlverhalten oder Abbrechen des Skripts kommen. Ein modifizierender Zugriff auf einen Stash oder Stashelemente in BEGIN -Blöcken sollte daher, wie bereits erwähnt, mit Bedacht erfolgen.

Da an Typeglobs nur Referenzen zugewiesen werden können, ist die Zuweisung von undef

  *hugo = undef;

unzulässig und wird mit einem

  Undefined value assigned to typeglob

kommentiert, ansonsten aber ignoriert. Erlaubt ist hingegen

  *hugo = \undef;

und es bleibt dem Leser als Aufgabe überlassen, sich zu überlegen, was hier geschieht (oder in der Fußnote L<[9]|/item__5b9_5d> nachzusehen).

Auslesen von Typeglobs

Die in Typeglobs enthaltenen Referenzen können ausgelesen und wie jede andere Referenz an Skalare zugewiesen oder sonst weiterverarbeitet werden. Allerdings wäre die Notation

  $ref = *glob;

nicht mehr eindeutig, da hieraus nicht hervorgeht, welche Referenz (d.h., welcher Slot) angesprochen werden soll. Perl bietet stattdessen eine hash-ähnliche Syntax zum Ansprechen der sechs Slots eines Typeglobs an:

  $scalarref = *glob{SCALAR};
  $arrayref  = *glob{ARRAY};
  $hashref   = *glob{HASH};
  $coderef   = *glob{CODE};
  $handleref = *glob{IO};
  $formatref = *glob{FORMAT};

Alle diese Zuweisungen liefern Referenzen des jeweiligen Typs zurück, die dann ganz normal dereferenziert werden können. So kann man mit

  @{$arrayref}

auf das Array, dessen Referenz dem Array-Slot des Typeglobs entnommen wurden, zugreifen. Anders gesagt, sind die beiden folgenden Ausdrücke identisch:

  \@hugo      *hugo{ARRAY}

und daher auch diese beiden:

   @hugo    @{*hugo{ARRAY}}

wobei die linke Schreibweise sicher kürzer und geläufiger ist, die rechte aber die internen Abläufe beim Zugriff veranschaulicht.

Ist der angegebene Slot des Typeglobs nicht in Verwendung, wird undef , im Falle von SCALAR eine Referenz auf undef zurückgeliefert (dieser Sonderfall muss daher beim Testen ggf. berücksichtigt werden; siehe dazu das erste Beispiel im Abschnitt Bearbeiten von Symboltabellen?).

Die nahe Verwandtschaft von Typeglobs und Referenzen führt dazu, dass an Stellen, an denen eine Referenz erwartet wird, auch ein Typeglob verwendet werden kann (aber nicht immer auch umgekehrt!).

Erwähnenswert sind auch noch die folgenden Konstrukte:

  $globref  = *glob{GLOB};
  $name     = *glob{NAME};
  $package  = *glob{PACKAGE};

Sie liefern eine Referenz auf den Typeglob selber, sowie den Symbolnamen (d.h., den Keynamen des Stashelements, das den Typeglob enthält), und den Packagenamen (Stashnamen) zurück. Auf den ersten Blick nicht sonderlich interessant, werden diese Konstrukte wichtig, wenn man Typeglobs in Variablen als Argumente an Funktionen übergibt. Die Funktion kann dann bei Bedarf Package- und Symbolnamen des übergebenen Typeglobs ermitteln und über die Referenz wiederum direkt darauf zugreifen, was im Umgang mit Datei- und Formathandles wichtig werden kann (siehe dazu auch Abschnitt Verwendung von Stashes und Typeglobs?).

Beachte, dass diese Konstrukte nur auf der rechten Seite einer Zuweisung (d.h. als Quelle) vorkommen dürfen, auf der linken Seite akzeptiert sie der Parser nicht. Das ist auch nicht notwendig, da, wie im Abschnitt Zuweisungen an Typeglobs? gezeigt, Perl dann anhand des Referenztyps selbst erkennt, welcher Slot des Typeglobs zu belegen ist.

Beachte auch, dass es sich trotz der hash-ähnlichen Syntax nicht um Hashes und Hashkeys handelt und dass daher abgesehen von den gezeigten Beispielen keine anderen Operationen zulässig sind. Die Keynamen sind fix vorgegeben, andere Werte (z.B. *glob{XXX} ) liefern den Wert undef zurück.

Ein Typeglob selbst wird von Perl beim Zuweisen wie ein Skalar betrachtet und daher ist die Anweisung

  $glob = *hugo;
  print $glob;         # Gibt "*main::hugo" aus (im Package main)

durchaus zulässig. Hier enthält der Skalar $glob nun den Typeglob *hugo und man kann mit

  *{$glob}

wiederum auf den Typeglob oder z.B. mit

   $slot = 'HASH';
   my %hash = %{*{$glob}{$slot}};

über den Typeglob auf den ursprünglichen Hash (hier also %hugo ) zugreifen. Da man Typeglobs in Skalaren abspeichern kann, kann man sie natürlich auch in Hash- oder Arrayelementen ablegen und so bei Bedarf an eine Funktion z.B. ein Typeglob-Array übergeben.

Es ist allerdings wichtig zu beachten, dass solcherart abgespeicherte Typeglobs nur sog. Fake-Kopien sind, was vereinfacht gesagt bedeutet, dass man auf sie bzw. ihre Slots nur mehr lesend zugreifen darf. Der Versuch, einen Slot durch Zuweisung einer Referenz zu beschreiben, bewirkt, dass der Skalar statt dem Typeglob nur mehr die angegebene Referenz entählt:

  $value = *glob;        # Erzeugt in $value ein Fake-Kopie von *glob
  print $value;          # Gibt "*main::glob" aus
  *{$value} = [1,2,3];   # Array-Slot soll ueberschrieben werden
  print $value;          # Gibt "ARRAY(0x<Adresse>)" (Arrayreferenz) aus

Der ursprüngliche Typeglob (hier *glob ) bleibt davon unbeeinflusst.

Möchte man auch das Modifizieren von Typeglobs, die in Skalaren enthalten sind, ermöglichen, wird man Globreferenzen (siehe nächster Absatz) verwenden. Man kann aber auch mit

  *newglob = $glob;

den Typeglob duplizieren und dann nicht mehr über den Skalar, sondern kürzer über den neuen Typeglob zugreifen. Das ist identisch mit der weiter oben beschriebenen Methode des Aliasing ; d.h., beide Typeglobs greifen wieder auf dieselben Slots zu, die dann natürlich auch wieder modifiziert werden können.

Man kann auch Typeglobs selber über Referenzen ansprechen, wenn man dem Typeglobnamen ein \ voranstellt oder die weiter oben erwähnte Hash-Notation verwendet:

  $globref = \*glob;
  $globref =  *glob{GLOB};

liefert beides eine Referenz auf den Typeglob zurück, wodurch das Anlegen von Fake-Kopien vermieden wird. Das kann außerdem für Funktionen, die unterschiedliche Referenzentypen entgegennehmen, hilfreich sein, da man dann mit der ref Funktion auch prüfen kann, ob eine Globreferenz übergeben wurde:

  sub globderef {
    my $arg = shift;
    die 'Not a glob reference' unless ref $arg eq 'GLOB';
    print 'Got glob reference  to ', *{$arg}, "\n";
  }

Hier ist es wichtig, vor der Dereferenzierung des Typeglobs sicherzustellen, dass eine solcher als Argument mitgegeben wurde, was mit ref einfach geschehen kann. Würde der Typeglob direkt übergeben, wäre eine solche Prüfung nicht so direkt möglich.

Man kann eine solche Funktion auch mit einem Prototyp * deklarieren, hier also

  sub deref(*) {
   ...
  }

dann erhält sie immer eine Globreferenz übergeben, egal ob sie nun mit einem Typeglob oder einer Referenz darauf aufgerufen wurde.

Wie hier auch zu erkennen ist, ist die Syntax zum Dereferenzieren einer Globreferenz und zum Auslesen eines Skalars, dem ein Typeglob zugewiesen wurde, diesselbe:

  $globvar =  *glob;        # Skalar enthaelt Fake-Kopie
  $globref = \*glob;        # Skalar enthaelt Globreferenz

  *newglob = *{$globvar};   # oder
  *newglob = *{$globref};

Sauberer ist aber der Ansatz mit der Globreferenz, weil dadurch das Anlegen einer Fake-Kopie vermieden wird.

Verwendung von Stashes und Typeglobs

Soviel zur Theorie über Stashes und Typeglobs. Wofür kann man sie aber jetzt beim Programmieren gebrauchen?

Die folgenden Abschnitte beschreiben Einsatzmöglichkeiten (mit Beispielen):

* Bearbeiten von Symboltabellen?

* Aliasing und Importing?

* Einrichten von Wrappern?

* Umgang mit Handles?
Eine weitere Verwendung bis Perl 4 war die Übergabe von Referenzen an und deren Verarbeitung in Funktionen, da es den Referenzoperator \ und den Dereferenzierungsoperator -> dort noch nicht gab. Dabei machte man sich die enge Verwandtschaft von Typeglobs und Referenzen zunutze. Um z.B. einer Funktion Zugriff auf ein Array zu ermöglichen, konnte man Folgendes schreiben:

  sub my_func {
    *array = shift;
    print join ',',@array;      # Gibt "1,2,3" aus
    @array = (2,4,6);
  }

  @val = (1,2,3);
  my_func(*val);
  print join ',',@val;          # Gibt "2,4,6" aus

Hier wird über den Typeglob tatsächlich ein Zugriff auf das Array (und nicht etwa eine Kopie) gestattet, wodurch Änderungen am Array auch außerhalb der Funktion bestehen bleiben.

Diese Art der Kodierung wurde in Perl 5 durch die Einführung von Referenzen überflüssig und sollte nicht mehr verwendet werden. Es ist aber möglich, dass man in älterem Perl-Code noch darauf stößt, so dass es kein Fehler ist, darüber Bescheid zu wissen.

Bearbeiten von Symboltabellen

Das Bearbeiten von Symboltabellen kann vor allem für Test- und Debuggingzwecke hilfreich sein. Da Symboltabellen als Hashes implementiert sind, kann man, wie bereits beschrieben, die mit Perl hierfür mitgelieferten Funktionen wie mit jedem anderen Hash verwenden. Dadurch wird es möglich, Symboltabellen zu durchsuchen und ihre Einträge weiterzuverarbeiten. Von dieser Möglichkeit macht unter anderem auch der Perl-Debugger in seinen V und X Befehlen Gebrauch.

Das folgende Beispiel zeigt, wie Stashes rekursiv durchsucht werden. Dabei macht es sich das in Abschnitt Stashes und Packages? beschriebene Konzept verschachtelter Packages zunutze (die Zeilennummern dienen nur zum Referenzieren und sind kein Bestandteil des Codes):

  1  use warnings;
  2  use strict;
  3  my @slots = qw[SCALAR ARRAY HASH CODE IO FORMAT];
  4  my $stash = shift || 'main::';
  5  $stash = 'main::' if $stash eq '::';
  6  $stash .= '::' unless $stash =~ /::$/;
  7  my $recflg = shift;
  8  show_globs($stash,$recflg);

  9  sub show_globs {
 10    my ($stash,$recflg,$index) = @_;
 11    $index ||= 0;
 12    no strict 'refs';
 13    foreach my $glob (values %{$stash}) {
 14      my $name = *{$glob}{NAME};
 15      next if $stash eq 'main::' && $name eq 'main::';
 16      my $fullname = $stash . $name;
 17      foreach my $slot (@slots) {
 18        my $text = ' ' x $index . '*' . $fullname . "{$slot}\n";
 19        if ($slot eq 'SCALAR') {
 20          print $text if defined ${$glob};
 21        }
 22        else {
 23          print $text if defined *{$glob}{$slot};
 24        }
 25      }
 26      show_globs($fullname,1,$index+1) if $name =~ /::$/ && $recflg;
 27    }
 28  }

Dieser Code listet vom angegebenen Stash (oder von %main:: , falls keine Angabe erfolgt ist) alle Typeglobs und die in ihnen belegten Slots in der Notation *package::name{slot} auf. Falls das zweite Argument auf true gesetzt ist, werden auch alle Stashes unterhalb des angegebenen rekursiv durchlaufen. An diesem Code ist einiges bemerkenswert.

Zunächst werden in Zeile 3 die bekannten Wertetypen von Perl, die in Slots von Typeglobs vorkommen können, aufgelistet. Wenn man nicht an allen Wertetypen interessiert ist, kann man die nicht Benötigten auch weglassen.

Weiters fällt die no strict 'refs' Anweisung innerhalb der Funktion in Zeile 12 auf; sie ist notwendig, da in Zeile 13 eine symbolische Referenz zum Ansprechen des jeweiligen Stashes ( %{$stash} ) verwendet wird. Dasselbe Problem stellt sich aber auch, wenn auf diese Art Typeglobs angesprochen werden sollen:

  my $name = 'ENV';
  print %{*{$name}{HASH}};      # dasselbe wie print %ENV

Dieser Code liefert mit use strict 'refs' den Laufzeitfehler

  Can't use string ("ENV") as a symbol ref while "strict refs" in use

zurück. Wie weiter unten noch gezeigt werden wird, kann es im Umgang mit Symboltabellen und Stashes häufig notwendig werden, strict 'refs' temporär abzuschalten.

Erwähnenswert ist auch Zeile 15 - sie verhindert einen Endloslauf des Skripts. Die Implementierung von Stashes, besonders der Algorithmus zur deren Verschachtelung sieht vor, dass jeder Stash einem Elternstash entstammt. Wem entstammt dann aber %main:: ? Nun, er stammt von sich selbst ab. Anders gesagt, enthält %main:: ein Element, dessen Name main lautet und das wiederum auf %main:: zeigt L<[10]|/item__5b10_5d>. Dadurch entsteht eine (gewollte) Zirkularität, die - in diesem Beispiel - in Zeile 15 unterbrochen wird. Wenn man also in Skripts Stashes auf diese Weise durchläuft und auch %main:: verarbeiten will, muss man dessen besondere Eigenschaft ( selbstreferenzierender Stash ) in der gezeigten oder in einer ähnlichen Weise berücksichtigen.

Die Prüfung beider Variable ist notwendig, da ansonsten Stashes wie z.B. MyPackage::main:: unberücksichtigt bleiben würden.

Schließlich soll noch auf die unterschiedliche Behandlung von Skalarslots und anderen Slots in Zeile 19 ff. hingewiesen werden. Wie bereits erwähnt, ist der Skalarslot eines Typeglobs immer belegt (auch wenn der Skalarwert nur undef ist). Daher muss die im Slot abgelegte Referenz dereferienziert werden, um feststellen zu können, ob tatsächlich ein Wert vorhanden ist. Bei allen anderen Wertetypen genügt das Testen des Slots selbst.

Auf die Ausgabe der einzelnen Werte der Variablen wurde hier verzichtet, da dies im Falle von Codewerten und Handles ohnehin nicht möglich ist und die Ausgabe bei Array- und Hashwerten in Abhängigkeit von deren Elementanzahl sehr umfangreich werden kann. Auch ist nicht gesagt, dass jeder Wert nur darstellbare Zeichen enthält, so dass diese u.U. vor der Ausgabe noch entsprechend bearbeitet werden müssten. Dies sei dem Leser als Hausaufgabe überlassen.

Da übrigens auch die Namen einzelner Perl-Variable teilweise mit nicht darstellenbaren Zeichen abgespeichert werden (so wird z.B. das ^U der Variable ${^UNICODE} mit seinem tatsächlichen ASCII-Wert 0x15 (octal 025) abgespeichert), erhält man beim Starten des obigen Skripts manchmal gar keine oder unvollständige Namen (z.B. nur NICODE ). Ob und wie solche Namen dargestellt werden, hängt von den Fähigkeiten des jeweiligen Ausgabegerätes ab. Guter Code stellt daher vor der Ausgabe von Stashnamen sicher, dass diese nur darstellbare Zeichen enthalten.

Ein weiteres Beispiel, das das Löschen von Symboltabellen zeigt, ist (in etwas gekürzter Form) dem Modul Symbol.pm entnommen:

  1  sub delete_package {
  2    my $pkg = shift;
  3    $pkg = "main::$pkg" unless $pkg =~ /^main::/;
  4    $pkg .= '::' unless $pkg =~ /::$/;

  5    my ($stem,$leaf) = $pkg =~ /(.*::)(\w+::)$/;
  6    my $stem_symtab = *{$stem}{HASH};
  7    return unless exists $stem_symtab->{$leaf};

  8    my $leaf_symtab = *{$stem_symtab->{$leaf}}{HASH};
  9    foreach my $name (keys %$leaf_symtab) {
 10      undef *{$pkg . $name};
 11    }
 12    %$leaf_symtab = ();
 13    delete $stem_symtab->{$leaf};
 14  }

Dieses Beispiel zeigt das Ansprechen von Stashes über Hashreferenzen, die direkt den jeweiligen Slots der Typeglobs entnommen werden (Zeilen 6 und 8). Der Code verdient eine nähere Betrachtung:

  my $stem_symtab = *{$stem}{HASH};

Hier wird der Hashslot der Stashes, dessen Name in $stem abgelegt ist, geholt; das Ergebnis in $stem_symtab ist eine Hashreferenz. In Zeile 7 wird mittels Dereferenzierung geprüft, ob das angegebene Element (d.h., das in diesem Stash enthaltene Package) existiert:

  return unless exists $stem_symtab->{$leaf};

Falls nicht, wird an dieser Stelle abgebrochen (was nicht existiert, braucht auch nicht gelöscht zu werden). Ansonsten wird nun der Stash, auf den dieses Element zeigt, geholt:

  my $leaf_symtab = *{$stem_symtab->{$leaf}}{HASH};

Schreibt man das ohne die Zwischenvariable $stem_symtab , so ergibt sich:

  my $leaf_symtab = *{*{$stem}{HASH}->{$leaf}}{HASH};

Das läßt sich - von innen nach außen - wie folgt auflösen:

  • *{$stem} liefert den Typeglob des Stashes, dessen Name in $stem steht.

  • *{$stem}{HASH} spricht den Hash-Slot dieses Typeglobs an, das ergibt daher eine Hashreferenz.

  • *{$stem}{HASH}->{$leaf} spricht durch Dereferenzierung das Hashelement an, dessen Name in $leaf steht. Der Wert dieses Elements ist wiederum ein Typeglob, der mit

  • *{*{$stem}{HASH}->{$leaf}} angesprochen wird, und somit wird mit

  • *{*{$stem}{HASH}->{$leaf}}{HASH} dessen Hashslot adressiert, wodurch man wiederum eine Hashreferenz - diesmal auf den gesuchten Stash - erhält. In der Schleife ab Zeile 9 wird dieser Hash dann durch erneutes Dereferenzieren und Auslesen der Elementnamen durchlaufen.

Setzt man für $stem den Wert "main::" und für $leaf den Wert "Alpha::" ein, so ergibt sich für diese Zeilen:

  6    my $stem_symtab = *main::{HASH};
  7    return unless exists $stem_symtab->{'Alpha::'};
  8    my $leaf_symtab = *{$stem_symtab->{'Alpha::'}}{HASH};

woraus die Funktionsweise nun klar erkennbar sein sollte.

In Zeile 10 werden nun nacheinander alle Typeglobs in $leaf_symtab auf undef gesetzt und damit alle Werte mit diesen Namen gelöscht. In Zeile 12 wird der Stash noch einmal explizit geleert, bevor in Zeile 13 das Stashelement aus der übergeordneten Symboltabelle entfernt wird.

Die Funktion ist kurz, aber gründlich - sie löscht das angegebene Package und alle darunter liegenden Packages komplett. Daher ist bei der Anwendung Vorsicht geboten; auf die Probleme, die sich aus schreibendem (bzw. löschendem) Zugriff auf Stashes ergeben können, wurde schon weiter oben hingewiesen.

Zum Abschluss sei noch kurz die Funktion gensym des o.a. Moduls erwähnt - sie liefert eine Referenz auf einen Typeglob zurück, die später z.B. anstelle eines Filehandles verwendet werden kann:

  package Symbol;
  my $genseq = 0;
  my $genpkg = "Symbol::";

  sub gensym () {
    my $name = "GEN" . $genseq++;
    my $ref = \*{$genpkg . $name};
    delete $$genpkg{$name};
    $ref;
  }

Hier wird ein temporärer Name für den Typeglob ( Symbol::GEN<n> ) erzeugt und anschließend auf diesen Typeglob eine Referenz gelegt. Das entsprechende Element wird sodann wieder aus dem Stash gelöscht, der Typeglob bleibt aber durch die auf ihn zeigende Referenz erhalten, wird also zu einem anonymen Typeglob. Die Referenz wird dann an den Aufrufer zurückgegeben.

Beachte, dass hier sowohl das Anlegen des Typeglobs als auch das Löschen des Stashelementes erst zur Laufzeit geschieht, wenn gensym() aufgerufen wird.

Aliasing und Importing

Unter Aliasing versteht man, wie schon kurz erwähnt, das Ansprechen ein und derselben Daten unter verschiedenen Namen. So kann nach Ausführen der Typeglob-Zuweisung

  *beta = *alpha;

alles in alpha auch als beta angesprochen werden. Das ist noch nicht besonders interessant, aber sobald unterschiedliche Packages in Spiel kommen, wird die Bedeutung von Aliasing erkennbarer:

  package Alpha;

  sub testfunc {
    ...
  }

  *main::testfunc = *testfunc;

Nun kann auch im Defaultpackage (im Hauptprogramm) die Funktion als testfunc() (ohne Packagenamen) angesprochen werden.

Diese Methode hat allerdings einen nicht ganz unwesentlichen Nebeneffekt - nicht nur die Funktion testfunc ist nun in beiden Packages unter diesem Namen sichtbar, sondern auch alle gleichnamigen Variablen und Handles. Wenn also im Package Alpha und im Hauptprogramm auch jeweils ein Skalar $testfunc verwendet wird, kann es mit diesem Ansatz Probleme geben, weil dann aus beiden Packages ein und dieselbe Variable - vermutlich unabsichtlich - angesprochen wird.

Daher existiert auch eine abgeschwächte Version des Aliasing, die man als partielles Aliasing bezeichnet. Die Idee dahinter ist nicht neu - anstatt den gesamten Typeglob zuzuweisen, wird nur der Slot des gewünschten Wertetyps - in der Regel der Codeslot - zugewiesen, also auf das obige Beispiel bezogen:

  *main::testfunc = *testfunc{CODE};

oder die häufigere Schreibweise mit dem Referenzoperator \ :

  *main::testfunc = \&testfunc;

Hier wird nur der Codeslot von *main::testfunc auf den Wert von *Alpha::testfunc gesetzt. Diese beiden Slots referenzieren also dieselbe Funktion. Die übrigen Slots beider Typeglobs, so sie verwendet werden, zeigen auf unterschiedliche Werte und daher sind z.B. $main::testfunc und $Alpha::testfunc nach wie vor unterschiedliche Variable.

Und damit wird das Funktionsprinzip des bekannten Exporter -Moduls deutlich, dessen import() Methode mit allen Symbolnamen in @EXPORT und, soferne beim Aufruf angegeben, auch in @EXPORT_OK , partielles Aliasing durchführt. Der folgende Code ist aus Exporter/Heavy.pm entnommen:

  1  foreach $sym (@imports) {
  2    (*{"${callpkg}::$sym"} = \&{"${pkg}::$sym"}, next)
  3      unless $sym =~ s/^(\W)//;
  4    $type = $1;
  5    *{"${callpkg}::$sym"} =
  6      $type eq '&' ? \&{"${pkg}::$sym"} :
  7      $type eq '$' ? \${"${pkg}::$sym"} :
  8      $type eq '@' ? \@{"${pkg}::$sym"} :
  9      $type eq '%' ? \%{"${pkg}::$sym"} :
 10      $type eq '*' ?  *{"${pkg}::$sym"} :
 11      do {require Carp; Carp::croak("Can't export symbol: $type$sym")};
 12  }

Ja, diese Zeilen implementieren die Kernfunktionalität von Exporter . Das Modul stellt noch einige weitergehende Möglichkeiten (Export-Tags, Exportieren in ein anderes als das aufrufende Modul, Versionsprüfungen, Symbole zum Exportieren sperren, u.a.m) zur Verfügung, aber letztlich basiert das alles auf obigem Code L<[11]|/item__5b11_5d>.

Was passiert hier? Nun, das Array @imports enthält die Liste aller Namen, die exportiert werden sollen (Funktions- oder Variablenamen). Funktionen können mit oder ohne führendem Sigil & angegeben werden. Die Variable $callpkg enthält den Namen des Packages, in das , $pkg das Package, aus dem exportiert werden soll. Durch die foreach Schleife wird @imports Element für Element abgearbeitet.

In den Zeilen 2 und 3 wird festgestellt, ob dem Namen ein Sigil vorangeht. Wie bereits erwähnt, muss dies bei Funktionsnamen nicht der Fall sein (und ist auch meistens nicht). Falls das Sigil fehlt, wird ein partielles Aliasing mit einer Funktionsreferenz durchgeführt. Damit werden solche Fälle wie

  use MyModule qw(xx yy zz);

in denen Funktionsnamen ohne & angegeben werden, abgedeckt.

Ansonsten wurde durch den regulären Ausdruck das Sigil ermittelt und mittels Substitution vom Symbolnamen entfernt, so dass nun ab Zeile 4 je nach Sigil ein partielles Aliasing mit der entsprechenden Referenz erfolgen kann.

Es sollte nun schon klar sein, dass man statt obigem Code auch

  5    *{$callpkg.'::'.$sym} =
  6      $type eq '&' ? *{$pkg.'::'.$sym}{CODE}   :
  7      $type eq '$' ? *{$pkg.'::'.$sym}{SCALAR} :
  8      $type eq '@' ? *{$pkg.'::'.$sym}{ARRAY}  :
  9      $type eq '%' ? *{$pkg.'::'.$sym}{HASH}   :
 10      $type eq '*' ? *{$pkg.'::'.$sym}         :
 11      do {require Carp; Carp::croak("Can't export symbol: $type$sym")};

hätte schreiben können. Hier hält es Perl wie so oft: TIMTOWTDI L<[12]|/item__5b12_5d>.

Doch halt! Warum wurde in Zeile 10 das {GLOB} weggelassen? Der Leser möge versuchen, es herauszufinden, bevor er in L<[13]|/item__5b13_5d> nachsieht.

Wird mittels des Exporters z.B. ein Skalar exportiert, so wird

  *{"${callpkg}::$sym"} = \${"${pkg}::$sym"};

also z.B. für die nach main zu exportierende Variable $hugo des Moduls My_Module

  *main::hugo = \$My_Module::hugo;

ausgeführt. Dabei wird der im Zielpackage neu angelegte Typeglob als importiert markiert. Diese Importierung ist typ-spezifisch, d.h., sie gilt (in diesem Beispiel) nur für den Skalarwert und nicht für die anderen möglichen Wertetypen. Natürlich können auch Arrays, Hashes oder Funktionen importiert werden, aber das muss durch Plazieren der entsprechenden Namen nach @EXPORT oder @EXPORT_OK explizit vom Modulautor vorgesehen werden.

Stößt der Parser später (im Package main ) auf den Ausdruck

  $hugo

so findet er im Typeglob den Skalarwert als importiert gekennzeichnet und erlaubt daraufhin das Verwenden des unqualifizierten Namens. Daher kann man Variable und Funktionen, sobald sie durch den Exporter (oder anderweitiges partielles Aliasing) derart markiert wurden, auch bei aktivem strict 'vars' ohne Packagenamen ansprechen.

Wird ein Programm mit use strict bzw. use strict 'vars' kompiliert, so muss jede globale Variable zuerst importiert werden, bevor sie ohne Packagenamen angesprochen werden kann. Dazu gibt es die Compilerpragmas vars und subs L<[14]|/item__5b14_5d>. Sieht man sich die import() Methode von vars an, so wird der darin enthaltene, auszugsweise wiedergegebene Code

  $sym = "${callpack}::$sym" unless $sym =~ /::/;
  *$sym = ( $ch eq "\$" ? \$$sym
          : $ch eq "\@" ? \@$sym
          : $ch eq "\%" ? \%$sym
          : $ch eq "\*" ? \*$sym
          : $ch eq "\&" ? \&$sym);

hoffentlich nicht mehr ganz so fremd anmuten. $ch enthält das Sigil, $sym den Namen des zu importierenden Symbols, der vor der Typeglob-Zuweisung ggf. noch mit dem Packagenamen des Aufrufers ergänzt wird, um einen voll qualifizierten Namen zu erhalten. Somit wird daher für das Importieren z.B. eines Skalars $hugo in das Package main durch

  use vars '$hugo';

nur mehr

  $sym = "main::hugo";
  *{$sym} = \${$sym};

ausgeführt, was sich zu

  *main::hugo = \$main::hugo;

reduzieren lässt. Und das entspricht vollkommen dem obigen Code des Exporters, nur mit dem Unterschied, dass hier Ziel- und Quellpackage dasselbe ist. Anders gesagt: das Modul Exporter und die Pragmas vars und subs basieren auf derselben Idee: sie importieren Symbole - nur sind die Quellpackages andere L<[15]|/item__5b15_5d>.

subs ist eine vereinfachte, auf das Importieren von Funktionen (die ohne Sigil angegeben werden) optimierte Version von vars . subs wird benötigt, um Funktionen vorzudeklarieren , um sie im Code vor der eigentlichen Definition verwenden zu können:

  func;
  ...
  sub func {print "!"}

Hier weiß der Parser beim Auffinden des Ausdrucks "func" nicht, was gemeint ist - in der Symboltabelle gibt es noch keinen zugehörigen Typeglob. Daher wird er den Ausdruck als nicht gequotetes Stringliteral interpretieren und sich nach der Ausgabe der Meldung

  Unquoted string "func" may clash with future reserved word

nicht weiter darum kümmern. Durch das Einsetzen von

  use subs 'func';

am Programmbeginn hingegen wird durch partielles Aliasing der Ausdruck func als Funktionsname bekannt gemacht und der Parser wird ihn ab dann als Funktionsaufruf und nicht mehr als Stringliteral interpretieren.

Kompiliert man folgenden Code

  use strict 'vars';
  use vars '$alpha';
  @alpha = (1,2,3);

so sollte nun auch die Bedeutung der erhaltenen Fehlermeldung

  Variable "@alpha" is not imported

verständlicher werden - der Compiler ist in der letzten Zeile auf den Array-Namen @alpha gestoßen. Durch die Deklaration in der vorhergehenden Zeile wurde zwar bereits der entsprechende Typeglob angelegt, aber als importiert wurde nur der Skalar markiert, nicht das Array. Da Importings, wie erwähnt, typ-spezifisch sind, muss in diesem Fall durch

  use vars qw($alpha @alpha);

auch das Array vor dem ersten Ansprechen entsprechend deklariert werden.

Fehlt hingegen im obigen Beispiel die zweite Zeile (d.h., es wird überhaupt nichts mit vars deklariert), so existiert auch noch kein Typeglob und der Compiler bricht mit den wohlbekannten Meldungen

  Global symbol "$alpha" requires explicit package name
  Global symbol "@alpha" requires explicit package name

seine Arbeit ab.

Beachte, dass folgender Code (im Package main )

  use strict 'vars';
  $main::alpha = 2;
  print $alpha;

nicht funktioniert und der Compiler seine Arbeit ebenfalls mit der not imported Meldung beendet. Durch die Angabe des vollqualifizierten Variablennamens in der zweiten Zeile wurde der Typeglob *alpha zwar angelegt, aber der Skalar wird nicht als importiert markiert . Der Effekt ist also derselbe wie weiter oben, als der Skalar importiert, aber das Array angesprochen wurde. Das Importing funktioniert nur durch Zuweisung einer Referenz an den jeweiligen Typeglob L<[16]|/item__5b16_5d>.

Aus diesem Umstand erklärt sich auch die globale Auswirkung von vars und subs - sie manipulieren Typeglobs, und die sind (einschließlich eventueller Markierungen) nun einmal im gesamten Programm sichtbar und nicht nur innerhalb bestimmter Blöcke. Und daher gibt es auch kein no vars bzw. no subs - es macht keinen Sinn, einmal erfolgte Importings (deren Existenz ohnehin nur während des Kompilierens mit strict 'vars' von Bedeutung ist) wieder rückgängig zu machen.

Handles dürfen übrigens aufgrund fehlender Deklarationsmöglichkeit immer mit unqualifizierten Namen angesprochen werden, daher ist kein Importing notwendig und es gibt auch keine derartigen Markierungen.

Einrichten von Wrappern

Ein Wrapper ist eine Funktion, die um eine andere Funktion "gelegt" wird, um deren Funktionsumfang zu erweitern oder an spezielle Gegebenheiten anzupassen. Das Besondere daran ist, dass im Idealfall weder der Code, der die ursprüngliche Funktion (im folgenden als Original bezeichnet) aufruft, noch das Original selber merkt, dass es von einem Wrapper umgeben worden ist. Im Fall von Perl ist diese Bedingung erfüllt, wenn sein Builtin caller innerhalb des Originals vor und nach dem Wrappen dasselbe Resultat zurückliefert.

Durch den Einsatz von Typeglobs läßt sich das recht einfach erreichen. Zum Testen dient die folgende Funktion:

  sub hello {
    print 'Args:   ', join(',', @_), "\n",
          'Caller: ', join(',', caller), "\n\n";
  }

Der Code, der den Wrapper generiert, sieht wie folgt aus:

  1  sub create_wrapper {
  2    no strict 'refs';
  3    no warnings 'redefine';
  4    my $name = caller . '::' . shift;
  5    my $oldsub = *{$name}{CODE} or die "Cant't find subroutine '$name'!\n";
  6    my $newsub = sub {
  7      my ($pkg, $file, $line) = caller;
  8      print STDERR "WRAPPER: Hi! $name(@_) was called:\n",
  9                   "WRAPPER: from '$pkg', file '$file', line $line\n\n";
  10     goto &$oldsub;
  11   };
  12   *{$name} = $newsub;
  13   return $oldsub;
  14 }

Mittlerweile wird dieser vormals so kryptisch anmutende Code nun hoffentlich schon einigermaßen vertraut wirken - das Wesentliche sind dabei die Zeilen 5 und 12.

In Zeile 5 wird durch Auslesen des Codeslots des Typeglobs, dessen Name create_wrapper als Name der zu wrappenden Funktion übergeben wurde, eine Codereferenz, d.h. ein Zeiger auf diese Funktion in $oldsub abgespeichert (man hätte natürlich auch

  $oldsub = \&{$name};

schreiben können L<[17]|/item__5b17_5d>). Existiert die angegebene Funktion nicht, so ist der Slot unbelegt und man erhält undef zurück, worauf mit Ausgabe einer entsprechenden Meldung und Abbruch reagiert wird. Ansonsten wird nun eine neue Funktion - der Wrapper erzeugt, der hier ausgibt, von wem er aufgerufen wurde und anschließend mit

  goto &$oldsub;

zur ursprünglichen Funktion springt L<[18]|/item__5b18_5d>. Die gezeigte Verwendung von goto bewirkt, dass der Callstack des Wrappers durch den des Aufrufers ersetzt wird; für die angesprungene Funktion sieht alles so aus, als wenn sie direkt aufgerufen worden wäre. Dieses spezielle goto wird auch in Zusammenhang mit AUTOLOAD -Funktionen häufig verwendet.

Nun wird in Zeile 12 durch die Zuweisung an den Typeglob dessen Codeslot, der bislang immer noch auf das Original zeigt, mit einer Referenz auf die Wrapper-Funktion überschrieben. Das Ergebnis ist, dass nun eine neue Funktion - das "gewrappte" Original - unter demselben Namen aufrufbar ist L<[19]|/item__5b19_5d>.

Zwei Dinge sollen hier noch kurz erwähnt werden:

  • Da auch hier wieder mit symbolischen Referenzen jongliert wird (Zeile 5 und 12), ist ein no strict 'refs' unerläßlich. Weiters ist Perl sehr misstrauisch, was Zuweisungen an Typeglobs zur Laufzeit betrifft, wenn dabei Codeslots überschrieben werden, und weist mit

      Subroutine ... redefined
    

    auf solche Praktiken hin, wenn use warnings (oder der -w Schalter) angegeben wurde L<[20]|/item__5b20_5d>. Innerhalb von create_wrapper ist dieses Misstrauen allerdings ungerechtfertigt, daher wird die Ausgabe dieser Meldung mit dem entsprechenden no warnings 'redefine' Pragma unterbunden. In Perl-Versionen vor 5.6 hätte man (weniger spezifisch, aber genauso effektvoll)

      local $^W = 0;
    

    geschrieben.

  • Die Möglichkeit des Wrappens (oder vollständigen Umdefinierens) von Funktionen zur Laufzeit ist nicht auf selbstbenannte Funktionen beschränkt. Reservierte Funktionen wie AUTOLOAD oder DESTROY , aber auch Methoden (die ja im Prinzip auch nur Funktionen mit einer anderen Aufrufkonvention sind), können genauso damit "behandelt" werden. So kann man das Verhalten von Autoload-Code, aber auch von DESTROY beeinflussen, und, wenn Packages als Klassen verwendet werden, durch Erzeugen neuer benannter Funktionen diesen Klassen Methoden zur Laufzeit hinzufügen oder bestehende abändern.

    Nicht funktionieren wird das allerdings mit den reservierten Namen von Blöcken wie z.B. BEGIN , weil diese Namen direkt vom Parser beim Kompilieren erkannt und intern anders abgespeichert werden - ein Typeglob repräsentiert ja nur genau einen Namen, es kann aber z.B. mehrere BEGIN -Blöcke geben.

Das folgende Beispiel zeigt einen Aufruf von create_wrapper in der Datei cre_wrp.pl :

  hello(qw[a b c]);
  my $oldfunc = create_wrapper('hello');
  hello(qw[d e f]);
  {
    no warnings 'redefine';
    *hello = $oldfunc;
  }
  hello(qw[g h i]);

und deren Ausgabe:

  Args:   a,b,c
  Caller: main,cre_wrp.pl,1

  WRAPPER: Hi! main::hello(d e f) was called:
  WRAPPER: from 'main', file 'cre_wrp.pl', line 3

  Args:   d,e,f
  Caller: main,cre_wrp.pl,3

  Args:   g,h,i
  Caller: main,cre_wrp.pl,8

In der ersten Zeile wird das Original aufgerufen, das ausgibt, mit welchen Argumenten und von wem es aufgerufen wurde. Danach wird der Wrapper erzeugt und man erhält nun beim Aufruf von hello() vor dessen Ausgabe noch den Output des Wrappercodes. Beachte, dass die von caller gelieferten Werte - von der Zeilennummer abgesehen - in beiden Fällen dieselben sind, es handelt sich also um einen "echten" Wrapper.

Der Code ab der vierten Zeile zeigt, wie man die Wirkung von create_wrapper wieder rückgängig machen kann. In der zweiten Zeile wurde der von dieser Funktion zurückgelieferte Wert - eine Referenz auf den Code des Originals - in einem Skalar zwischengespeichert. Innerhalb des folgenden Blocks wird nun durch Zuweisung dieses Skalars an den Typeglob wieder der alte Zustand hergestellt; beim nächsten Aufruf von hello() wird daher wieder das Original, ohne Wrapper angesprungen.

Auch hier muss vorher wieder die Ausgabe der redefined Meldung mittels no warnings unterdrückt werden, und die sauberste Art, dies zu tun, ist das Unterdrücken und die Zuweisung innerhalb eines eigenen Blocks (oder ebenfalls in einer Funktion).

Eine denkbare Erweiterung des obigen Codes wäre das Zwischenspeichern auch der zweiten Codereferenz auf den Wrapper und dann (z.B. mittels zweier Funktionen wrapper_on und wrapper_off ) durch Zuweisung der jeweiligen Referenz an den Typeglob zwischen der Version mit Wrapper und der ohne Wrapper "hin- und herzuschalten". Die sich aus dieser Technik ergebenden Möglichkeiten sind außerordentlich vielfältig.

Umgang mit Handles

Neben den bisher erwähnten Wertetypen ( Skalar , Array , Hash , Code ) gibt es noch zwei weitere: Datei- und Formathandles . Ähnlich wie der Code-Typ, aber im Gegensatz zu den anderen, verfügen sie über keine besondere Kennzeichnung im Namen (kein Sigil ). Sie werden also mit Barewords angegeben. Das bedeutet, dass sie nur an solchen Stellen vorkommen dürfen, an denen sie der Parser explizit erwartet. Sollen sie etwa als Argumente im Aufruf selbst geschriebener Funktionen verwendet werden, so muss der Parser durch einen entsprechenden Prototyp darauf vorbereitet werden, was oft unerwünscht ist.

Während dies bei Formathandles weniger problematisch ist, da diese ein eher bescheidenes Dasein führen L<[21]|/item__5b21_5d>, wird es bei Dateihandles manchmal als lästig empfunden, weil dadurch z.B. keine Übergabe von und an Funktionen und keine Lokalisierung möglich ist. Weiß man aber, dass man an Stellen, an denen ein Dateihandle erwartet wird, auch einen Typeglob verwenden kann (über dessen IO Slot dann der Handle angesprochen wird), dann sind die durch die Barewords von Handles vorgegebenen Einschränkungen kein Problem mehr L<[22]|/item__5b22_5d>.

Möchte man den Handle einer geöffneten Datei an eine Funktion zur Verarbeitung übergeben, so kann man

  open FH,'input.dat';
  process(*FH);

  sub process {
    my $fh = shift;
    while (<$fh>) {
      ...
    }
  }

schreiben. An process wird einfach der gleichnamige Typeglob übergeben. Dieser wird innerhalb der Funktion in einen Skalar kopiert und danach über diesen der Typeglob und der darin enthaltene IO Slot angesprochen. Eine weniger schöne Alternative wäre:

  sub process {
    *SUBFH = shift;
    while (<SUBFH>) {...

Hier wird der Typeglob in einen zweiten kopiert und in Befehlen, die die Angabe eines Handles durch ein Bareword vorsehen, dieser verwendet. Diese Alternative ist deswegen weniger schön, weil der innerhalb der Funktion angelegte Typeglob ebenfalls global ist (wodurch man sich die Übergabe als Funktionsargument sparen und gleich *FH hätte verwenden können) und damit die Gefahr besteht, inner- und außerhalb der Funktion unabsichtlich denselben Handle bzw. Typeglob zu verwenden. Außerdem ist damit ein rekursives Verwenden der Funktion nicht möglich (sie ist non-reentrant ).

Abzuraten ist auch von folgendem Code:

  $name = 'FH';
  open $name,'>output.dat';     # Dasselbe wie open FH,'>output.dat';
  print $name 'Hallo, Welt!';   # Dasselbe wie print FH 'Hallo, Welt!;

Hinter diesem Code steckt nichts weiter als eine symbolische Referenz L<[23]|/item__5b23_5d>; der Skalar, der als Handle-Argument in open verwendet wird, enthält hier einen String und daher wird dieser als Handlename (d.h., Name des Typeglobs) verwendet. Die Verwendung von symbolischen Referenzen in Verbindung mit IO Handles ist auch deswegen gefährlich, weil sie leicht zu falschen Annahmen führen kann:

  sub process {
    my $fh = 'FH';
    open $fh,'>temp.tmp.';
    print $fh 'Hallo, Welt!';
  }

Hier wird innerhalb der Funktion eine lexikalische Variable, die als Filehandle verwendet wird, deklariert. Es liegt die Vermutung nahe, dass nach Beendigung der Funktion durch das Verschwinden der lexikalischen Variable infolge des Blockendes auch die Datei geschlossen wird. Diese Vermutung ist jedoch falsch. Da es sich nur um eine symbolische Referenz handelt, besteht zwischen dem lexikalischen Skalar und dem über ihn angesprochenen Handle kein Zusammenhang; der globale Typeglob (hier also *FH ) und der darin enthaltene IO Handle bleiben nach dem Ende der Funktion unverändert bestehen und somit die betreffende Datei auch weiterhin geöffnet. Die intuitiv erwartete "Bereinigung" lexikalischer Komponenten am Blockende findet hier also nicht statt, und daher sollte man solchen Code - auch im Interesse derer, die ihn später warten müssen - tunlichst vermeiden.

Eine Möglichkeit, das automatische Schließen einer Datei bei Beenden einer Funktion zu erreichen, besteht im Lokalisieren des Typeglobs , der als Handle verwendet wird:

  sub process {
    local *FH;
    open FH,'>temp.tmp';
    ...
  }  # temp.tmp wird jetzt geschlossen

Durch das Lokalisieren des Typeglobs werden alle Slots temporär abgespeichert und durch neue, unbenutzte Slots ersetzt. War also bereits eine Datei geöffnet, so bleibt sie geöffnet, kann aber innerhalb der Funktion nicht mehr angesprochen werden. Stattdessen wird ein neu angelegter, lokalisierter IO Slot zum Öffnen der temporären Datei verwendet. Beim Erreichen des Funktionsendes muss Perl wieder den abgespeicherten Slot zurückholen. Dabei erkennt es, dass der temporäre Slot noch belegt ist, und dass daher vor dessen Freigabe die betreffende Datei erst geschlossen werden muss.

Für mehr Informationen zum Lokalisieren - speziell von Typeglobs - siehe perlscopetut.

Mit Perl 5.6 wurde Auto-Vivification mit IO Handles eingeführt. Diese Fähigkeit, die man am besten mit "zum Leben erwecken" übersetzen kann, gibt es auch in anderen Fällen, z.B. wird mit

  $alle->[4]->{gamma}->[76] = 'Hallo';

das 77. Element eines anonymen Arrays belegt, auf das das Element gamma eines anonymen Hashes zeigt, welches wiederum über das fünfte Element eines weiteren anonymen Arrays angesprochen wird, auf das schließlich die Referenz in $alle zeigt. Mit Auto-Vivification ist nun hier gemeint, dass alle diese Elemente und Referenzen beim Ausführen dieses einen Befehls automatisch angelegt werden, wenn sie noch nicht existieren.

Das Gleiche ist ab 5.6 bei

  open $fh,'>temp.tmp';

der Fall, wenn der Skalar $fh zum Zeitpunkt der Ausführung noch auf undef gesetzt ist. Da der Skalar noch nichts (d.h, auch keinen String, der als symbolische Referenz verwendet werden könnte) enthält, wird automatisch ein Typeglob und in dessen IO Slot der Handle "zum Leben erweckt" und in den Skalar eine Referenz darauf geschrieben. Handle und Typeglob sind hier aber, wie auch die Arrays und Hashes im Beispiel weiter oben, anonym , d.h., man kann den IO Handle nur über den Skalar, der nun eine echte Referenz enthält L<[24]|/item__5b24_5d>, ansprechen. Und damit wird auch klar, dass man mit

  {
    open my $fh,'>temp.tmp';
    print $fh 'Hallo, Welt!';
    ...
  }

nun auch erreichen kann, dass eine Datei bei Verlassen des Blocks (oder einer Funktion) automatisch geschlossen wird: da der lexikalische Skalar der einzige Bezugspunkt zum anonymen Typeglob ist, kann dieser, nachdem der Skalar verschwunden ist, ebenfalls entfernt werden und damit wird vorher implizit die Datei, die über sein IO Handle geöffnet war, geschlossen.

Auto-Vivification wird natürlich nicht nur von open , sondern auch von alle anderen Builtins, die Handles anlegen (z.B. opendir , pipe , sysopen , socket und accept ) unterstützt.

Aber auch Benutzer älterer Perl-Versionen brauchen auf diese Funktionalität nicht zu verzichten: es gibt zwar noch keine Auto-Vivification, aber die Verwendung von Handle-Referenzen ist genauso unterstützt. Man muss Perl nur etwas unter die Arme greifen:

  my $fh = \*FH;        # Erzeuge Globreferenz
  delete $main::{FH};   # Loesche Stashelement (Typeglob wird anonym)
  open $fh,'>x.x';      # Weiter wie gehabt

Hier wird zunächst eine Referenz auf einen benannten Typeglob angelegt. Anschließend wird der Eintrag des Globs aus der Symboltabelle (Stash) wieder gelöscht L<[25]|/item__5b25_5d>, der Typeglob wird dadurch anonym. Nun kann der Skalar genauso wie beim Auto-Vivifying als Handle verwendet werden; auch hier wird die Datei automatisch geschlossen, wenn der Bereich verlassen wird, innerhalb dessen $fh sichtbar ist.

In diesem Zusammenhang soll auch noch die Funktion

  Symbol::geniosym();

erwähnt werden, deren Code vereinfacht als

  sub geniosym {
    my $name = 'GEN'. $genseq++;
    my $sym = \*{'Symbol::' . $name};
    delete $Symbol::{$name};
    select(select $sym);
    return *{$sym}{IO};
  }

dargestellt werden kann. Die ersten drei Zeilen erzeugen, wie weiter oben beschrieben, einen anonymen Typeglob. Die vorletzte Zeile erzwingt durch die Verwendung von select die Initialisierung des IO Slots des erzeugten Typeglobs (dadurch wird vermieden, dass die Funktion eine Referenz auf undef zurückliefert), und die letzte Zeile gibt das Handlesymbol (eine Referenz auf den IO Slot) zurück. Dadurch kann der Slot direkt über die Referenz und somit schneller als über einen Typeglob angesprochen werden, allerdings ist die Erzeugung, wie man sieht, dafür umständlicher. Der von geniosym zurückgelieferte Wert ist somit keine Glob-, sondern eine IO (Handle) Referenz L<[26]|/item__5b26_5d>.

Tatsächlich kann man an open (und die anderen Funktionen zum Anlegen von Filehandles) folgendes direkt oder in einem Skalar übergeben:

  $fh =  undef;     open $fh,'x.x';     # Auto-Vivifying
  $fh = \*GLOB;     open $fh,'x.x';     # Globreferenz
  $fh =  *GLOB;     open $fh,'x.x';     # Typeglob
  $fh =  *GLOB{IO}; open $fh,'x.x';     # IO (Handle) Referenz

wobei Methoden 1 und 4 erst ab Perl 5.6 unterstützt sind L<[27]|/item__5b27_5d>. Das mit Methode 1 durchgeführte Auto-Vivifying entspricht, wie weiter oben gezeigt, der Globreferenz von Methode 2.

Das Arbeiten mit lexikalischen Globreferenzen ist die einfachste und sicherste Möglichkeit, mit Handles umzugehen. Bis Perl 5.6 wurden stattdessen Typeglobs direkt verwendet und waren zumindest in diesem Bereich absolut unentbehrlich. Da man immer wieder auf Code, der typeglob-basierende Handles verwendet, stoßen kann, macht es Sinn, auch heute noch darüber Bescheid zu wissen.

Fußnoten

[1]

Das ist nur ein Beispiel zum Veranschaulichen.

Hier geht es wieder Zurück.

[2]

Von dieser Regel gibt es Ausnahmen: die Symbole ARGV , ARGVOUT , ENV , INC , SIG , STDERR , STDIN und STDOUT werden immer im Stash %main:: angelegt (und später dort gesucht), auch wenn mit package gerade ein anderes Package ausgewählt ist. Sollen diese Symbole in anderen Stashes angelegt werden, so ist der Symbolname immer vollqualifiziert anzugeben. Jedoch bleibt die spezielle Bedeutung der einzelnen Variablen auf jene im Stash %main:: beschränkt.

Beachte, dass hier von Symbolen die Rede ist: Obiges trifft daher z.B. auf $ENV , @ENV , %ENV , &ENV , das Dateihandle ENV und das Formathandle ENV zu, d.h., auf alle Wertetypen mit den angeführten Namen ( *ENV ).

Es trifft weiters auch auf alle Interpunktions-Variable (solche, deren Name aus keinem alphabetischen Zeichen besteht oder damit beginnt) zu. Bei solchen Variablen erlaubt der Parser außerdem keine Angabe eines Packagenamens, so dass diese Variable immer nur im Stash %main:: vorkommen können.

Zurück

[3]

"Wissen" ist hier im übertragenen Sinn zu verstehen - die Stashelemente werden bereits beim Kompilieren angelegt und der vom Compiler generierte Code greift nur über deren Adressen auf den Inhalt zu. Der Zugriff über den Stash wird nur notwendig, wenn ein Variablenname erst zur Laufzeit gebildet wird, wie das z.B. in Code wie

  my $package = 'Hugo';
  my $name = 'Test';
  ${$package.'::'.$name} = 'Hallo';

geschieht (d.h., über symbolische Referenzen) . Hier kann beim Kompilieren keine Elementadresse verwendet werden, da Stash- und Keynamen noch nicht bekannt sind.

Zurück

[4]

Aus Implementierungsgründen ist der Skalar-Slot eines Typeglobs immer belegt; wird er nicht verwendet, so enthält er den Wert undef (bzw. eine Referenz darauf).


Zurück

[5]

Und Sache desjenigen, der später solchen Code warten muss.


Zurück

[6]

Wie bereits erwähnt, laufen diese Vorgänge beim Kompilieren ab; zur Laufzeit wird nur mehr über die vom Compiler generierte Adresse auf den Typeglob zugegriffen.


Zurück

[7]

Es ergeben sich außerdem erhebliche Unterschiede im Umgang mit lexikalischen Variablen, die außerhalb einer Funktion deklariert, aber innerhalb der Funktion angesprochen werden. Siehe perlscopetut.


Zurück

[8]

Das gilt übrigens auch für Funktions- und Handle-Bezeichnungen. Anders gesagt, bezieht sich use strict 'vars' - Nomen est omen - nur auf Variablenamen .


Zurück

[9]

Es wird an den Typeglob eine Referenz auf undef zugewiesen. undef ist ein skalarer Wert, daher handelt es sich um eine Skalarreferenz, die den Skalarslot des Typeglobs löscht. Anders gesagt bewirken die folgenden Befehle dasselbe:

  *hugo = \undef;
  $hugo =  undef;

Beachte, dass der vom Compiler generierte Code jedoch unterschiedlich ist, d.h., die beiden Befehle bewirken dasselbe, sind aber nicht identisch .


Zurück

[10]

Genauer gesagt, enthält der Hash-Slot des Typeglobs von $main::{main::} eine Referenz auf %main:: . Anders wäre es nicht möglich, wie im Beispiel gezeigt, mit

  *main::{HASH}

auf die oberste Symboltabelle zuzugreifen. Da ein Typeglob immer in einem Element eines Stashes abgelegt ist, muss es natürlich auch für das Element main:: einen Hash - eben %main:: - geben. Somit könnte man den Typeglob *main auch als Hashelement

  $main::{'main::'}

ansprechen. Puh!

(Beachte, dass hier der Keyname gequoted werden muss, um den Parser nicht durcheinander zu bringen, weil dieser ansonsten die Doppelpunkte "verschlucken" würde. Auch ein Parser hat irgendwann einmal genug!).


Zurück

[11]

Wobei man im Kopf behalten sollte, dass dieser Code im Context eines BEGIN -Blocks abgearbeitet wird, d.h., bevor die durch das partielle Aliasing angelegten Variablen angesprochen werden.


Zurück

[12]

There Is More Than One Way To Do It - es gibt mehrere Wege, (in Perl) etwas zu tun. Eines der Mottos der Perl-Gemeinde, das auf den Umstand anspielt, dass Perl's Parser in seinen Syntaxprüfungen sehr großzügig ist und es daher fast immer etliche Möglichkeiten gibt, ein und dasselbe Problem mit teilweise erheblich unterschiedlichem Code zu lösen.


Zurück

[13]

Der Ausdruck

  *{$pkg.'::'.$sym}{GLOB}

liefert eine Referenz auf den Typeglob. Das ist hier aber nicht verlangt, vielmehr soll der Exporter bei einer Angabe von *symnam tatsächlich ein vollständiges Aliasing durchführen, und das geschieht bekanntlich durch Zuweisung eines Typeglobs direkt an einen anderen. Daher fehlt im ursprünglichen Code auch der Referenzoperator vor dem '*' .

In den laufenden Perl-Versionen sind die Zuweisungen

  *alpha = \*beta;
  *alpha =  *beta;

identisch; der Compiler generiert zwar genau diesen Code (mit und ohne Referenzoperator), aber im zweiten Fall wird das \ zur Laufzeit verworfen. Dieses Verhalten ist allerdings nicht festgelegt und kann sich in künftigen Perl-Versionen ändern (bzw. war möglicherweise in früheren Versionen schon anders), daher wurde im Exporter darauf Bedacht genommen und man sollte das ggf. auch selber tun.


Zurück

[14]

Auf die seit Perl 5.6 bestehende Möglichkeit, globale Variable mit our zu deklarieren, wird in perlsub und perlscopetut eingegangen.


Zurück

[15]

Es gibt noch einen subtilen Unterschied zwischen Exporter.pm und vars.pm . Letzterer weist auch im Fall eines Typeglobs (z.B. use vars '*hugo' ) eine Referenz an den Zieltypeglob zu. Der Exporter führt hier gleich ein direktes Aliasing durch. Aber wie in L<[13]|/item__5b13_5d> schon angeführt, macht das (zumindest in den aktuellen Perl-Versionen) keinen Unterschied, was nicht heißt, dass sich das nicht künftigen Versionen ändern kann und dann ein Anpassen des vars Pragmas notwendig wird.

Ident hingegen ist beiden Modulen, dass ihr Code als BEGIN -Block, d.h., während der Compilerphase abläuft und durch Aliasung bzw. Importing das Verhalten des Compilers beeinflusst.


Zurück

[16]

Außerdem muss tatsächlich etwas importiert werden, d.h., das gerade eingestellte Package muss unterschiedlich sein zu dem, in das importiert wird. Folgender Code würde daher nicht funktionieren:

  use strict 'vars';
  package main;

  BEGIN {
    *main::hugo = \$main::hugo;
  }
  $hugo = 2;

Wird in diesem Beispiel der Name in der package -Anweisung geändert und vor die Zuweisung an $hugo ein package main; eingefügt, dann wird der Code funktionieren.

Im Falle der Pragmas ist das kein Problem, da ja während der Abarbeitung ihres Codes ohnehin gerade die Packages vars bzw. subs aktiv sind.


Zurück

[17]

Obwohl die beiden Schreibweisen

  *glob =  *func{CODE};
  *glob = \&func;

letztlich dasselbe bewirken, werden unterschiedliche Tätigkeiten durchgeführt (und daher ist auch der kompilierte Code unterschiedlich): im ersten Fall wird der Slot eines Typeglobs ausgelesen und die darin enthaltene Referenz zugewiesen. Im zweiten hingegen wird der Funktionswert als solcher angesprochen und erst eine Referenz darauf erzeugt, bevor die Zuweisung durchgeführt wird. Anders gesagt liefert das zweite Konstrukt zur Laufzeit genau denselben Wert, der im Slot des Typeglobs bereits durch den Parser beim Kompilieren des Codes abgelegt wurde.

Meistens wird die zweite Schreibweise verwendet, obwohl eigentlich die erste transparenter macht, was hier tatsächlich zugewiesen wird. Außerdem ist letztere, da lediglich ein bereits vorhandener Wert kopiert wird, effizienter.


Zurück

[18]

Tatsächlich ist diese Schreibweise unrichtig - genau genommen, bedeutet

  goto &$oldsub;

"Rufe die Funktion, deren Name (oder Referenz) in $oldsub steht, auf, und gehe anschließend zu jener Stelle, auf die die von ihr zurückgelieferte Codereferenz zeigt". Da das aber in den seltensten Fällen wirklich gewüscht ist, zeigt der Parser hier Intelligenz, indem er daraus ungefragt ein

  goto \&$oldsub;

macht, was tatsächlich dem entspricht, was erwartet wird. Und damit ist klar, dass man kürzer auch gleich

  goto $oldsub;

schreiben kann. Kurioserweise findet man in der Fachliteratur fast immer die obige, eigentlich unrichtige Schreibweise vertreten, weswegen sie auch im Beispiel beibehalten wurde. Trotzdem soll auf diese Ungenauigkeit hingewiesen werden.


Zurück

[19]

Tatsächlich erzeugt create_wrapper eine Closure , d.h., eine Funktion, die eine lexikalische Variable ( $oldsub ), die außerhalb ihres Geltungsbereiches liegt, anspricht. Dadurch wird der Wert der Variablen, den diese zum Zeitpunkt der Definition der Closure hatte, darin "eingeschlossen" (Name!). Es können beliebig viele derartige Closures gleichzeitig erzeugt werden, jede mit ihrem eigenen Wert für $oldsub . Mehr dazu in perlref und perlscopetut.

Während Closures meistens nur als anonyme Subroutinen angelegt werden, erhalten sie hier - da sie ja bestehende, benannte Funktionen ersetzen sollen - durch Zuweisung ihrer Codereferenzen an Typeglobs ebenfalls Namen.


Zurück

[20]

Und der wird doch immer angegeben, oder etwa nicht? ;-)


Zurück

[21]

Zu unrecht, wie ich meine. Das in Perl eingebaute, automatische Formatierungssystem, das mit den Variablen $. , $% , $= , $- , $~ , $^ , $: und $^L und den Befehlen format (Deklaration) und write (Ausführung) gesteuert wird, ist sehr leistungsfähig und Perl verdankt das "r" in seinem Namen ( Practical Extraction & Report Language ) nicht zuletzt diesem System. Ein Blick in perlform lohnt sich allemal. Die einzige Einschränkung ist, dass Formate, wie auch Packages, Subroutinen und lexikalische Variable deklariert werden müssen und daher bereits beim Kompilieren namentlich bekanntzugeben sind.


Zurück

[22]

Auf die sich durch das Auto-Vivifying ab Perl 5.6 ergebenden Möglichkeiten wird weiter unten eingegangen; es werden zunächst die Wege, die bereits in älteren Perl-Versionen vorhanden waren, erläutert.


Zurück

[23]

Was man bei der Verwendung von strict 'refs' auch schnell merkt.


Zurück

[24]

Man kann sich davon mit

  print ref $fh;        # Gibt 'GLOB' (d.h., Globreferenz) aus

überzeugen.


Zurück

[25]

Hier unter der Annahme, dass der Code im Package main ausgeführt wird.


Zurück

[26]

Allerdings wird man über die Ausgabe von

  use Symbol 'geniosym';
  my $sym = geniosym();
  print ref $sym;               # Gibt 'IO::Handle' aus

etwas erstaunt sein - ein IO Handle (d.h., die IO Datenstruktur, auf die die Referenz in $sym zeigt), ist aus implementierungstechnischen Gründen stets ein Objekt des Types IO::Handle (d.h., eine Referenz auf einen Wert, der mit dieser speziellen Klasse "gesegnet" wurde). Dieser Mechanismus ermöglicht das Verwenden von IO Handles als Objektinstanzen. So sind folgende Befehle (weitgehend) identisch:

  print $sym 'Hallo';

  $sym->print('Hallo');

oder, da hier auch wieder Typeglobs als Stellvertreter verwendet werden können:

  print FH 'Hallo';     # oder: print {*FH} 'Hallo';

  FH->print('Hallo');   # oder: *FH->print('Hallo');

Auch wenn es nicht so aussieht, ist FH hier kein Klassenname, sondern eine Objektinstanz - genauer gesagt wird damit das durch den IO Slot des Typeglobs *FH repräsentierte Objekt und darüber die Methode print angesprochen. Ganz schön gefinkelt, nicht wahr?

Der objekt-orientierte Ansatz hat den Vorteil der größeren Flexibilität: durch Vererbung können Methoden hinzugefügt werden und damit kann der Funktionsumfang eines IO Handles weit über die von Perl's Builtin-Funktionen bereitgestellten Möglichkeiten hinausreichen. Außerdem wird dadurch die Benutzung von Funktionen oder Eigenschaften, die sich auf das gerade aktuelle Handle beziehen, erleichtert - "das gerade aktuelle" Handle ist dann die Objektinstanz, die beim Aufruf der jeweiligen Methode angegeben wird. Das macht Code, der mit solchen Methoden arbeitet, transparenter.


Zurück

[27]

Man kann in Perl-Versionen vor 5.6 auch eine noch nicht verwendete Handlereferenz an open und Konsorten übergeben, man muss sie nur vorher über den Typeglob initialisieren :

  select (select *GLOB);        # Initialisierung
  my $fh = *GLOB{IO};           # IO-Referenz holen
  open $fh,'x.x';               # An open() uebergeben

Der select Befehl legt eine Handlestruktur an und läßt den IO Slot des angegebenen Typeglobs darauf zeigen. Danach kann dieser in den Skalar übernommen (oder auch direkt mit open() angegeben) werden.

Nicht, dass man das umbedingt brauchen würde, es sollte nur der Vollständigkeit halber gezeigt werden. :-)

Dieser Umstand (ein IO Slot musste bis Perl 5.6 vor der ersten Benutzung initialisiert werden), ist auch der Grund dafür, dass dies, wie weiter oben gezeigt, in Symbol::geniosym (das natürlich auch noch ältere Perl-Versionen unterstützt) immer noch geschieht. In perldata (Abschnitt Typeglobs und Filehandles ) wird sogar extra darauf hingewiesen:

  That's because *HANDLE{IO} only works if HANDLE has already been used
  as a handle.  In other words, *FH must be used to create new symbol
  table entries; *foo{THING} cannot.

Ab Perl 5.6 stimmt das nicht mehr.


Zurück

SIEHE AUCH

perlsub, perlref, perldata, perlmod, perlform, perlscopetut.

AUTOR UND COPYRIGHT

Copyright (c) 1998, 2000, 2006 by Ferry Bolhar bol@adv.magwien.gv.at . Alle Rechte vorbehalten.

Dieses Dokument darf beliebig verteilt und die darin enthaltenen Code-Beispiele können beliebig verwendet und den jeweiligen Gegebenheiten angepasst werden. Dokumentationen, die daraus abgeleitet sind, müssen diesen Copyright-Hinweis vollständig enthalten. Es gelten ansonsten die Lizenzbestimmungen der jeweiligen Perl-Distribution.

-- FerryBolhar - 23 Aug 2007

-- HaraldBongartz - 04 Sep 2007

Topic attachments
I Attachment Action Size Date Who Comment
perlglobtut.podpod perlglobtut.pod manage 78.0 K 2007-09-06 - 13:40 FerryBolhar Neue Perl-Doku: Typeglobs, Stashes und Exporter
Topic revision: 2007-09-06, FerryBolhar
 
Bitte die NutzungsBedingungen beachten.
Bei Vorschlägen, Anfragen oder Problemen mit dem PerlCommunityWiki bitten wir um WebBottomBarExample">Rückmeldung.