Funktionen und Methoden

Manche Sprachen erlauben, die Klammern am Ende eines Funktionsaufrufs wegzulassen, wenn man keine Argumente übergibt. In Python sind die Klammern jedoch Pflicht. Der Ausdruck fobj.close in Listing 9 liefert nur die Methode selbst, ruft diese aber nicht auf. Die Datei bleibt daher offen.

Listing 9

fobj = open(dateiname, 'rb')
# Lies die ersten 100 Bytes.
data = fobj.read(100)
# Keine Klammern nach `close` -> kein Aufruf.
fobj.close

Default-Argumente werden nur einmal erzeugt, nämlich während der Definition der Funktion oder Methode. Deshalb bleibt das Listen-Objekt L in Listing 10 während der gesamten Laufzeit des Codes erhalten.

Listing 10

def anhaengen(obj, L=[]):
    L.append(obj)
    return L
print anhaengen(2)  # [2]
print anhaengen(5)  # [2, 5]
print anhaengen(1, [])  # [1]; Default-Argument nicht benutzt

Längere Parameterlisten führen leichter als kurze zu einer falschen Reihenfolge der Argumente in einem Aufruf. In Python minimieren Sie dieses Risiko, indem Sie Parameter beim Aufruf mit deren Namen versehen (Listing 11). Sie müssen also nicht unbedingt auswendig wissen, dass in der Definition die Liste vor der Zeichenkette steht. Die drei Aufrufe von merke sind gleichwertig: Alle hängen die Zeichenkette an die Liste, auch wenn wie im dritten Aufruf der String zuerst steht.

Listing 11

def merke(liste, string):
    liste.append(string)
L = []
merke(L, u"Python macht Spaß!")
merke(liste=L, string=u"Python macht Spaß!")
merke(string=u"Python macht Spaß!", liste=L)

In Python dürfen Sie in Funktions- oder Methodenaufrufen ein Tupel, eine Liste oder ein Dictionary implizit in mehrere Argumente umwandeln. Die entsprechenden Beispiele aus Listing 12 sind zwar konstruiert, erklären aber, was hier passiert. Die in Form eines Tupels oder einer Liste übergebenen Argumente nennt man Positionsargumente ("positional arguments"), die als Dictionary übergebenen Argumente dagegen Schlüsselwortargumente ("keyword arguments").

Natürlich schreibt man in realem Code keine Parameterübergaben wie in Listing 12. Liegen die Argumente aber schon als Tupel, Listen oder Dictionaries vor, vereinfachen Positions- und Schlüsselwortargumente den Code.

Listing 12

def print3(a, b, c):
    """Gib die drei Argumente aus."""
    print a, b, c
# Übergabe eines Tupels oder einer Liste für die drei Parameter.
# Entspricht print3("Parameterübergabe", "mit", "*")
print3(*("Parameterübergabe", "mit", "*"))
print3(*["Parameterübergabe", "mit", "*"])
# Übergabe eines Dictionarys für die drei Parameter.
# Entspricht print3(a="Parameterübergabe", c="**", b="mit")
print3(**{"a": "Parameterübergabe", "c": "**", "b": "mit"})

Positions- und Schlüsselwortargumente lassen sich nicht nur beim Aufruf einer Funktion oder Methode anwenden, sondern auch in deren Definition. Dabei "sieht" die aufgerufene Funktion die Parameter als Tupel beziehungsweise Dictionary. Ein Beispiel dazu zeigt Listing 13. Die Funktion parameterausgabe gibt die für eine Funktion bestimmten Argumente aus und ruft die Funktion anschließend mit diesen Argumenten auf. Die Funktion parameterausgabe muss dabei nichts über die aufzurufende Funktion wissen, sondern reicht einfach nur die Parameter durch.

Listing 13

def parameterausgabe(func, *args, **kwargs):
    """Gib die Argumente aus und rufe die Funktion func
    mit diesen auf.
    """
    # Funktionsname.
    print "Funktion:", func.__name__
    # `args` ist ein Tupel; kein * davor.
    print "Positions-Argumente:", args
    # Dito für Dictionary.
    print "Schlüsselwort-Argumente:", kwargs
    # Rufe Funktion auf.
    return func(*args, **kwargs)
def polynom(x, a, b, c):
    return a * x**2 + b * x + c
# Ausgabe:
# 18
#
# Funktion: polynom
# Positions-Argumente: (2,)
# Schlüsselwort-Argumente: {'a': 2, 'c': 4, 'b': 3}
# 18
print polynom(2, a=2, b=3, c=4)
print
print parameterausgabe(polynom, 2, a=2, b=3, c=4)

Eine weitere nützliche Anwendung zeigt Listing 14. Hier muss die abgeleitete Klasse nicht im Einzelnen wissen, welche Parameter der Konstruktor der Basisklasse verarbeitet. Sie kümmert sich stattdessen nur um das für sie bestimmte Argument titel.

