Datentypen als Objekte#
Inzwischen habt ihr die grundlegenden Python-Datentypen kennengelernt und wisst, wie ihr mit Hilfe von Klassen eure eigenen Datentypen erstellen könnt. Beachtet dabei, dass Python dynamisch typisiert ist, d.h., die Typen werden zur Laufzeit bestimmt, nicht zur Kompilierzeit. Dies ist einer der Gründe, warum Python so einfach zu benutzen ist. Ihr könnt einfach folgendes ausprobieren:
>>> type(3)
<class 'int'>
>>> type('Hello')
<class 'str'>
>>> type(['Hello', 'Pythonistas'])
<class 'list'>
In diesen Beispielen seht ihr die eingebaute type
-Funktion in Python.
Sie kann auf jedes Python-Objekt angewendet werden und gibt den Typ des Objekts
zurück. In diesem Beispiel sagt euch die Funktion, dass 3
ein int
(Integer) ist, dass 'Hello'
ein str
(String) und dass ['Hello', 'Pythonistas']
eine list
(Liste) ist.
Von größerem Interesse dürfte jedoch die Tatsache sein, dass Python als Antwort
auf die Aufrufe von type
Objekte zurückgibt; <class 'int'>
,
<class 'str'>
und <class 'list'>
sind die Bildschirmdarstellungen der
zurückgegebenen Objekte. Ihr könnt diese Python-Pbjekte also miteinander
vergleichen:
>>> type('Hello') == type('Pythonistas!')
True
>>> type('Hello') == type('Pythonistas!') == type(['Hello', 'Pythonistas'])
False
Mit dieser Technik könnt ihr u.a. eine Typüberprüfung in euren Funktions- und Methodendefinitionen durchführen. Die häufigste Frage zu den Typen von Objekten ist jedoch, ob ein bestimmtes Objekt eine Instanz einer Klasse ist. Ein Beispiel mit einer einfachen Vererbungshierarchie macht dies klarer:
Zunächst definieren wir zwei Klassen mit einer Vererbungshierarchie:
>>> class Form: ... pass ... >>> class Square(Form): ... pass ... >>> class Circle(Form): ... pass
Nun könnt ihr eine Instanz
c1
der KlasseCircle
erstellen:>>> c1 = Circle()
Wie erwartet, gibt die type-Funktion auf
c1
aus, dassc1
eine Instanz der KlasseCircle
ist, die in Ihrem aktuellen__main__
Namespace definiert ist:>>> type(c1) <class '__main__.Circle'>
Ihr könnt genau dieselben Informationen auch durch Zugriff auf das
__class__
-Attribut der Instanz erhalten:>>> c1.__class__ <class '__main__.Circle'>
Ihr könnt auch explizit überprüfen, ob die beiden Klassenobjekte identisch sind:
>>> c1.__class__ == Circle True
Zwei eingebaute Funktionen bieten jedoch benutzerfreundlichere Möglichkeit, die meisten der normalerweise benötigten Informationen zu erhalten:
isinstance()
stellt fest, ob z.B. eine Klasse, die an eine Funktion oder Methode übergeben wird, vom erwarteten Typ ist.
issubclass()
stellt fest, ob eine Klasse die Unterklasse einer anderen ist.
>>> issubclass(Circle, Form) True >>> issubclass(Square, Form) True >>> isinstance(c1, Form) True >>> isinstance(c1, Square) False >>> isinstance(c1, Circle) True >>> issubclass(c1.__class__, Form) True >>> issubclass(c1.__class__, Square) False >>> issubclass(c1.__class__, Circle) True
Duck-Typing#
Die Verwendung von type
, isinstance()
und
issubclass()
macht es ziemlich einfach, die Vererbungshierarchie
eines Objekts oder einer Klasse korrekt zu bestimmen. Python hat jedoch auch
eine Funktion, die die Verwendung von Objekten noch einfacher macht:
Duck-Typing:
„If it walks like a duck and it quacks like a duck, then it must be a duck.“
Dies bezieht sich auf Pythons Art und Weise zu bestimmen, ob ein Objekt der erforderliche Typ für eine Operation ist, wobei der Schwerpunkt auf der Schnittstelle eines Objekts liegt. Kurz gesagt müsst ihr euch in Python nicht um die Typüberprüfung von Funktions- oder Methodenargumenten und Ähnlichem kümmern, sondern euch stattdessen auf lesbaren und dokumentierten Code in Verbindung mit Tests verlassen, um sicherzustellen, dass ein Objekt bei Bedarf „wie eine Ente quakt.“
Duck-Typing kann die Flexibilität von gut geschriebenem Code erhöhen und gibt euch in Kombination mit fortgeschrittenen objektorientierten Funktionen die Möglichkeit, Klassen und Objekte zu erstellen, die fast jede Situation abdecken. Solche speziellen Methoden sind Attribute einer Klasse mit besonderer Bedeutung für Python. Sie sind zwar als Methoden definiert, aber nicht dazu gedacht, sie direkt aufzurufen; stattdessen werden sie von Python automatisch als Reaktion auf eine Anforderung an ein Objekt dieser Klasse aufgerufen.
Eines der einfachsten Beispiele für eine spezielle Methode ist
object.__str__()
. Wenn es in einer Klasse definiert ist, wird das
__str__
-Methodenattribut jedes Mal aufgerufen, wenn eine Instanz dieser
Klasse verwendet wird und Python eine benutzerlesbare Zeichenkettendarstellung
dieser Instanz benötigt. Um dieses Attribut in Aktion zu sehen, verwenden wir
erneut unsere Form
-Klasse mit der Standardmethode __init__
um Instanzen
der Klasse zu initialisieren, sondern auch eine __str__
-Methode um
Zeichenketten zurückzugeben, die Instanzen in einem lesbaren Format darstellen:
>>> class Form:
... def __init__(self, x, y):
... self.x = x
... self.y = y
... def __str__(self):
... return "Position: x={0}, y={1}".format (self.x, self.y)
...
>>> f = Form(2,3)
>>> print(f)
Position: x=2, y=3
Auch wenn unser spezielles __str__
-Methodenattribut nicht von unserem Code
explizit aufgerufen wurde, konnte es dennoch von Python verwendet werden, da
Python weiß, dass das __str__
-Attribut, falls vorhanden, eine Methode zur
Umwandlung von Objekten in benutzerlesbare Zeichenketten definiert. Und genau
dies zeichnet die speziellen Methodenattribute aus. So ist es z.B. oft eine gute Idee, das __str__
-Attribut für eine Klasse zu
definieren, damit ihr im Debugging-Code print(instance)
aufrufen könnt und
eine informative Aussage über euer Objekt zu erhalten.
Umgekehrt kann es jedoch auch verwundern, dass ein Objekttyp anders auf spezielle Methodenattribute reagiert. Daher verwende ich spezielle Methodenattribute meist nur in einer der folgenden beiden Fälle:
in einer häufig verwendeten Klasse, meist für Sequenzen, die sich ähnlich wie ein in Python eingebauter Typ verhält, und die durch spezielle Methodenattribute nützlicher wird.
in einer Klasse, die sich fast identisch zu einer eingebauten Klasse verhält, z.B. Listen, die als balancierte Bäume implementiert sind, um das Einfügen zu beschleunigen, kann ich die speziellen Methodenattribute definieren.