Perl Idioms
(Weiterleitung von Perl Idiom)
 
StartSeite | Neues | TestSeite | ForumSeite | Teilnehmer | Kategorien | Index | Hilfe | Einstellungen | Ändern

Von WiederkehrendeBedürfnisse. Die Seite für kleine, nützliche Perl-Redewendungen oder "snippets". Dinge, die der erfahrende Programmierer sucht, wenn er sich in Perl einarbeiten möchte. Um Spenden wird gebeten.

Inhaltsverzeichnis dieser Seite
Der Name des aktuellen Programms   
Programmparameter   
Umgebungsvariablen   
Aktuelles Datum und Uhrzeit   
Zeichenkettenersetzung in Strings   
idiomatisches Funktionsargumente-Entnehmen   
Parameterübergabe By Reference   
Die Bedeutung des &   
Warum kein & ?   
Wann doch & ?   
idiomatische Verwendung von local und speziellen Variablen   
Schwartzsche Transformation   
Arbeit mit Dateien   
Modularisierung   
Benchmarks (KategorieZeit)   


Der Name des aktuellen Programms    

Unter den vielen spezialisierten und vordefinierten Variablen gibt es die Variable $0, die den kompletten Pfad des ausgeführten Scripts enthält:

$ProgrammName=$0;


Programmparameter    

Da Perl direkt von C "abstammt", ist auch die Umstellung, was Programmargumente angeht, nicht groß. Die Stehen in @ARGV, einem ansonsten ganz normalen (also auch manipulierbaren) Array:

$ARGV[0] ... der 1. Programmparameter (nicht wie in C der Programmname)
$ARGV[1] ... der 2. Programmparameter
...


Umgebungsvariablen    

Eine vordefinierte HashTabelle %ENV enthält die Namen der Umgebungsvariablen und ihre Werte. Man braucht sie nur auslesen:

$value = $ENV{'NameDerUmgebungsvariable'};

Welche Umgebungsvariablen sind definiert:

print join(' ',keys %ENV);

Schliesslich eine einfache Ausgabe aller Umgebungsvariablen:

printf "%20s: %s\n",$_ => $ENV{$_} for keys %ENV


Aktuelles Datum und Uhrzeit    

Die Funktion time() liefert die aktuellen Epochensekunden. Die spezielle Variable $^T speichert die Zeit des Programmbeginns, auch in Epochensekunden. Mit Hilfe von localtime() kann man diesen Wert in seine Komponenten zerlegen.

@MonthNameLong = ("Januar", "Februar", "März", "April", "Mai", "Juni",
    "Juli", "August", "September", "Oktober", "November", "Dezember");

sub TimeRetDateLong {
  my ($t)=@_;
  my ($sec, $min, $hour, $day, $month, $year) = localtime($t);
  return "$day. " . $MonthNameLong[$month]. " " . ($year+1900); 
}

print TimeRetDateLong( time );

sub TimeRetDateShort {
  my ($t)=@_;
  my ($sec, $min, $hour, $day, $month, $year) = localtime($t);
  return "$day." . "$month." . ($year+1900); 
}

sub SystemRetDateShort {
  return TimeRetDateShort( time );
}

Grundlegende Datumsberechnungen lassen sich auf Basis der Epochensekunden erledigen, indem man mit Hilfe von Time::Local (Standardmodul) ein Datum in Epochensekunden umwandelt und mit diesen einfach rechnet.

Abstrakter geht es mit dem Modul Date::Calc. Zu finden auf CPAN - ComprehensivePerlArchiveNetwork.


Zeichenkettenersetzung in Strings    

Auf Grund der mächtigen eingebauten Funktionen macht man das in Perl mit Einzeilern. Nur hier zur Demonstration als Funktion:

sub StrReplaceGlobal {
  my($s,$s1,$s2,$global)=@_;
  if($global eq "") {
    $s =~ s/\Q$s1\E/$s2/;
  } else {
    $s =~ s/\Q$s1\E/$s2/g;
  }
  return $s;  
}

Da die Parameterübergabe ByReference in Perl möglich, aber nicht üblich ist, wird hier das Ergebnis als Return-Wert übergeben.\Q und \E dienen dazu, den Suchbegriff zu quoten. Ansonsten würde nach dem regulären Ausdruck in $s1 gesucht.

