Home / LinuxUser / 2007 / 01 / Einfache Architektur

Newsletter abonnieren

Lies uns auf...

Folge LinuxCommunity auf Twitter

Top-Beiträge

Mandriva gibt Distribution in die Hände der Community
(268 Punkte bei 24 Stimmen)
Neues vom Systemd
(161 Punkte bei 4 Stimmen)
Mandriva in Nöten
(161 Punkte bei 4 Stimmen)

Heftarchiv

LinuxUser Heftarchiv

EasyLinux Heftarchiv

Ubuntu User Heftarchiv

Ubuntu User Heftarchiv

Partner-Links:

Shopping
Topsuche
 
Yatego Deutschlands größte Shoppingmall. 10000 Shops,
3.5 Mio Artikel. Alle Bestseller, Servertechnik und Technik Themenwelten.

Notebooks und Netzwerkhardware bei Mercateo günstig kaufen.
Internet Telefonie mit VoIP Telefonen von Gigaset
Das B2B Portal www.Linx.de informiert über Produkte und Dienstleistungen.
Günstige Digitalkameras finden Sie im Preisvergleich.

Einfache Architektur

Objektorientiertes Programmieren mit Python

01.01.2007 Spezialmethoden und geschicktes Vererben von Klassenmethoden vereinfachen das Programmieren mit Python drastisch. Unser Workshop zeigt, welche Tricks die Skriptsprache auf Lager hat.

Im ersten Teil der Python-Workshops (LinuxUser 09/2006) ging es um den interaktiven Interpreter, Datentypen und Ablaufstrukturen. Der zweite Teil (LinuxUser 10/2006) behandelte das Aufteilen von Code in Funktionen, Module und Pakete. Der dritte Teil (LinuxUser 11/2006) befasste sich mit Fehlern und Ausnahmen und bot einen Einstieg in das objektorientierte Programmieren, das dieser Artikel vertieft.

Im dritten Teil des Workshops hat sich durch das Bearbeiten des Textes eine sinnentstellende Aussage in Bezug auf überladene Methoden ergeben: Überladene Methoden (gleicher Name, aber unterschiedliche Parameterlisten) wie in C++ oder Java gibt es in Python nicht. Man erreicht eine ähnliche Funktionalität, indem man den auszuführenden Code nach den im Aufruf verwendeten Parameternamen auswählt. Alternativ kann man prüfen, ob ein übergebener Parameter eine bestimmte Schnittstelle unterstützt. Die direkte Nachbildung der Typunterscheidung mit der Funktion isinstance ist nicht zu empfehlen, da sie das Duck Typing aushebelt.

Arg abstrakt

Wie in einigen anderen Programmiersprachen gibt es auch in Python das Prinzip einer abstrakten Klasse. Von dieser sollte es keine Instanzen geben. Die abstrakte Basisklasse enthält nur allgemeine (abstrakte) Definitionen; erst die abgeleiteten Klassen definieren sinnvolles, verwendbares Verhalten. Listing 1 zeigt ein Beispiel.

Listing 1
import math
class Form(object):
    def flaeche(self):
        raise NotImplementedError("in abgeleiteter Klasse definieren")
class Quadrat(Form):
    def __init__(self, seitenlaenge=0.0):
        self.seitenlaenge = seitenlaenge
    def flaeche(self):
        return self.seitenlaenge * self.seitenlaenge
class Kreis(Form):
    def __init__(self, radius=0.0):
        self.radius = radius
    def flaeche(self):
        return math.pi * self.radius * self.radius
# ggf. weitere Form-Klassen
# …
def gesamtflaeche(formen):
    # "generator comprehension"
    flaechen = (form.flaeche() for form in formen)
    return sum(flaechen, 0.0)
if __name__ == '__main__':
    q = Quadrat(10)
    k = Kreis(5)
    # Ausgabe 178.53981634
    print gesamtflaeche([q, k])

