Klassen und Objektorientierung¶
Um Quellcode besser zu strukturieren, werden bei der objektorientierten Programmierung so genannte Klassen erzeugt, die jeweils bestimmte Eigenschaften („Attribute“) und Funktionen („Methoden“) besitzen. Klassen werden in Python allgemein durch einzelne Zeichenketten mit großen Anfangsbuchstaben gekennzeichnet.
Eine neue Klasse wird in Python mit dem Schlüsselwort class
, gefolgt vom
Klassennamen und einem Doppelpunkt eingeleitet. Alle darauf folgenden
Definitionen von Eigenschaften und Funktionen, die zur Klasse gehören, werden
um eine Tabulatorweite (üblicherweise 4 Leerzeichen) eingerückt.
Definition und Initialisierung eigener Klassen¶
Ebenso wichtig wie der Begriff einer Klasse ist der Begriff der Instanz einer Klasse. Während beispielsweise die Klasse „Wecker“ einen Objekttyp eines meist unhöflich Lärm erzeugenden Gerätes darstellt, so ist ein einzelner neben einem Bett stehender Wecker ein konkreter Vertreter dieser Klasse. Eine solche Instanz hat, zumindest in der Programmierung, stets alle in der Klasse definierten Eigenschaften und Funktionen, allerdings mit möglicher unterschiedlicher Ausprägung (beispielsweise Farbe oder Klingelton).
In Python könnte die Implementierung einer Beispielklasse etwa so aussehen:
class AlarmClock():
"""
Just a simple Class example.
"""
def __init__(self, color, sound):
"""
Initialize a new alarm clock.
Arguments:
* color (string): Color of the alarm clock.
* sound (string): Ringing sound of the clock.
"""
self.color = color
self.sound = sound
def show_color(self):
return self.color
def ring(self):
return self.sound + "!!!"
Im obigen Beispiel wurde zunächst die Klasse AlarmClock
definiert und als.
erstes mit einem Docstring versehen, der eine kurze
Beschreibung der Klasse liefert.
In der Funktion __init__()
wird anschließend festgelegt, wie eine neue
Instanz der Klasse erzeugt wird. Die angegebenen Argumente werden dabei als
Eigenschaften der Instanz, die in Python mit self
bezeichnet wird,
gespeichert. Nach der Initialisierung stehen im obigen Beispiel dem neuen Objekt
dann die beiden weiteren angegebenen Funktionen show_color()
und ring()
bereit, die als Ausgabe der jeweiligen Objektvariablen dienen.
Die Methode __init__()
wird automatisch aufgerufen, wenn man den Namen der
Klasse als Funktion aufruft. Eine neue Instanz einer Klasse lässt sich im
Fall des obigen Beispiels also folgendermaßen erzeugen:
# Initialisierung:
my_alarm_clock = AlarmClock("green", "Ring Ring Ring")
# Objekttyp testen:
type(my_alarm_clock)
# Ergebnis: __main__.AlarmClock
# Funktionen testen:
my_alarm_clock.show_color()
# Ergebnis: 'green'
my_alarm_clock.ring()
# Ergebnis: 'Ring Ring Ring!!!'
Mittels type(objektname)
kann allgemein angezeigt werden, zu welcher Klasse
ein beliebiges Python-Objekt gehört; ebenso kann mit isinstance(objektname,
klassenname)
geprüft werden, ob ein Objekt eine Instanz der angegebenen Klasse
ist.
Möchte man eine konkrete Instanz wieder löschen, so ist dies allgemein mittels
del(name_der_instanz)
möglich, im obigen Fall also mittels
del(my_alarm_clock)
. Genau genommen wird die Instanz allerdings nur dann
gelöscht, wenn die letzte Referenz auf die Instanz gelöscht wird. Sind
beispielsweise mittels alarm_clock_1 = alarm_clock_2 = AlarmClock("blue",
"Ring!")
zwei Referenzen auf die gleiche Instanz erzeugt worden, so wird
mittels del(alarm_clock_1)
nur die erste Referenz gelöscht; die Instanz
selbst bleibt weiter bestehen, da die Variable alarm_clock_2
immer noch
darauf verweist. Beim Löschen der letzten Referenz auf wird automatisch Pythons
„Garbage Collector“ aktiv und übernimmt die Aufräumarbeiten, indem die
entsprechende __del__()
-Methode aufgerufen wird; ebenso wird der benötigte
Platz im Arbeitsspeicher dadurch automatisch wieder freigegeben.
Sollen bei der Löschung einer Instanz weitere Aufgaben abgearbeitet werden, so
können diese in einer optionalen Funktion __del__()
innerhalb der Klasse
festgelegt werden.
Allgemeine Eigenschaften von Klassen¶
Klassen dienen allgemein dazu, die Attribute und Methoden (kurz: „Member“)
einzelner Objekte festzulegen. Bei der Festlegung der Attribute und Methoden
wird jeweils das Schlüsselwort self
genutzt, das auf die jeweilige Instanz
einer Klasse verweist.
Attribute eines Objekts werden mittels self.attributname = wert
festgelegt;
dies erfolgt üblicherweise bereits bei der Initialisierung. Anschließend kann
auf die so definierten Attribute innerhalb der Klasse mittels
self.attributname
und außerhalb mittels instanzname.attributname
zugegriffen werden.
Methoden eines Objekts werden ebenso wie Funktionen definiert, mit der einzigen
Besonderheit, dass als erstes Argument stets self
angegeben wird. Innerhalb
der Klasse können so definierte Methoden mittels self.methodenname()
und
außerhalb mittels instanzname.methodenname()
aufgerufen werden.
Geschützte und private Attribute und Methoden
In manchen Fällen möchte man vermeiden, dass die Attribute eines Objekts durch andere Objekte manipuliert werden können; ebenso sind manche Methoden nur für den „internen“ Gebrauch innerhalb einer Klasse geeignet. In Python gibt es für diesen Zweck sowohl geschützte („protected“) als auch private („private“) Attribute und Methoden:
- Geschützte Member (Attribute und Methoden) werden durch einen einfach Unterstrich vor dem jeweiligen Namen gekennzeichnet. Auf derartige Attribute oder Methoden kann weiterhin von einer anderen Stelle aus sowohl lesend als auch schreibend zugegriffen werden; es ist vielmehr eine Konvention zwischen Python-Entwicklern, dass auf derartige Member nicht direkt zugegriffen werden sollte.
- Private Member werden durch einen doppelten Unterstrich vor dem jeweiligen Namen gekennzeichnet. Auf derartige Attribute kann außerhalb der Klasse weder lesend noch schreibend zugegriffen werden.
Attribute sollten beispielsweise dann als geschützt oder privat gekennzeichnet werden, wenn sie nur bestimmte Werte annehmen sollen. In diesem Fall werden zusätzlich so genannte „Getter“ und „Setter“-Methoden definiert, deren Aufgabe es ist, nach einer Prüfung auf Korrektheit den Wert des entsprechenden Attributs auszugeben oder ihm einen neuen Wert zuzuweisen.
Setter- und Getter-Methoden werden bevorzugt als so genannte „Properties“ definiert; dazu wird folgende Syntax verwendet:
class MyClass:
def __init__(self):
self._myattr = None
def get_myattr(self):
return self._myattr
def set_myattr(self, value):
# todo: check if value is valid
self._myattr = value
myattr = property(get_myattr, set_myattr)
Mittels der property()
-Funktion wird die Setter- und Getter-Methode eines
geschützten oder privaten Attributs dem gleichen, nicht-geschützten
Attributnamen zugewiesen. Von außerhalb der Klasse ist dieses gezielte Handling
also nicht sichtbar, das entsprechende Attribut erscheint von außen also wie ein
gewöhnliches Attribut. Dieses Prinzip der Kapselung von Aufgaben ist typisch für
objektorientierte Programmierung: Wichtig ist es, die Aufgabe eines Objekts klar
zu definieren sowie seine „Schnittstellen“, also seine von außerhalb
zugänglichen Attribute und Methoden, festzulegen. Solange das Objekt als
einzelner Baustein seine Aufgabe damit erfüllt, braucht man sich als Entwickler
um die Interna dieses Bausteins nicht weiter Gedanken zu machen.
Statische Member
Neben Attributen und Methoden, die jede einzelne Instanz einer Klasse an sich bereitstellt, können innerhalb einer Klasse auch Attribute und/oder Methoden definiert werden, die für alle Instanzen der Klasse gleichermaßen gelten. Derartige Klassenmember werden „statisch“ genannt.
Statische Attribute lassen sich erzeugen, indem innerhalb der Klasse
(üblicherweise gleich zu Beginn des Klassen-Blocks) die gewünschten Attribute
wie normale Variablen gesetzt werden, also ohne vorangestelltes self
und
außerhalb einer Methoden-Definition. Der Zugriff auf statische Attribute kann
dann (sowohl lesend als auch schreibend) wahlweise mittels
Klassenname.attributname
oder mittels Instanzname.attributname
erfolgen:
# Definition eines statischen Attributs:
class MyClass:
myattr = 42
pass
# Zugriff auf ein statisches Attribut:
MyClass.myattr
# Ergebnis: 42
MyClass().myattr
# Ergebnis: 42
Statische Methoden werden ebenso wie gewöhnliche Methoden definiert, außer dass
bei der Definition das self
als erstes Argument weggelassen wird;
stattdessen wird die Methode anschließend mittels der Funktion
staticmethod()
zur statische Methode deklariert:
# Definition einer statischen Methode:
class MyClass:
def mymethod():
print("Hello!")
mymethod = staticmethod(mymethod)
# Aufruf einer statischen Methode:
MyClass.mymethod()
# Ergebnis: Hello!
MyClass().mymethod()
# Ergebnis: Hello!
Statische Attribute und Methoden können auch dann genutzt werden, wenn (noch) keine Instanz der Klasse existiert.
„Magic“ Member
Als „Magic Member“ werden private Attribute und Funktionen von Klassen
bezeichnet, die es beispielsweise ermöglichen, einzelne Instanzen der Klassen
mittels Operatoren miteinander in Relation zu setzen oder Builtin-Funktionen auf
die einzelnen Instanzen anzuwenden. Die Bezeichnung „magisch“ stammt daher, dass
diese Methoden und Attribute selten direkt mit ihrem Namen angesprochen werden,
aber dafür oft implizit genutzt werden – wie beispielsweise die __init__()
oder __del__()
-Funktionen als Konstruktor- und Destruktor-Methoden einzelner
Instanzen. Beispielsweise wird anstelle MyClass.__init__()
wird
üblicherweise MyClass()
geschrieben, um eine neue Instanz einer Klasse zu
erzeugen; bei letzterer Variante wird die __init__()
-Funktion vom
Python-Interpreter implizit aufgerufen.
Folgende Member sind für die Repräsentation von Objekten vorgesehen:
- Mittels der Methode
__str()__
wird eine für Menschen gut lesbare Zeichenkette definiert, die beim Aufruf vonstr(MyClass)
als Objektbeschreibung ausgegeben werden soll.
- Die Methode
__repr()
wird so definiert, dass sie Python-Code als Ergebnis zurückgibt, bei dessen Ausführung eine neue Instanz der jeweiligen Klasse erzeugt wird. - Die Methode
__call__()
bewirkt, sofern sie definiert ist, dass eine Instanz einer Klasse – ebenso wie eine Funktion – aufgerufen werden kann.
Folgende Member sind für den Zugriff auf Attribute vorgesehen:
- Mit dem Attribut
__dict__
wird als dict aufgelistet, welche Attribute und zugehörigen Werte die jeweilige Instanz der Klasse aktuell beinhaltet.
- Durch das (statische) Attribut
__slots__
kann mittels einer Liste festgelegt werden, welche Attributnamen die Instanzen einer Klasse haben. Wird versucht, mittelsdel(instanzname.attributname)
ein Attribut einer Instanz zu löschen, dessen Name in der__slots__
-Liste enthalten ist, so schlägt das Löschen mit einemAttributeError
fehl. Umgekehrt wird auch dann einAttributeError
ausgelöst, wenn man versucht, ein neues Attribut für die Instanz festzulegen, dessen Name nicht in der__slots__
-Liste enthalten ist.
Mit der Methode
__getattr__()
wird definiert, wie sich die Klasse zu verhalten hat, wenn ein angegebenes Attribut abgefragt wird, aber nicht existiert. Wahlweise kann ein Standard-Wert zurückgegeben werden, üblicherweise wird jedoch einAttributeError
ausgelöst.Mit der Methode__getattribute__()
wird das angegebene Attribut ausgegeben, sofern es existiert. Andernfalls kann – wie bei__getattr__()
– wahlweise ein Standard-Wert zurückgegeben oder einAttributeError
ausgelöst werden. Wenn die__getattribute()__
definiert ist, wird die__getattr__()
-Methode nicht aufgerufen, außer sie wird innerhalb von__getattribute()__
explizit aufgerufen.Zu beachten ist außerdem, dass innerhalb von__getattribute()__
nur mittelsklassenname.__getattribute__(self, attributname)
auf ein Attribut zugegriffen werden darf, nicht mittelsself.attributname
; im letzteren Fall würde ansonsten eine Endlos-Schleife erzeugt.
- Mit der Methode
__setattr__()
wird eine Routine angegeben, die aufgerufen wird, wenn ein Attribut neu erstellt oder verändert wird. Dafür wird zunächst der Name des Attributs und als zweites Argument der zuzuweisende Wert angegeben. Insbesondere kann mit dieser Methode kontrolliert werden, dass keine unerwünschten Attribute vergeben werden können.Bei der Verwendung von__setattr__()
ist zu beachten, dass die Wertzuweisung mittelsself.__dict__['attributname'] = wert
erfolgen muss, da ansonsten eine Endlos-Schleife erzeugt würde.
- Mit
__delattr__()
wird als Methode festgelegt, wie sich eine Instanz beim Aufruf vondel(instanzname.attributname)
verhalten soll.
Folgende Member sind für den Vergleich zweier Objekte vorgesehen:
- Mit den Methoden
__eq__()
(„equal“) und__ne__()
(„not equal“) kann festgelegt werden, nach welchen Kriterien zwei Instanzen verglichen werden sollen, wenninstanz_1 == instanz_2
beziehungsweiseinstanz_1 != instanz_2
aufgerufen wird. Diese Operator-Kurzschreibweise wird intern ininstanz_1.__eq__(instanz_2)
übersetzt, es wird also die Equal-Methode des ersten Objekts aufgerufen.
- Mit den Methoden
__gt__()
(„greater than“) und__ge__()
(„greater equal“) kann festgelegt werden, nach welchen Kriterien sich zwei Instanzen verglichen werden sollen, wenninstanz_1 > instanz_2
beziehungsweiseinstanz_1 >= instanz_2
aufgerufen wird. Entsprechend kann mit__lt__()
(„less than“) und__le__()
(„less equal“) kann festgelegt werden, nach welchen Kriterien sich zwei Objekte verglichen werden, wenninstanz_1 < instanz_2
beziehungsweiseinstanz_1 <= instanz_2
aufgerufen wird.
- Mit der Methode
__hash__()
wird ein zu der angegebenen Instanz gehörender Hash-Wert ausgegeben, der die Instanz als Objekt eindeutig identifiziert.
Folgendes Member ist für logische Operationen vorgesehen:
- Mit der Methode
__bool__()
wird festgelegt, in welchen Fällen eine Instanz den WahrheitswertTrue
oder den WahrheitswertFalse
zurückgeben soll, wennbool(instanz)
aufgerufen wird; dies erfolgt beispielsweise implizit bei if-Bedingungen.
Folgende Member sind für numerische Operationen vorgesehen:
- Mit den Methoden
__int__()
,__oct__()
,__hex__()
,__float__()
,__long__()
und__complex__()
kann festgelegt werden, wie das Objekt durch einen Aufruf vonint(instanz)
,oct(instanz)
usw. in den jeweiligen numerischen Datentyp umzuwandeln ist. - Mit den Methoden
__pos__()
und__neg__()
kann festgelegt werden, welche Ergebnisse die unären Operatoren+instanz
beziehungsweise-instanz
liefern sollen; zusätzlich kann mit der Methode__abs__()
(Absolut-Betrag) festgelegt werden, welches Ergebnis ein Aufruf vonabs(instanz)
liefern soll. Ebenso wird durch die Methoden__round__()
(Rundung) bestimmt, welches Ergebnis beim Aufruf vonround(instanz)
bzw.round(instanz, num)
zu erwarten ist. - Mit den Methoden
__add__()
,__sub__()
,__mul__()
und__truediv__()
wird festgelegt, welches Ergebnis sich bei der Verknüpfung zweier Instanzen mit den vier Grundrechenarten ergeben soll, alsoinstanz_1 + instanz_2
,instanz_1 - instanz_2
, usw. Zusätzlich wird durch die Methode__pow__()
(Potenzrechnung) festgelegt, welches Ergebnisinstanz_1 ** instanz__2
liefern soll. - Mit der Methode
__floordiv__()
wird die Bedeutung voninstanz_1 // instanz_2
festgelegt; bei normalen Zahlen wird damit derjenigeint
-Wert bezeichnet, der kleiner oder gleich dem Ergebnis der Division ist (beispielsweise ergibt5 // 3
den Wert1
, und-5 // 3
den Wert-2
). Zudem wird durch die Methode__mod__()
(Modulo-Rechnung) die Bedeutung voninstanz_1 % instanz_2
festgelegt, was bei normalen Zahlen dem ganzzahligen Divisionsrest entspricht.
Folgende Member sind für Wertzuweisungen vorgesehen:
- Mit den Methoden
__iadd__()
,__isub__()
,__imul__()
und__itruediv__()
wird festgelegt, welches Ergebnis sich bei der kombinierten Wertzuweisung zweier Instanzen mit den vier Grundrechenarten ergeben soll, alsoinstanz_1 += instanz_2
,instanz_1 -= instanz_2
, usw. Zusätzlich wird durch die Methode__ipow__()
(Potenzrechnung) festgelegt, welches Ergebnisinstanz_1 **= instanz__2
liefern soll. - Mit der Methode
__ifloordiv__()
wird die Bedeutung voninstanz_1 //= instanz_2
festgelegt; bei normalen Zahlen wirdinstanz_1
dabei derjenige Wert zugewiesen, der sich bei der Auswertung voninstanz_1 // instanz_2
ergibt. In gleicher Weise wird durch die Methode__imod__()
(Modulo-Rechnung) die Bedeutung voninstanz_1 %= instanz_2
festgelegt; hierbei erhältinstanz_1
als Wert das Ergebnis voninstanz_1 % instanz_2
.
Folgende Member sind für Container, Slicings und Iteratoren vorgesehen:
- Die Methode
__len__()
wird aufgerufen, wenn mittelslen(instanz)
die Länge eines Containers geprüft werden soll. - Die Methode
__contains__()
wird aufgerufen, wenn mittelselement in instanz
geprüft wird, ob das als Argument angegebene Element im Container enthalten ist. - Die Methode
__getitem__()
wird aufgerufen, wenn mittelsinstanz[key]
der zu dem als Argument angegebenen Schlüssel gehörende Wert abgerufen werden soll. - Die Methode
__setitem__()
wird aufgerufen, wenn mittelsinstanz[key] = value
dem als erstes Argument angegebenen Schlüssel der als zweites Argument angegebene Wert zugewiesen werden soll - Die Methode
__delitem__()
wird aufgerufen, wenn mittelsdel instanz[element]
das als Argument angegebene Element aus dem Contaniner gelöscht werden soll.
Folgende Member sind für die Verwendung des Objekts innerhalb von with-Konstrukten vorgesehen:
- Mit der Methode
__enter__()
werden Anweisungen definiert, die einmalig ausgeführt werden sollen, wenn eine Instanz der Klasse in eine with-Umgebung geladen wird. - Mit der Methode
__exit__()
werden Anweisungen definiert, die einmalig ausgeführt werden sollen, wenn eine with-Umgebung um eine Instanz der Klasse wieder verlassen wird.
Mit Hilfe der „Magic Members“ können also neue Klassen von Objekten definiert
werden, deren Verhalten dem von bestehenden Klassen (Zahlen, Listen, usw.)
ähnelt. Wird einem Operator, beispielsweise dem Plus-Operators +
, mittels
der Funktion __add__()
eine neue, auf den Objekttyp passendere Bedeutung
zugewiesen, so spricht man auch von „Operator-Überladung“.
Objekte zeichnen sich also weniger durch ihre Bezeichnung, sondern vielmehr durch ihre Eigenschaften und Methoden aus. Dieses Konzept wird bisweilen auch als „Duck Typing“ bezeichnet:
„Wenn ich einen Vogel sehe, der wie eine Ente läuft, wie eine Ente schwimmt und wie eine Ente schnattert, dann nenne ich diesen Vogel eine Ente.“
Kann eine Funktion umgekehrt auf verschiedene Objekt-Typen angewendet werden, so
nennt man sie polymorph. Beispielsweise kann die Builtin-Funktion sum()
auf
beliebige listen-artige Objekttypen angewendet werden, sofern für diese eine
__add__()
-Funktion definiert ist. Durch Polymorphismus als Spracheigenschaft
wird einmal geschriebener Code oftmals auch an einer anderen Stelle verwendbar.
Vererbung¶
Mit dem Begriff „Vererbung“ wird ein in der Objekt-orientierten Programmierung sehr wichtiges Konzept bezeichnet, das es ermöglicht, bei der Definition von fein spezifizierten Objekte auf allgemeinere Basis-Klassen zurückzugreifen; die Basis-Klasse „vererbt“ dabei ihre Attribute und Methoden an die abgeleitete Sub-Klasse, wobei in dieser weitere Eigenschaften hinzukommen oder die geerbten Eigenschaften angepasst werden können. Aus mathematischer Sicht ist die Basis-Klasse eine echte Teilmenge der daraus abgeleiteten Klasse, da diese alle Eigenschaften der ursprünglichen Klasse (und gegebenenfalls noch weitere) enthält.
Um die Eigenschaften einer Basis-Klasse in einer neuen Klasse zu übernehmen, muss diese bei der Klassendefinition in runden Klammern angegeben werden:
class SubClass(MyClass):
pass
Bis auf diese Besonderheit werden alle Attribute und Methoden in einer abgeleiteten Klasse ebenso definiert wie in einer Klasse, die keine Basis-Klasse aufweist.
In Python ist es prinzipiell möglich, dass eine Klasse auch mehrere Basis-Klassen aufweist; in diesem Fall werden die einzelnen Klassennamen bei der Definition der neuen Klasse durch Kommata getrennt angegeben. Die links stehende Klasse hat dabei beim Vererben der Eigenschaften die höchste Priorität, gleichnamige Attribute oder Methoden werden durch die weiteren Basis-Klassen also nicht überschrieben, sondern nur ergänzt. Da durch Mehrfach-Vererbungen allerdings keine eindeutige Baumstruktur mehr vorliegt, also nicht mehr auf den ersten Blick erkennbar ist, aus welcher Klasse die abgeleiteten Attribute und Methoden ursprünglich stammen, sollte Mehrfach-Vererbung nur in Ausnahmefällen und mit Vorsicht eingesetzt werden.
Dekoratoren¶
Dekoratoren werden in Python als Kurzschreibweise verwendet, um bestimmte, innerhalb einer Klasse definierte Methoden mit zusätzlichen Methoden zu „umhüllen“.
Der wohl wichtigste Dekorator ist @property: Mit Hilfe dieses Dekorators kann eine get-Methode zu einem „Attribut“ gemacht werden, dessen Wert nicht statisch in einer Variablen abgelegt ist, sondern dynamisch mittels der dekorierten get-Methode abgefragt wird. Die grundlegende Funktionsweise ist folgende:
# Beispiel-Klasse definieren:
class C(object):
# Variable, die nur "intern" verwendet werden soll:
_counter = 0
# get-Methode, mittels derer ein Wert ausgegeben werden soll:
def get_counter(self):
return self._counter
# Markierung der get-Methode als Property
counter = property(get_counter)
Anhand des obigen Beispiels kann man gut erkennen, dass in der Beispiel-Klasse C
neben der als nur zur internen Verwendung vorgesehenen Variablen _counter
auch noch ein zweites Attribug counter
definiert wird, und zwar explizit als
Aufruf von property()
.
Wird via c = C()
eine neue Instanz der Klasse erzeugt, so wird mittels
c.counter
die get_counter()
-Funktion aufgerufen. Die für einen
Methoden-Aufruf typischen runden Klammern entfallen also, von außen hat es den
Anschein, als würde auf ein gewöhnliches Attribut zugegriffen. Intern hingegen
wird die _counter
-Variable, die womöglich an anderen Stellen innerhalb der
Klasse verändert wird, ausgegeben.
Als Kurzschreibweise für ähnliche Methoden-„Wrapper“ gibt es in Python folgende Syntax:
class C(object):
_counter = 0
@property
def get_counter(self):
return self._counter
Die Zeile @property
wird dabei „Dekorator“ genannt. Diese Kurzschreibweise
hat den gleichen Effekt wie das obige, explizite Wrapping der zugehörigen
Methode.
… to be continued … .. TODO
Generatoren und Iteratoren¶
Generatoren sind Objekte, die über eine __next__()
-Funktion verfügen, also
bei jedem Aufruf von next(generator_object)
einen neuen Rückgabewert
liefern. Man kann sich einen Generator also vorstellen wie eine Funktion, die
mit jedem Aufruf das nächste Element einer Liste zurückgibt. Kann der Generator
keinen weiteren Rückgabewert liefern, wird ein StopIteration
-Error
ausgelöst.
Der Vorteil von Generatoren gegenüber Listen liegt darin, dass auch bei sehr
großen Datenmengen kein großer Speicherbedarf nötig ist, da immer nur das
jeweils aktuell zurückgegebene Objekt im Speicher existiert. Beispielsweise
handelt es sich auch bei File-Objekten um Generatoren, die beim
Aufruf von next(fileobject)
jeweils die nächste Zeile der geöffneten
ausgeben. Auf diese Weise kann der Inhalt einer (beliebig) großen Datei
beispielsweise mittels einer for
-Schleife abgearbeitet werden, ohne dass der
Inhalt der Datei auf einmal eingelesen und als Liste gespeichert werden muss.
Generatoren werden mit einer Syntax erzeugt, die der von List Comprehensions sehr ähnlich ist; es wird lediglich runde Klammern anstelle der eckigen Klammern zur Begrenzung des Ausdrucks verwendet:
# List Comprehension:
mylist = [i**2 for i in range(1,10)]
mylist
# Ergebnis: [1, 4, 9, 16, 25, 36, 49, 64, 81]
# Generator:
mygen = (i**2 for i in range(1,10))
next(mygen)
# Ergebnis: 1
next(mygen)
# Ergebnis: 4
# ...
Generatoren können auch verschachtelt auftreten; bestehende Generatoren können also zur Konstruktion neuer Generatoren verwendet werden:
# Neuen Generator mittels eines existierenden erzeugen:
mygen2 = (i**2 for i in mygen)
next(mygen2)
# Ergebnis: 81
next(mygen2)
# Ergebnis: 256
Python kann diese Art von verschachtelten Generatoren sehr schnell auswerten, so dass Generatoren allgemein sehr gut für die Auswertung großer Datenströme geeignet sind. Zu beachten ist lediglich, dass der Generator bei jedem Aufruf – ähnlich wie eine Funktion – einen Rückgabewert liefert, der von sich aus nicht im Speicher verbleibt, sondern entweder unmittelbar weiter verarbeitet oder manuell gespeichert werden muss.