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:

  1. Zunächst definieren wir zwei Klassen mit einer Vererbungshierarchie:

    >>> class Form:
    ...     pass
    ...
    >>> class Square(Form):
    ...     pass
    ...
    >>> class Circle(Form):
    ...     pass
    
  2. Nun könnt ihr eine Instanz c1 der Klasse Circle erstellen:

    >>> c1 = Circle()
    
  3. Wie erwartet, gibt die type-Funktion auf c1 aus, dass c1 eine Instanz der Klasse Circle ist, die in Ihrem aktuellen __main__ Namespace definiert ist:

    >>> type(c1)
    <class '__main__.Circle'>
    
  4. Ihr könnt genau dieselben Informationen auch durch Zugriff auf das __class__-Attribut der Instanz erhalten:

    >>> c1.__class__
    <class '__main__.Circle'>
    
  5. Ihr könnt auch explizit überprüfen, ob die beiden Klassenobjekte identisch sind:

    >>> c1.__class__ == Circle
    True
    
  6. 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.