Im Zusammenhang \Q und \E sei auch quotemeta() erwähnt.

Die RegularExpressions in Perl können (und das ist eher eine Untertreibung) tausendmal mehr und heben Perl wirklich in eine eigene Klasse was die Unterstützung von Stringverarbeitung betrifft. Vieles läßt sich in anderen Sprachen nicht einmal mit Bibliotheksfunktionen nachbilden.


idiomatisches Funktionsargumente-Entnehmen    

Da dieses Dokument PerlIdioms heisst, soll es auch ein wenig um solche gehen. So zum Beispiel das idiomatische Argumenteverarbeiten via shift.

sub eine_funktion {
    my $argument = shift;

    #...
}

Der shift Operator nimmt im Normalfall als Argument ein Array an, von dem er das erste Element entfernt und zurückgibt. Bekommt shift keine Argumente, verwendet es @_. Ein argumentloses shift gibt also immer das nächste Argument der Funktion zurück. Befindet sich ein argumentloses shift ausserhalb einer Funktion im Hauptprogramm, wird es nicht von @_, sondern von @ARGV Elemente entnehmen.

Das wirkt zwar etwas verwirrend, ist aber mit die populärste Methode Funktionsargumente zu verarbeiten. Die zweite populäre Variante (und aus Sicht der Verwender anderer ProgrammierSprachen die "bessere") ist die Verwendung folgender Methode:

sub eine_funktion {
    my ( $arg1 , $arg2 , ... ) = @_;

    # ...
}

Der Vorteil für den SoftwareEntwickler liegt auf der Hand: die Ähnlichkeit zu SpracheCee, SpracheJava und und und. Dennoch bietet die shift-Variante eine Möglichkeit, die es so populär macht:

use Carp;
sub noch_eine_funktion {
    my $argument1 = shift or croak "Ich hätte gerne mind. 1 Argument. Danke";
    my $argument2 = shift || 'Default-Wert';

    # ...
}

Allgmein ist "man" sich einig, dass die Listenvariante stets zu empfehlen ist, wenn viele Argumente übersichtlich zu verarbeiten sind.


Parameterübergabe By Reference    

Real beobachtbarer Perl-Code enthält relativ selten Parameterübergaben "by Reference" (bR). Aber es ist zumindest bei Skalaren leicht möglich. Skalare, die nicht w. o. dem Argumentarray @_ entnommen werden, wirken an sich schon als bR:

sub StrStripHtml {
  $_[0] =~ s/<[^<>]*>//g;
}

Aufruf:
  &StrStripHtml($text);

Arrays und Hashes werden bei der Parameterübergabe automatisch in die Einzelelemente aufgelöst. Will man hier bR, dann muss explizit eine Referenz mit "\" erzeugt werden:

sub HashBrCountArrayBr {
  my ($h_hash,$a_array)=@_;
  foreach (@$a_array) {
    $$h_hash{$_}++;
  }
}

my %count;
my @words;
...
while (<*.txt>) {
  @words=FileRetWords($_);
  HashBrCountArrayBr(\%count,\@words);
  # ersetzt:
  # foreach (@words) {
  #   $count{$_}++;
  # }
}

Obiger Code (ungetestet) übergibt nur zwei skalare Referenzen an das Unterprogramm und zählt die Array-Elemente.

Die obige Schreibweise mit $h_hash und $a_array (bzw. $p_skalar) ist nur ein persönliche Schreibweise um Referenzen im Zeilenfokus eindeutig lesbar und verwendbar zu machen. -- HelmutLeitner


Die Bedeutung des &    

Das & vor Funktionsaufrufen ist eigentlich ein Relikt aus Perl4. Es gilt in Perl5: In der Regel ist es nicht nur überflüssig, sondern sogar gefährlich. Um eine Funktion aufzurufen, reicht völlig:

funktion( argumente )

Um eine Funktion ohne Argumente aufzurufen, empfiehlt sich folgendes:

funktion()

Das entspricht auch dem, was man von SpracheCee bzw. SpracheJava gewohnt ist.

Verzichtet man auf die Klammern, muss die Funktion vorher deklariert sein, weil sonst der Parser nicht erkennt, dass das ein Funktionsaufruf ist:

sub funktion { ... }

funktion argumente;

funktion;

Warum kein & ?    

Das & hat eine besondere Bedeutung, besonders wenn man auf die Parameterklammern verzichtet:

