Aufmacher

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.

LinuxCommunity kaufen

Einzelne Ausgabe
 
Abonnements
 

Ähnliche Artikel

  • Fehlerbehandlung und objektorientierte Programmierung in Python
    Mit Skriptsprache Python entfaltet erst dann ihre ganze Kraft, wenn objektorientierte Konzepte zum Einsatz kommen. Sie hilft Ihnen, komplexe Probleme in überschaubre Einheiten zu zerlegen.
  • 3D-Welten mit Python und Panda3D
    Es gibt zwar einige freie Spiele-Engines, doch nicht jede ist leicht zu programmieren. Panda3D vereinfacht durch die Skriptsprache Python den Einstieg, bietet aber auch Potenzial für Profis.
  • Typische Fehler in Python-Code vermeiden
    Python ist einfach – aber nicht so einfach, dass man es allein durch Lesen von Code lernen könnte. Mit etwas Hintergrundwissen vermeiden Sie typische Fehler in Python-Code und schreiben so bessere Programme.
  • Python 3.1 erschienen
    Die neue Version soll stabiler und in einigen Teilen schneller laufen. Darüber hinaus wurden einige der mit Python 3.0 eingeführten Sprachelemente optimiert und erweitert. Mit den Ordered Dictionarys gibt es sogar eine neue Datenstruktur.
  • Entwicklungshelfer
    Nachdem in den ersten Teilen der Python-Einführung die Sprache beschrieben wurde, geht es im letzten Teil um nützliche Hilfen rund um Python.
Kommentare

Infos zur Publikation

LU 11/2014: VIDEOS BEARBEITEN

Digitale Ausgabe: Preis € 4,95
(inkl. 19% MwSt.)

Mit der Zeitschrift LinuxUser sind Sie als Power-User, Shell-Guru oder Administrator im kleinen Unternehmen monatlich auf dem aktuelle Stand in Sachen Linux und Open Source.

Sie sind sich nicht sicher, ob die Themen Ihnen liegen? Im Probeabo erhalten Sie drei Ausgaben zum reduzierten Preis. Einzelhefte, Abonnements sowie digitale Ausgaben erwerben Sie ganz einfach in unserem Online-Shop.

NEU: DIGITALE AUSGABEN FÜR TABLET & SMARTPHONE

HINWEIS ZU PAYPAL: Die Zahlung ist auch ohne eigenes Paypal-Konto ganz einfach per Kreditkarte oder Lastschrift möglich!       

Tipp der Woche

Schnell Multi-Boot-Medien mit MultiCD erstellen
Schnell Multi-Boot-Medien mit MultiCD erstellen
Tim Schürmann, 24.06.2014 12:40, 0 Kommentare

Wer mehrere nützliche Live-Systeme auf eine DVD brennen möchte, kommt mit den Startmedienerstellern der Distributionen nicht besonders weit: Diese ...

Aktuelle Fragen

Artikelsuche
Erwin Ruitenberg, 09.10.2014 07:51, 1 Antworten
Ich habe seit einige Jahre ein Dugisub LinuxUser. Dann weiß ich das irgendwann ein bestimmtes Art...
Windows 8 startet nur mit externer Festplatte
Anne La, 10.09.2014 17:25, 4 Antworten
Hallo Leute, also, ich bin auf folgendes Problem gestoßen: Ich habe Ubuntu 14.04 auf meiner...
Videoüberwachung mit Zoneminder
Heinz Becker, 10.08.2014 17:57, 0 Antworten
Hallo, ich habe den ZONEMINDER erfolgreich installiert. Das Bild erscheint jedoch nicht,...
internes Wlan und USB-Wlan-Srick
Gerhard Blobner, 04.08.2014 15:20, 2 Antworten
Hallo Linux-Forum: ich bin ein neuer Linux-User (ca. 25 Jahre Windows) und bin von WIN 8 auf Mint...
Server antwortet mit falschem Namen
oin notna, 21.07.2014 19:13, 1 Antworten
Hallo liebe Community, Ich habe mit Apache einen Server aufgesetzt. Soweit, so gut. Im Heimnet...