Listing 14

class Person(object):
    def __init__(self, vorname, nachname):
        self.vorname = vorname
        self.nachname = nachname
    def __str__(self):
        return "%s %s" % (self.vorname, self.nachname)
class Autor(Person):
    def __init__(self, *args, **kwargs):
        self.titel = kwargs.pop("titel")
        super(Autor, self).__init__(*args, **kwargs)
autor = Autor(u"Stefan", u"Schwarzer",
              titel=u"Robuste Python-Programme")

Die Übergabe eines Arguments läuft wie eine Zuweisung ab: Der lokale Name innerhalb der Funktionsdefinition wird mit dem übergebenen Objekt verknüpft. Zuweisungen innerhalb des Funktionsrumpfes beziehen sich aber standardmäßig nur auf den lokalen Namensraum der Funktion. Der Code in Listing 15, der die übergebene Liste löschen soll, funktioniert daher nicht.

Listing 15

def liste_loeschen(liste):
    liste = []
eine_liste = [1, 2, 3]
liste_loeschen(eine_liste)
print eine_liste  # [1, 2, 3], nicht gelöscht!

Tatsächlich entsteht hier innerhalb der Funktion eine neue Liste, die mit dem lokalen Namen liste verknüpft wird. Die vorherige Bindung an die Liste [1, 2, 3] geht verloren, sodass das ursprüngliche Argument nicht mehr zugänglich ist. Dennoch lässt sich die übergebene Liste innerhalb von liste_loeschen leeren, indem man die Zeile liste = [] durch liste[:] = [] ersetzt. Dadurch ändert sich die übergebene Liste.

Im Gegensatz dazu funktioniert so etwas bei unveränderlichen Objekten definitionsgemäß nicht. Hier ist das übliche Vorgehen, das Ergebnis eines Funktionsaufrufs dem ursprünglichen Namen zuzuweisen. Bei veränderlichen Argumenten verfährt man aber oft genauso.

Umgang mit Ausnahmen (Exceptions)

Einige Sprachen, wie die Unix-Shell oder C, nutzen typischerweise Rückgabewerte zur Fehlerbehandlung. Verschiedene anderen Sprachen, unter anderem Python, arbeiten stattdessen mit Ausnahmen ("Exceptions"). Zwei Erörterungen des Programmierers Ned Batchelder beschreiben anhand von Beispielen ausführlich die Vorteile der beiden Strategien ([3],[4]).

Oft findet man in Python-Code "leere" except-Anweisungen wie in Listing 16. Der Hintergedanke des Programm-Autors dabei ist normalerweise, dass nur ein bestimmter Fehler auftreten kann. Das würde die gezielte Kontrolle auf einen IOError, OSError oder eine andere Ausnahme unnötig machen. Dieser Ansatz erweist sich als problematisch, weil ein leerer except-Zweig auch Schreibfehler in Form von NameError- und AttributeError-Ausnahmen abfängt.

Listing 16

# Dieser Code tut nicht, was man erwartet.
try:
    fobj = opne("evtl_nicht_da")
except:
    print "Datei nicht vorhanden"

Im Beispiel steht in der zweiten Zeile opne statt open. Das ist nicht etwa ein Syntaxfehler, sondern erzeugt – zur Laufzeit – einen NameError. Der Code weist beim Lauf also auf eine nicht gefundene Datei hin, auch wenn es diese tatsächlich gibt. Viel Spaß beim Finden solcher Fehlers! Falls nicht ausgesprochen gute Gründe dagegen sprechen, sollten Sie die zu behandelnden Ausnahmeklassen also immer angeben.

Eine Übersicht der von der Sprache selbst erzeugten Ausnahmen gibt es unter [5]. Die mit Python gelieferten Module können darüber hinaus freilich noch andere Ausnahmen auslösen, deren Dokumentation sich jeweils in den Modulbeschreibungen findet.

Tückisch ist auch zu viel Code in einem try-Block. Listing 17 zeigt ein Beispiel, in dem eine Funktion alter_aus_db das Alter einer Person aus einer Datenbank laden soll. Die Idee dabei: Ein Zugriff auf person[name] löst einen KeyError aus, falls es keinen Personen-Datensatz im Dictionary person gibt. (Der Dictionary-Zugriff über den Schlüssel alter kann nicht zu einem KeyError führen, da der Schlüssel alter gegebenenfalls erzeugt wird.)

Listing 17

def alter_aus_db(name):
    ...
try:
    person[name][alter] = alter_aus_db(name)
except KeyError:
    print 'Kein Datensatz für Person "%s"' % name

So weit, so gut. Enthält aber alter_aus_db selbst einen Dictionary-Zugriff, zum Beispiel return cache[name], fängt dies unabsichtlich auch einen dadurch entstandenen KeyError ab.