&funktion;

Hier passiert nämlich etwas ungewohntes: Die Funktionsargumente in @_ werden _nicht_ lokalisiert, d.h. funktion() erhält die Argumente der übergeorneten Funktion:

sub foo {
    &bar; #keine Parameter? Denkste!
}
sub bar {
    print "@_";
}

foo qw/a b c/;

Verwendet man das & _und_ Klammern, bewirkt man auch ein spezielles Verhalten: man schaltet die Funktionsprototypenprüfung ab. Prototypen sind eine umstrittene Geschichte, sind sie aber für eine Funktion definiert, kann das & dazu führen, falsche Parameterübergaben nicht zu bemerken:

sub foo ($$) { # erwarte 2 skalare Werte
    my ($x,$y) = @_;
    # ...
}

foo( 1 );  #compile-time Fehler "not enough arguments"
&foo( 1 ); #kein Fehler, aber Funktion erhält zu wenig Argumente

Wann doch & ?    

Einmal zum Erschaffen von Referenzen auf benannte Funktionen

sub foo {  ... }

my $x = \ foo;  #Referenz auf Rückgabewert von foo()
my $y = \ &foo; #Referenz auf foo()

Und schliesslich eine weiter Ausnahme: goto. SoftwareEntwickler wissen, dass goto so gut wie immer eine schlechte Idee ist. In Perl gibt es eine Ausnahme:

sub foo {
    goto &bar;
    return 2;
}
sub bar {
    print "@_\n";
    return 1;
}

my $x = foo qw/a b c/;
print "$x\n";

  Ausgabe:
a b c
1

Schliesslich _kann_ das & zum Dereferenzieren von Funktionsreferenzen dienen, dabei gelten allerdings die gleichen Warnungen wie bei normalen Funktionsaufrufen.

---

idiomatische Verwendung von local und speziellen Variablen    

Das Schlüsselwort local ist eigentlich eine Geschichte für sich. Es bewirkt eine Lokalisierung einer globalen Variablen, es erschafft keineswegs eine neue Variable, sondern lediglich einen neuen Speicherplatz. Zur Lebenszeit der Funktion, die eine Variable local()isiert hat, hat diese globale Variable einen anderen Wert und wird nach Beendigung der Funktion wieder zurückgesetzt. In Perl6 wird dieses Schlüsselwort zur Vermeidung von Verwirrung zu 'temp' umbenannt.

use v5.6.0; #ab perl version 5.6 existiert 'our'

our $foo = 1; #globale Variable

sub printfoo {
    print "$foo\n"; #gibt globale Variable aus
}
sub localize_foo {
    local $foo = 2;
    printfoo()
}

printfoo();
localize_foo();
printfoo();

#AUSGABE:
1
2
1

Mit Hilfe von local kann man also globale Variablen für eine Zeit so belegen, wie man will und kann sich sicher sein, dass man ihren Wert nicht auf Dauer zerstört. In Kombination mit Perls vordefinierten superglobalen Variablen ergeben sich viele Konstrukte, die man immer wieder findet.

use v5.6.0; # dreiargumentiges 'open' ab perl version 5.6
sub lies_datei {
    my $filename = shift;

    # übrigens auch idiomatisch: open .. or die .. $!;
    open my $fh , '<' , $filename or die "$filename: $!";

    local $/; #hier passiert's
    my $content = <$fh>;

    close $fh;

    $content
}

Im obigen Beispiel wird $/ (${INPUT RECORD SEPERATOR}?) lokalisiert und auf undef gesetzt (weil nicht initialisert). Der <> Operator liest nun ein Record aus der Datei und das bis zum EOF, weil $/ ja undef ist. Nachdem die Funktion sich beendet hat, ist $/ wieder auf seinem Ursprungswert, die Umgebung wird nicht behindert.

Zum "Aufsaugen" einer Datei hat sich folgendes Idiom breitgemacht:

open my $filehandle, ... or die ...;

my $content = do {    #ein neuer Block
            local $/; #lokalisiert bis zum Ende von do {}
            <$filehandle>
};

close $filehandle;

Man kann das auch ungehindert einsetzen und es ist _wesentlich_ effektiver als folgende, zwar lesbare, aber schrecklich aufwendig Alternative, die den Aufbau einer internen Liste forciert.

