Beschwörung

Wenn Softwarepakete einen größeren Versions-sprung durchmachen, stellen deren Anwender und andere immer die Frage nach neuen und verbesserten Eigenschaften. Das ist bei der Scriptsprache Python, die jetzt als Version 2.0 vorliegt, nicht anders.

vorlesen Druckansicht 10 Kommentare lesen
Lesezeit: 17 Min.
Von
  • Rainer Fischbach
Inhaltsverzeichnis

Python gehört zu den Sprachen, deren Popularität in den letzten Jahren gestiegen ist. Trotzdem liegt diese immer noch weit unter der von vergleichbaren Sprachen wie Perl und Java; wobei schon die Unterschiedlichkeit der Vergleichsobjekte darauf hindeutet, dass Python nicht ganz in Schubladen wie ‘Scriptsprachen’ oder ‘OO-System-Programmiersprachen’ passt. Mit der jetzt vorliegenden 2.0-Release enthält Python wichtige Erweiterungen, von denen die Unicode-Integration die bedeutendste ist.

Grundsätzliches zu Python findet sich in dem früheren iX-Artikel [4] sowie in weiterer Literatur (zum Beispiel [3, 5]). Hier seien nur knapp die Charakteristika zusammengefasst, die Python zu einer außergewöhnlichen und näherer Betrachtung werten Sprache machen, die im Vergleich mit den populäreren Konkurrenten gut abschneidet:

  • einfache und übersichtliche Syntax mit ‘Abseitsregel’; das heißt, wie in Haskell [#literatur [6]] oder Miranda entscheidet die Einrückungstiefe über die Zugehörigkeit von Programmzeilen zu strukturierten Anweisungen (Funktionsdefinition, Schleife et cetera),
  • wenige mächtige und kombinierbare Konstrukte,
  • flexible höhere Datentypen wie Strings, Tupel, Listen und Dictionaries mitsamt den dazugehörenden Literalen (Notationen für die Werte dieser Typen),
  • dynamisches Objektmodell mit Reflexion; alle Objekte sind zur Laufzeit inspizierbar, Klassen zur Laufzeit erweiterbar,
  • High-order-Programming: auch Klassen und Funktionen beziehungsweise Methoden sind Objekte,
  • Interpretation und dynamische Code-Erzeugung; der Compiler ist eine aufrufbare Funktion,
  • mächtige Module für reguläre Ausdrücke, Netz-Protokolle, den Zugriff auf Betriebssystem-Funktionen et cetera.

Es gilt als Indiz für größere Änderungen beziehungsweise Erweiterungen, wenn die erste Ziffer in einer Versionsbezeichnung wechselt. Nach dem lange währenden Python 1.52 und einer nur flüchtig in Erscheinung getretenen Version 1.6 lässt 2.0 also einiges erwarten.

Die Liste der neuen Merkmale [1] räumt jedenfalls die Furcht aus, der Anschein könnte hier trügen, und berechtigt dazu, nach etwas mehr als einem Jahr diese Sprache wieder zum Thema zu machen; wobei es hier hauptsächlich um die mit der neuen Version erzielten oder immer noch ausstehenden Fortschritte gehen soll. Die wichtigen Neuerungen in Python 2.0 umfassen:

  • Unicode-Unterstützung,
  • String-Methoden,
  • neue Listenkonstruktoren,
  • erweiterte Import-Syntax,
  • erweiterte Zuweisungsoperatoren im Stil von C,
  • neue Module: webBrowser, pyexpat, mmap,
  • andere zahllose Fehlerkorrekturen und
  • eine verbesserte Version (0.6) der Programmierumgebung Idle.

Man kann natürlich auch fragen, was es immer noch nicht gibt. Enttäuscht wurden alle, die etwa auf die folgenden Neuerungen gewartet hatten:

  • ‘Lexical scope’, die dem Programmtext entsprechende Verschachtelung der Gültigkeitsbereiche,
  • vollwertige Lambda-Ausdrücke für anonyme Funktionen,
  • UCS-4/ISO 10646: die volle Unterstützung des aktuellen Standes der internationalen Normung auf dem Gebiet der Zeichensätze.

Auch diese Nicht-Innovationen haben Konsequenzen (siehe unten).

Unter den Neuerungen sticht die Unicode-Unterstützung sicherlich am meisten hervor. Damit steigt die Attraktivität des Python-Einsatzes in allen Projekten, in denen es um die Verarbeitung von zeitgemäßen Textformaten wie XML geht. Bisher war dies ein Aufgabenfeld, für das Java beinahe exklusiv qualifiziert zu sein schien.

Die Implementierung des Unicode-Zeichensatzes folgt einem Entwurf von Marc-Andre Lemburg [2]. Dessen wesentliche Elemente bestehen in einem zusätzlichen String-Typ und in einem Satz von Konversionsroutinen. Anders als in Java - dort liegt den Charakter- und String-Typen grundsätzlich der 16-Bit-Unicode zu Grunde - hat man in Python 2.0 den Weg der inkrementellen Verbesserung gewählt: Die bisherige 8-Bit-Implementierung bleibt neben der neuen bestehen; was diejenigen, die alten Python-Code zu pflegen haben, einiger Kopfschmerzen enthebt. Allerdings behalten sich die Python-Macher vor, jene in einer späteren Version völlig wegfallen zu lassen. Wer sich darauf vorbereiten möchte, kann probeweise Python mit der -u-Option starten, bei deren Anwendung das System keine 8-Bit-Strings mehr kennt.

Da es in Python keinen eigenen Character-Datentyp gibt, beschränken sich die Änderungen auf den String-Datentyp. Ein vorangestelltes ‘u’ unterscheidet Unicode- von herkömmlichen String-Literalen. Zur Repräsentation der auf heutiger Hardware nicht darstellbaren Werte stehen Fluchtsequenzen zur Verfügung, die mit ‘\u’ beginnen und in einer vierstelligen Hexadezimalzahl enden. Die Anweisung

s = u'abc\u01ff'

bindet die Variable s an den Unicode-String der Länge 4, dessen letztes Element die Ordnungszahl 511 hat. Der Definitionsbereich der Standardfunktion ord, die die Ordnungszahl des einzigen Elementes eines Strings der Länge 1 liefert, umfasst jetzt auch Unicode-Strings.

ord (s[-1])

liefert also 511. In Python adressieren negative Indizes eine Sequenz von hinten. s[-1] bedeutet also soviel wie s[len(s)-1]; wobei die Standardfunktion len die Länge einer Sequenz liefert. Auch Tupel und Listen gehören zur Familie der Sequenzen, die alle eine Reihe von gemeinsamen Operationen akzeptieren. Wie 8-Bit-Strings sind auch Unicode-Strings immutable Sequenzen; das heißt, dass die Unicode-Objekte nicht veränderlich sind. Es ist also nicht möglich, ein solches Objekt zu verkürzen, zu verlängern oder seine Elemente auszutauschen; was im Gegensatz dazu bei Listen geht, da diese mutabel sind.

Neben die Standardfunktion chr, die zu einer Ordnungszahl (sofern diese zwischen 0 und 255 liegt) den entsprechenden String der Länge 1 liefert, tritt nun unichr, die zu einer Ordnungszahl zwischen 0 und 65 535 den passenden Unicode-String liefert.

Treten Unicode- und herkömmliche Strings in einem Ausdruck gemischt auf, erfolgt immer eine Typausweitung. Der Ausdruck s + 'ubu' liefert den Unicodestring u'abc\u01FFubu' der Länge 7. Unicode- und 8-Bit-Strings gleichen Inhalts sind gleich bezüglich des ‘==’-Operators und liefern auch identische Hashcodes.

u'abc' == 'abc'

liefert demnach den Wert 1, und beide Objekte fungieren als identische Schlüssel in Dictionaries. Diese assoziieren beliebige Werte mit Schlüsseln, die immutable Objekte sein müssen. In Python-Implementierungen verbergen sich hinter Dictionaries Hashtabellen.

Eine Methode encode wandelt Unicode-Strings gemäß dem in einem optionalen Parameter spezifizierten Verfahren in entsprechende 8-Bit-Folgen um. Das Default-Verfahren ist ‘ASCII’; was natürlich schon bei den deutschen Umlauten und Ligaturen zu einem Fehler führt.

u'ß'.encode ('iso-8859-1')

liefert dagegen einen 8-Bit-String, der das Element mit der Ordnungszahl 223 enthält.

(u'abc\u01FF' + 'ubu').encode ('utf-8')

gibt die 8-Bit-Folge abc\307\277ubu zurück; wobei ein ‘\’ gefolgt von drei Ziffern eine oktale Ordnungszahl repräsentiert. Die in der ISO 10646 definierte UTF-8-Kodierung ersetzt Zeichen mit Ordnungszahlen über 127 durch Folgen von 8-Bit-Zeichen. Die Standardfunktion unicode wandelt 8-Bit-Folgen, die Unicode-Strings gemäß einer Kodierung repräsentieren wieder in solche zurück. Der optionale zweite Parameter spezifiziert dabei die Kodierung.

Das Modul codecs bietet eine Schnittstelle, um Kodierungs- beziehungsweise Dekodierungsfunktionen zu registrieren und abzufragen. Zu einer durch einen String-Parameter spezifizierten Kodierung ist dabei ein 4er-Tupel von Funktionen abzulegen/abzufragen. Die ersten beiden sind die Kodierungs- und die Dekodierungsfunktion, die zwischen Unicode- in 8-Bit-Code-Strings konvertieren, während die beiden letzten Filterobjekte liefern, durch die sich Unicode-Strings mit dem betreffenden Verfahren, (de)kodiert durch die Methoden write/read, in Dateien schreiben beziehungsweise aus Dateien lesen lassen. Das geöffnete Dateiobjekt ist dazu als Parameter zu übergeben.

Bislang ist die Zahl der Kodierungs/Dekodierungsverfahren, die eine Implementierung von Python 2.0 bieten muss, beschränkt, deckt jedoch die wichtigsten Anwendungsfälle ab. Verfügbar sind neben den beiden in ISO 10646 definierten Codes UTF-8 und UTF-16, die auch jeder XML-Prozessor beherrschen muss, der für HTML obligatorische erweiterte ASCII-Code nach ISO 8859-1 sowie die schon erwähnten hexadezimalen Fluchtsequenzen. Java bietet dagegen zahllose weitere Codes an, die auf speziellen Plattformen oder in anderen Ländern üblich sind. Was sowohl Python als auch Java fehlt, ist die unmittelbare Unterstützung von UCS-4, des in ISO 10646 definierten 32-Bit-Codes, der es erlauben würde, sämtliche chinesischen Schriftzeichen zu implementieren. Unicode entspricht nur dem ‘kleinen’ UCS-2-Standard der ISO.

Höchst unvollständig wäre die Unicode-Unterstützung, wenn reguläre Ausdrücke sich nicht auch mit Unicode-Strings bilden ließen oder wenn sie nicht mit solchen abgleichbar wären. Ohne jenes wichtige Werkzeug zur Analyse und Manipulation von Texten kommt kaum ein signifikantes Programm aus. Das ‘re’-Modul hat deshalb eine völlige Neuimplementierung erfahren, die auch Unicode-Strings akzeptiert, jedoch leider noch einen hässlichen Bug aufweist: Die ‘nicht-gierigen’ Operatoren funktionieren nicht (während das mit dem ‘gierigen’ Operator gebildete Muster a* sich mit der längstmöglichen Folge von a’s deckt, tut es das mit dem ‘nicht gierigen’ gebildete a*? mit der kürzestmöglichen). Glücklicherweise gibt es das alte ‘re’-Modul noch unter dem Namen ‘pre’. Wer Python-Code, der von diesen Operatoren Gebrauch macht, am Leben halten muss, kann auf ‘pre’ zurückgreifen anstatt auf den nächsten Patch zu warten.

Bisher stellte das String-Modul die elementaren Operationen zum Umgang mit Zeichenketten zur Verfügung, etwa zum Zerlegen und Verketten oder zum Suchen und Ersetzen von Teilstrings. Das tut es zwar auch weiterhin, doch stellt es nicht mehr den bevorzugten Zugang dazu dar. Stattdessen besitzen String-Objekte jetzt entsprechende Methoden. An die Stelle von

string.split ('abc-def-uvw', '-')

(das Ergebnis ist die Liste [‘abc’, ‘def’, ‘uvw’]) tritt jetzt

'abc-def-uvw'.split ('-')

und statt

string.replace ('abc-def-uvw', '-', '/')

funktioniert jetzt auch

'abc-def-uvw'.replace ('-', '/')

Da Strings weiterhin immutable Objekte bleiben, handelt es sich bei den zu ihnen gehörenden Methoden um Funktionen, die keine Seiteneffekte ausüben, sondern ein Ergebnis liefern.

Ähnlich wie die funktionalen Sprachen Scheme, ML et cetera bietet Python Funktoren und Faltungsfunktionen zur Manipulation von Listen. map, filter und reduce wenden ihr Funktionsargument auf jedes Listenelement an beziehungsweise filtern oder verknüpfen die Listenelemente unter Anwendung des Funktionsarguments. Zur Konstruktion passender Funktionen an Ort und Stelle steht wie in den funktionalen Sprachen die Lambda-Definition zur Verfügung, in deren Körper jedoch nur Ausdrücke stehen dürfen; was eine Auswahl ausschließt, da diese in Python eine Anweisung ist. Die anonyme Funktion

lambda x: x < 100

testet, ob ihr Argument kleiner als 100 ist, und ihre Anwendung auf eine Liste von Zahlen mittels filter

filter (lambda x: x < 100, [111, 200, 33, 669, 69, 27, 999])

liefert die Auswahlliste [33, 69, 27] der Zahlen < 100 aus der Argumentliste. Die Anwendung

map (lambda x: x < 100, [111, 200, 33, 669, 69, 27, 999])

liefert dagegen die Liste der Vergleichsresultate; hier also [0, 0, 1, 0, 1, 1, 0]. So weit, so gut. Schwierig wird es, wenn der Vergleichswert umgebungsabhängig - etwa durch einen Parameter k - gegeben sein soll; der Ansatz

def filterk (k, ls):
return filter (lambda x: x < k, ls)
filterk (150, [111, 200, 33, 669, 69, 27, 999])

funktioniert nicht (es sei denn, k gehört zu den Bindungen des globalen Gültigkeitsbereichs, was hier so nicht beabsichtigt ist), da nach den Regeln von Python die Bindungen des textuell umgebenden Gültigkeitsbereichs (hier etwa der Funktionsdefinition, in der der Ausdruck steht) in denen der Lambda-Funktion nicht enthalten sind. Python kennt k innerhalb der Letzteren nicht, da es anders als die klassischen funktionalen Sprachen keinen ‘lexical scope’ kennt; was Liebhaber des entsprechenden Programmierstils zum Wahnsinn treibt. Der Umweg über ein im Grunde sinnloses optionales Argument mit einem immer benutzten Default-Wert ist umständlich und schwer verständlich.

def filterk (k, ls):
return filter (lambda x, kw = k: x < kw, ls)

hingegen funktioniert tatsächlich und ist bei diesem einfachen Beispiel auch noch nachvollziehbar. Die Listing 1 zu entnehmende Funktion, die alle Paare von Indizes rechts oberhalb der Hauptdiagonalen einer quadratischen Matrix der Dimension dim in einer Liste liefert, zeigt jedoch, dass dieser Trick schnell zu kaum noch durchschaubarem Code führt.

Mehr Infos

Listing 1: Paare von Indizes

from operator import concat

def lambdadreieckro (dim):
return reduce (concat,
map (lambda n, di = dim:
map (lambda m, z = n: (z, m), range (n+1, di)),
range (dim))

Nach der Abseitsregel ist es wieder ein von Haskell [#literatur [6]] entlehntes Konzept, das hier Übersicht schafft: die ‘list comprehensions’. Diese Listen-Zusammenfassungs-Konstruktoren, wie man sie in etwas bürokratischem Deutsch nennen könnte, vermögen map, filter und reduce weitgehend sowie viele Fälle von lambda zu ersetzen. Sie beseitigen dabei auch den Ärger, der aus dem fehlenden ‘lexical scope’ resultiert, da sie keine neuen Gültigkeitsbereiche schaffen [Anmerkung 16. 2. 2001: Eine Alpha-Version Python 2.1 ist mittlerweile freigegeben, die den Ärger mildern dürfte.]. Die Notation ist in Python nicht so knapp wie in Haskell, lehnt sich dafür jedoch stärker an den Mainstream an. Das Schema lautet

[<exp> for <v1> in <l1> ... for <vN in lN> if <cond>]

und liefert die Liste aller die Bedingung cond erfüllenden Werte, die der Ausdruck exp annimmt, wenn die Variablen v1 ... vN alle sich aus dem jeweiligen Durchlaufen der Listen l1 ... lN ergebenden Kombinationen von Werten annehmen. Ohne Trick ergibt sich so der verallgemeinerte Listenfilter

def filtk (k, ls):
return [n for n in ls if n < k]

und auch die Liste der Vergleichsergebnisse:

def mapk (k, ls):
return [n < k for n in ls]

Selbst die Indices der Dreiecksmatrix sind nun einfach zu berechnen (siehe Listing 2). Die neuen Listenkonstruktoren vereinfachen also viele Aufgaben und mildern den Ärger über ein voraussichtlich erst in der 2.1 verschwindendes konzeptionelles Defizit von Python, wenn sie auch das Fehlen vollwertiger Lambda-Definitionen und der Schachtelung von Gültigkeitsbereichen nicht völlig zu kompensieren vermögen.

Mehr Infos

Listing 2: Dreiecksmatrix-Indizes

def compdreieckro (dim):
return [(i, j) for i in range (dim) for j in range (i + 1, dim)

Beim Programmieren das Rad nicht täglich neu zu erfinden, sondern vorhandene Bausteine auch anderer Autoren zu nutzen, ist eine verführerische Idee. Doch da man meist weder seine eigene frühere noch die Namensgebungspraxis anderer besonders gut unter Kontrolle hat, sind Namenskollisionen nahezu unvermeidlich. Für dieses an sich triviale Problem gibt es in der Welt der Programmiersprachen bisher nur wenige einfache Lösungen. Bertrand Meyer hat bei Eiffel vorgemacht, wie es geht [#literatur [7]], und in Python 2.0 findet sich eine ähnliche Lösung: Umbenennung beim Import. Dazu gibt es zwei Schemata:

import <module> as <alternative>
from <module> import <member> as <alternative>

Die Import-Anweisung aus Listing 1 ließe sich also abwandeln, sofern der Name concat bereits an anderer Stelle vergeben wäre.

from operator import concat as op_concat

stellt die Function concat unter dem Namen op_concat bereit. Die Namenskollision wäre natürlich auch durch den qualifizierten Import vermeidbar, doch kann dies unter Performance-Gesichtspunkten kontraindiziert sein. operator.concat erfordert einen zusätzlichen Table-Lookup.

C-Fans kommen mit Version 2.0 auf ihre Kosten: Python stellt jetzt die von C bekannten erweiterten Zuweisungsoperatoren zur Verfügung. Statt x = x + d kann man nun auch x += d schreiben und spart damit ein wenig Tipparbeit.

Eine Menge neue Module runden die neue Version ab. Hier eine kleine unvollständige Auswahl:

  • webBrowser gestattet den Aufruf von Browsern aus einem Python-Programm; was nützlich ist, wenn man HTML als Ausgabeformat benutzt.
  • pyexpat bietet ein Interface zu James Clarks bekannten XML-Parser Expat.
  • xml.sax bietet ein Framework für XML-Parser nach der SAX2-Spezifikation
  • mmap ermöglicht es unter Unix und NT, Dateien unmittelbar in den Arbeitsspeicher abzubilden. Den vollen Zugriff auf die Mechanismen der Speicherverwaltung, den das gleichnamige System V-Modul gestattet, bietet es jedoch nicht.

Wenn manche auch noch einiges vermissen mögen und einige Bugs, wie bei einer .0-Version kaum anders zu erwarten, das Leben erschweren, stellt Python 2.0 doch einen großen Schritt vorwärts dar und nährt die Hoffnung, dass die Akzeptanz der Sprache dadurch eine Steigerung erfährt.

Rainer Fischbach
ist Senior Consultant bei der Engineering Consulting & Solutions GmbH in Neumarkt.

[1] A. M. Kuchling, Moshe Zadka; What’s New in Python 2.0

[2] Marc-Andre Lemburg: Python Unicode Integration

[3] Martin von Löwis, Nils Fischbeck: Python 2; München (Addison-Wesley) 2000

[4] Rainer Fischbach; Scriptsprachen; Beschränkung aufs Wesentliche; Wie Python arbeitet

[5] David M. Beazley: Python Essential Reference. Indianopolis, In: New Riders, 2000

[6] Paul Hudak, Joseph H. Fasel: ‘A Gentle Introduction to Haskell’ ACM SIGPLAN Notes, Mai 1992, S. 22

[7] Bertrand Meyer: Object-Oriented Software Construction. 2. Auflage, Upper Saddle River, NJ: Prentice Hall, 1997

Mehr Infos

iX-TRACT

  • Version 2.0 der Scriptsprache Python bietet viele Erweiterungen - und einige Enttäuschungen über nicht enthaltene.
  • Durch die Unterstützung von Unicode (UTF-8 und -16) reiht sich Python in die Sprachen ein, die künftig XML-Formate gut bearbeiten können.
  • Weitere neue Eigenschaften betreffen die String-Methoden sowie neu hinzugekommene Module.

(hb)