Die abstrakte Klasse Form (Zeilen 3 bis 5) definiert die Methode flaeche nur als Platzhalter. Obwohl es in Python nicht zwingend notwendig ist, eine abstrakte Methode wie Form.flaeche zu notieren, dient es manchmal als Dokumentation; die Methode löst lediglich eine Ausnahme vom Typ NotImplementedError aus (Zeile 5). In den abgeleiteten Klassen Quadrat und Kreis (Zeilen 7 bis 19) sind die konkreten Arten der Flächenberechnung bekannt, und flaeche ist jeweils implementierbar.

Da alle Formen eine Methode flaeche haben, berechnet die Funktion gesamtflaeche (Zeilen 24 bis 27) auf einfache Weise die Summe der Flächen, die die Sequenz formen enthält.

Duck Typing

Beim Bearbeiten der Objekte in der Sequenz formen greift ein Prinzip namens Duck Typing. Der Ursprung dieses seltsamen Namens ist der Satz "If it looks like a duck and quacks like a duck, it must be a duck." (deutsch: Wenn es wie eine Ente aussieht und wie eine Ente quakt, muss es eine Ente sein.) Es gibt keine Typdefinitionen für die Parameter wie in C++ oder Java – also keine Vorschrift, die vorgibt, dass die Klassen der Objekte in formen von der gleichen Basisklasse abstammen müssen. Deshalb dürfen Sie Quadrat und Kreis direkt von object ableiten; die abstrakte Klasse Form ist nicht nötig.

Duck Typing ist in Python sehr gebräuchlich. Ein anderes Beispiel sind dateiartige Objekte, die bestimmte Methoden von Dateiobjekten besitzen. So dürfen Sie beispielsweise an eine Funktion, die die Methoden read, readline und readlines eines Dateiobjekts erwartet, auch ein Objekt vom Typ StringIO[1] übergeben. Das ist sinnvoll, wenn die Daten, die Sie an die Funktion durchreichen, nicht aus einem Dateisystem stammen. Listing 2 zeigt ein entsprechendes Beispiel.

Listing 2
#! /usr/bin/env python
def nummerierte_datei(dateiobjekt):
    """
    Eine Generatorfunktion, die die Zeilen aus der Datei
    file_object mit dreistelligen Zeilennummern versieht.
    """
    zeilennummer = 1
    while True:
        try:
            zeile = dateiobjekt.next()
        except StopIteration:
            raise
        yield "%03d %s" % (zeilennummer, zeile)
        zeilennummer += 1
def ausgabe_nummerierte_datei(dateiobjekt):
    for zeile in nummerierte_datei(dateiobjekt):
        print zeile,
if __name__ == '__main__':
    programmdatei = open("nummerierte_datei.py")
    ausgabe_nummerierte_datei(programmdatei)
    programmdatei.close()
    print
    # dies sei im Programm generierter Text
    text = """\
Dies ist ein mehrzeiliger
Text, der in die Funktion
ausgabe_nummerierte_datei
gesteckt wird."""
    import StringIO
    textdatei = StringIO.StringIO(text)
    ausgabe_nummerierte_datei(textdatei)
    textdatei.close()

Bei nummerierte_datei (Zeilen 3 bis 15) handelt es sich um eine Generatorfunktion, wie im zweiten Teil des Python-Kurses beschrieben. Die voranzustellenden Zeilennummern sollen mit 001 beginnen, deshalb bekommt die Variable zeilennummer in Zeile 8 den Startwert 1. Nun durchläuft das Programm bis auf Widerruf (siehe While-Bedingung) die durch dateiobjekt beschriebene Datei. Jede gelesene Zeile erzeugt in Programmzeile 11 eine neue Zeichenkette, die in Codezeile 14 eine Zeilennummer vorangestellt bekommt.

Finden sich keine weiteren Zeilen in der Datei, löst die Codezeile 11 die Ausnahme StopIteration aus, die die Zeile 13 einfach nach oben durchreicht. Dadurch bricht die For-Schleife korrekt ab, die in ausgabe_nummerierte_datei (Programmzeilen 17 bis 19) über das Ergebnis von nummerierte_datei iteriert.