my $content = join '' , <$filehandle>;

Um es auf die Spitze zu treiben kann man in begrenzt wichtigen Quelltexten folgendes verwenden und sich sogar das eigenständige open() und close() sparen:

my $content = do { local (@ARGV,$/) = $filename; <> };

Denn hier übernimmt Perl das Öffnen der Datei und auch die Fehlermeldung setzt es korrekt ab. Zurecht sollte soetwas vermieden werden, wenn Fehler beim Öffnen anders als durch direktes warn()en nach STDERR abgefangen werden sollen. Ausserdem riskiert man Unverständnis bei denen, denen die viele Magie dieser Zeile nicht bekannt ist.

Hier fliessen nämlich 3 PerlIdioms ein: local, das habe ich erklärt. Der Ausdruck "<>" ist gleichbedeutend mit "< ARGV >". Das magische Dateihandle *ARGV bewirkt, dass alle Elemente in @ARGV als Dateinamen betrachtet werden. Wird von *ARGV gelesen, wird also die erste Datei geöffnet (im Fehlerfall eine Warnung ausgegeben) und an *ARGV geliefert. Ist diese Datei bis zum Ende gelesen, wird die nächste geöffnet, usw..

Als sei das nicht genug ist natürlich zu beachten, dass die Zuweisung der Liste (@array,$rest) = totalegal _immer_ dazu führt, dass $rest undefiniert ist und alle zugewiesenen Elemente nach @array gehen.


Schwartzsche Transformation    

Ein unglaublich wichtiges PerlIdiom ist die Schwartzsche Transformation. Sie ist auch ein Idiom, das _nur_ positiv zu bewerten ist. Benannt ist die Geschichte nach Randal L. Schwartz, dem Erfinder.

Problem: Ich sortiere eine Liste von "Dingen" nach einem bestimmten Kriterium, und zwar ist dieses Kriterium eine Funktion des "Dings", die einigermassen aufwendig ist. Beispielsweise das Sortieren von Dateinamen nach dem Alter der Datei.

Der erste Ansatz ist:

my @sortiert =
    sort { -M $a <=> -M $b }
    @unsortiert

Das funktioniert prima, ist nur unerträglich ineffektiv, da die Feststellung des Alters für eine Datei viele Male durchgeführt wird (perls sort() verwendet QuickSort und neuerdings auch MergeSort).

Lösung: Man berechnet das Alter der Datei nur einmal und sortiert dann. Aus der eindimensionalen Ausgangsliste wird eine 2-Dimensionale, bei der jedes Element nicht nur den Dateinamen, sondern auch das Dateialter enthält. Nach der Sortierung werden die Dateialter wieder weggeworfen, die Liste wird wieder eindimensional.

Umgesetzt wird das ganze _ohne_ temporäre Arrays, sondern nur mit perl-internen Listen und dem PowerTool? map.

In Gegenteil, es werden sogar eine Menge Arrays erzeugt, nämlich einer für jeden Eintrag.
map { [ $_ , -M ] } erzeugt für jeden Eintrag einen anonymen, zwei-elementigen Array, keine Liste.

@sortiert =
    map  { $_->[0] }
    sort { $a->[1] <=> $b->[1] }
    map  { [ $_ , -M ] }
    @unsortiert;

Gelesen werden muss dieser Code von hinten nach vorne: Das unterste map macht aus jedem Element der Ausgangsliste eine anonyme Arrayreferenz, die das Originalelement und das Dateialter enthält. Der sort-Block sortiert nun anhand des zweiten Elementes jeder Arrayreferenz. Das obere Map extrahiert nun aus jedem Array wieder das erste, das Ausgangselement.

Dieses PerlIdiom sollte man wirklich kennen, weil man unter Garantie darauf stoßen wird, wenn man sich mit Perl beschäftigt.


Arbeit mit Dateien    

Da Perl bekanntermaßen die Sprache zur Textmanipulation ist, ist auch die Arbeit mit Dateien eine zentrale Angelegenheit, die kaum in wenigen Worten zu erklären ist.

Um WiederkehrendeBedürfnisse zu befriedigen, hier kurz das gängige Muster. (Achtung: open() mit 3 Parametern ist erst ab perl 5.6.1 möglich. Aktuell ist 5.8.0, bei dem open() weitere, geniale, Tricks hat)