Die Abhilfe besteht darin, gezielt nur jenen Code in den try-Block zu schreiben, der von der Ausnahmebehandlung betroffen sein soll (Listing 18). Noch besser: Lassen Sie alter_aus_db einen KeyError gar nicht erst "durchreichen", sondern setzen Sie ihn in eine stärker abstrahierte Exception wie CacheError um. Dazu fangen Sie den KeyError in alter_aus_db und lösen die stärker abstrahierte Ausnahme mit raise aus.

Listing 18

def alter_aus_db(name):
    ...
db_alter = alter_aus_db(name)
try:
    person[name][alter] = db_alter
except KeyError:
    print 'Kein Datensatz für Person "%s"' % name

Erzeugen Sie irgendwo im Code Ressourcen wie Dateien, Sockets oder Datenbank-Verbindungen, dann sollten Sie diese nach der Verwendung mit einem try/finally- oder einem with-Konstrukt freigegeben (Listing 19).

Listing 19

db_conn = connect(datenbank)
try:
    # Datenbank-Operationen
    ...
finally:
    db_conn.rollback()
    db_conn.close()
# Für Dateien alternativ mit `with` (ab Python 2.5) ...
# Import-Anweisung ist nur bei Python 2.5 nötig.
from __future__ import with_statement
with open(dateiname) as fobj:
    # Datei wird am Ende des `with`-Blocks automatisch geschlossen.
    data = fobj.read()

Wollen Sie mehrere Ausnahmetypen gleich behandeln, umgeben Sie diese wie in Listing 20 mit Klammern. Fehlen letztere, versteht Python das folgendermaßen: Fange einen ValueError ab und mache das Ausnahme-Objekt unter dem Namen IndexError verfügbar. In Python 3 sollte diese Fehlerquelle nicht mehr vorkommen, denn es macht beim gleichzeitigen Fangen mehrerer Ausnahmeklassen solche Klammern zur Pflicht; ein Ausnahme-Objekt wird stattdessen durch except ValueError as exception_object zugänglich. Python 2.6 erlaubt diese Syntax ebenfalls.

Listing 20

try:
    # Kann `ValueError` oder `IndexError` auslösen.
    ...
except (ValueError, IndexError):
    # Gleiche Fehlerbehandlung für `ValueError` und `IndexError`
    ...

Da bei mehreren passenden except-Zweigen immer der erste verwendet wird, müssen abgeleitete Ausnahmeklassen wie in Listing 21 vor deren Basisklassen erscheinen. Anderenfalls wird nur der except-Zweig der Basisklasse ausgeführt.

Listing 21

class DatabaseError(Exception):
    pass
class NotUniqueError(DatabaseError):
    pass
...
try:
    ...
# Abgeleitete vor der Basisklasse!
except NotUniqueError:
    ...
except DatabaseError:
    ...

LinuxCommunity kaufen

Einzelne Ausgabe
 
Abonnements
 
TABLET & SMARTPHONE APPS
Bald erhältlich
Get it on Google Play

Deutschland

Ähnliche Artikel

Kommentare

Infos zur Publikation

LU 12/2017: Perfekte Videos

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

LinuxUser erscheint monatlich und kostet 5,95 Euro (mit DVD 8,50 Euro). Weitere Infos zum Heft finden Sie auf der Homepage.

Das Jahresabo kostet ab 86,70 Euro. Details dazu finden Sie im Computec-Shop. Im Probeabo erhalten Sie zudem drei Ausgaben zum reduzierten Preis.

Bei Google Play finden Sie digitale Ausgaben für Tablet & Smartphone.

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

Stellenmarkt

Aktuelle Fragen

Huawei
Pit Hampelmann, 13.12.2017 11:35, 2 Antworten
Welches Smartphone ist für euch momentan das beste? Sehe ja die Huawei gerade ganz weit vorne. Bi...
Fernstudium Informatik
Joe Cole, 12.12.2017 10:36, 2 Antworten
Hallo! habe früher als ich 13 Jahre angefangen mit HTML und später Java zu programmieren. Weit...
Installation Linux mint auf stick
Reiner Schulz, 10.12.2017 17:34, 3 Antworten
Hallo, ich hab ein ISO-image mit Linux Mint auf einem Stick untergebracht Jetzt kann ich auch...
Canon Maxify 2750 oder ähnlicher Drucker
Hannes Richert, 05.12.2017 20:14, 4 Antworten
Hallo, leider hat Canon mich weiterverwiesen, weil sie Linux nicht supporten.. deshalb hier die...
Ubuntu Server
Steffen Seidler, 05.12.2017 12:10, 1 Antworten
Hallo! Hat jemand eine gute Anleitung für mich, wie ich Ubuntu Server einrichte? Habe bisher...