Natürlich wäre es denkbar, mit einer Funktion zu arbeiten, die die zu nummerierenden Zeilen als Sequenz annimmt. Das setzt allerdings voraus, dass das Skript die ganze Datei auf einmal einliest, was bei großen Dateien problematisch wäre.

Der Testcode in den Zeilen 22 bis 37 verwendet zwei dateiartige Objekte, um die Zeilen daraus zu lesen und sie nummeriert auszugeben. In den Programmzeilen 23 bis 25 ist es eine gewöhnliche Datei, nämlich das Programm selbst. Die Zeilen 28 bis 37 erzeugen dagegen ein dateiartiges Objekt direkt aus einer Zeichenkette. Dieses StringIO-Objekt besitzt, wie auch ein echtes Dateiobjekt, eine Methode next, die in Zeile 11 zum Einsatz kommt. Listing 3 zeigt die Programmausgabe, wobei die Ausgabezeilen 6 bis 32 fehlen.

Listing 3
001 #! /usr/bin/env python
002
003 def nummerierte_datei(dateiobjekt):
004     """
005     Eine Generatorfunktion, die die Zeilen aus der Datei
.
.
.
033 wird."""
034     import StringIO
035     textdatei = StringIO.StringIO(text)
036     ausgabe_nummerierte_datei(textdatei)
037     textdatei.close()
001 Dies ist ein mehrzeiliger
002 Text, der in die Funktion
003 ausgabe_nummerierte_datei
004 gesteckt wird."""

Dieser Code setzt nicht voraus, dass sich Pythons Dateiklasse und StringIO von der gleichen Basisklasse ableiten. Es reicht, eine bestimmte Schnittstelle zu definieren (hier die Methode next) und eine Ausnahme StopIteration am Ende der Datei zu erzeugen.

Einem Freund empfehlen    Druckansicht Bookmark and Share
Kommentare

1566 Hits
Wertung: 165 Punkte (8 Stimmen)

Schlecht Gut

Infos zur Publikation

Infos zur Publikation

LinuxUser 06/2012

Aktuelle Ausgabe kaufen:

Heft bestellen Heft als PDF kaufen

LinuxUser erscheint monatlich und kostet in der Nomedia-Ausgabe EUR 5,50 und mit DVD EUR 8,50. Weitere Informationen zum Heft finden Sie auf der LinuxUser-Homepage.

Im LinuxUser-Probeabo erhalten Sie drei Ausgaben für 3 Euro. Das Jahresabo (ab EUR 56,10) können Sie im LNM-Shop bestellen.

Tipp der Woche

Adobe AIR
Adobe-AIR-Programme installieren und (manuell) starten
Tim Schürmann, 14.05.2012 13:09, 0 Kommentare

Es gibt sie noch: neue Anwendungen, die Adobes Integrated Runtime voraussetzen. Aktuellstes und vermutlich auch größtes Beispiel ist das Adventure Botanicula

Aktuelle Fragen

gibt es ein Kommandozeilen Tool, um ein X11-Fenster in ein Anderes einzubetten?
GoaSkin , 21.05.2012 16:44, 0 Antworten
Das XEmbed-Protokoll ist u.A. dazu gedacht, dass man eine X11-Anwendung in eine andere wie ein Wi...
Apache2, Options -Indexes geht nicht
no no, 12.05.2012 19:01, 8 Antworten
Habe in apache2.conf folgendes stehen: Options -Indexes ...
LInux auf Dell LS H500
Andreas Endresl, 09.05.2012 08:54, 2 Antworten
Habe einen alten Dell Latitude LS H500 nur mit ext. Floppy und CD es geht nur immer eines von den...
Datenwiederherstellung unter Ubuntu 12.04 mit "Simple Backup" nach Umzug von Linux Mint
Christian Lottmann, 07.05.2012 13:33, 0 Antworten
Vor dem Umzug auf Ubuntu 12.04 habe ich unter Linux MInt mit "Simple Backup" voll (15.4.2012) und...
DKMS für den propritären NVIDIA-Treiber
Commander Data, 26.04.2012 22:02, 2 Antworten
Hallo an die Gemeinde. Ich habe hier ein interessantes Stück openSuSE gefunden. http://forums.op...