open my $filehandle , '<' , $filename
     or die "Fehler ... : $!";

# zeilenweises lesen
while( defined( $_ = <$filehandle> ) ){
    chomp;
    # Zeileninhalt (ohne Newline) in $_
    # ...
}

close $filehandle;

Einen kurzen Abriss enthält `perldoc -f open`. Kennen sollte man natürlich die besonderheiten von close(), sowie seek(), tell() und read(), die alle sehr intuitiv nutzbar sind.

Zur Vermeidung von gleichzeitigen Zugriffen sei flock() erwähnt, Doku auch dazu in perlfunc.


Modularisierung    

Es gilt: TIMTOWTDI. Perl ist mitunter so beliebt, weil es so modular ist. Module sind eigenständige Dateien, die ihren eigenen Namensraum (aka Package) definieren. In diesem Package definieren sie Funktionen bzw. Methoden. Ein Modul kann sowohl eine Klasse als auch eine einfache Bibliothek sein. CGI.pm, das wichtigste Modul zur CgiProgrammierungMitPerl, kann sowohl Funktions- als auch Objektorientiert verwendet werden.

Eine einfache Bibliothek (typischerweise in "Foo.pm"):

package Foo;
use strict;
use warnings;

sub bar {
    return 'Foo::bar'
}

1;

Verwendung desselben Moduls:

use Foo;
print Foo::bar();

Moechte man bestimmte Attribute des Moduls exportieren, ohne immer bei dessen Verwendung den vollstaendigen Modulnamen voranstellen zu muessen:

package Baz;

use strict;
require Exporter;

our @ISA = qw(Exporter);     # Basis-'modul'
our @EXPORT = qw(camel);     # Symbole, die beim laden des Moduls automatisch exportiert werden
our @EXPORT_OK = qw($foo);   # Symbole, die nur auf anfrage exportiert werden
our $VERSION = 1.00;         # Versions Nummer des Moduls

sub camel {
    print "I'm a camel\n";
}

our $foo = 42;
1;

Verwendung des Moduls:

use Baz qw(camel $foo);

camel();

print "The scalar $Baz::foo is $foo\n";

Eine Klasse:

package Foo;
use strict;
use warnings;

sub new {
    my $class = shift;
    bless {
        bar => shift,
        buz => shift,
   } , $class
}

sub bar {
    my $self = shift;
    return $self->{bar}
}

1;

Und Verwendung:

use Foo;
my $foo = Foo->new( 'BAR' , 'BUZ' );

print $foo->bar

Näheres in `perldoc perlobj` (u.A.). Vgl. OopInPerl.


Benchmarks (KategorieZeit)    

Dafür hat Perl ein parktisches StandardModul?, genannt: Benchmark, wie sonst?

use Benchmark qw(cmpthese);

cmpthese( 1_000_000 , {
    'Funktion 1' => sub { mach_was; },
    'Funktion 2' => sub { mach_was_anderes; },
} );

Dieses kleine Stückchen Code wird die beiden anonymen Routinen jeweils 1 Million mal ausführen und sehr exakt die Laufzeiten berechnen und danach tabellarisch bewerten.

Frage: Heißt diese Routine wirklich "cmpthese". In meiner Version von Benchmark.pm gibt es nur eine analoge Funktion "timethese"?

Jep, cmpthese. Welche perl- bzw. Benchmark Version ist das denn? In meiner manpage steht, dass 'cmpthese' seit September 1999 zum Modul gehört.

Unter diesen:
FreeBSD 4.4-RELEASE #7: v5.6.1 built for i386-freebsd (Copyright 1987-2001)
Win98: v.5.6.0 built for MSWin32-x86-multi-thread (Built Oct 31 2000)

Hmm, das "dürfte eigentlich nicht sein". Update würde ich vorschlagen...

timethese hat ledeglich eine andere Ausgabe als cmpthese, aber die selbe Funktionsweise.
Allerdings muss cmpthese() extra importiert werden, während timethese automatisch zur Verfügung gestellt wird.

KategorieResourcen KategoriePerl
StartSeite | Neues | TestSeite | ForumSeite | Teilnehmer | Kategorien | Index | Hilfe | Einstellungen | Ändern
Text dieser Seite ändern (zuletzt geändert: 29. August 2003 8:26 (diff))
Suchbegriff: gesucht wird
im Titel
im Text