10 Fortgeschrittene Objektorientierung
type(), isinstance() und issubclass()Diese eingebauten Funktionen helfen beim Umgang mit Typen und Vererbung.
type(obj) gibt den exakten Typ des Objekts zurück.isinstance(obj, cls) prüft, ob obj eine Instanz von cls oder einer abgeleiteten Klasse ist.issubclass(sub, super) prüft, ob sub eine Unterklasse von super ist.class Animal: pass
class Dog(Animal): pass
a = Animal()
d = Dog()
print(type(d)) # <class '__main__.Dog'>
print(isinstance(d, Animal)) # True
print(issubclass(Dog, Animal)) # True
Diese Funktionen sind wichtig für dynamisches Verhalten, Validierung und Typprüfung.
__init__ vs __new____new__ ist für das Erzeugen eines neuen Objekts zuständig.__init__ wird anschließend aufgerufen, um das Objekt zu initialisieren.class Custom:
def __new__(cls, *args, **kwargs):
print('Creating instance')
return super().__new__(cls)
def __init__(self, value):
print('Initializing with', value)
self.value = value
obj = Custom(42)
__new__ wird z. B. bei unveränderlichen Typen wie str oder tuple benötigt, wenn diese beeinflusst werden sollen oder in der Metaprogrammierung (Singleton-Pattern).
@staticmethod definiert eine Methode, die keinen Zugriff auf self oder cls benötigt.@classmethod arbeitet mit cls und kann so auf die Klasse zugreifen.class Example:
@staticmethod
def utility():
print('Static method called')
@classmethod
def construct(cls):
print(f'Creating instance of {cls.__name__}')
return cls()
Example.utility()
obj = Example.construct()
classmethod wird häufig für alternative Konstruktoren verwendet.
Mit @property können Methoden wie Attribute verwendet werden. Das ist nützlich für gekapselte Attribute, z. B. mit Validierung oder automatischer Berechnung.
class Person:
def __init__(self, name):
self._name = name # Private Konvention
@property
def name(self):
return self._name
@name.setter
def name(self, new):
self._name = new
p = Person('Alice')
print(p.name)
p.name = 'Bob'
print(p.name)
Durch Properties kann die API einfach bleiben, während intern komplexe Logik stattfinden kann.
Dunder (Double Underscore) Methoden ermöglichen benutzerdefiniertes Verhalten für Operatoren und eingebaute Funktionen (__str__, __len__, __getitem__ usw.).
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return f'({self.x}, {self.y})'
def __eq__(self, other):
return self.x == other.x and self.y == other.y
p1 = Point(1, 2)
p2 = Point(1, 2)
print(str(p1)) # (1, 2)
print(p1 == p2) # True
Diese Methoden machen Objekte "pythonisch".
Das abc-Modul erlaubt die Definition abstrakter Basisklassen. Methoden mit @abstractmethod müssen in Subklassen implementiert werden.
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
class Circle(Shape):
def area(self):
return 3.14 * 5 * 5
Abstrakte Methoden zwingen Unterklassen zur Implementierung und helfen beim Design stabiler APIs.
Python verwendet das C3 Linearization-Verfahren, um die Reihenfolge von Vererbungen zu bestimmen. Die mro()-Methode zeigt die Aufrufreihenfolge.
class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass
print(D.mro()) # [D, B, C, A, object]
Wichtig bei Mehrfachvererbung.
Ein Iterator benötigt die Methoden __iter__ und __next__. Alternativ kann yield verwendet werden.
class Count:
def __init__(self, max):
self.max = max
self.current = 0
def __iter__(self):
return self
def __next__(self):
if self.current >= self.max:
raise StopIteration
self.current += 1
return self.current
Generatoren vereinfachen Iteration:
def count_up_to(max):
current = 0
while current < max:
current += 1
yield current
Mithilfe von __getitem__ und __setitem__ können Objekte wie Listen verwendet werden.
class CustomList:
def __init__(self, data):
self.data = data
def __getitem__(self, index):
return self.data[index]
def __setitem__(self, index, value):
self.data[index] = value
Sehr nützlich für eigene Containerklassen.
Dataclasses automatisieren Konstruktor, Vergleich und Darstellung.
from dataclasses import dataclass
@dataclass
class User:
name: str
age: int
Mit __slots__ wird der Speicherverbrauch reduziert und der Zugriff beschleunigt.
class Slim:
__slots__ = ('x', 'y')
def __init__(self, x, y):
self.x = x
self.y = y
Seit Python 3.10 ist folgendes möglich (gleichwertiger Code):
@dataclass(slots=True)
class Slim:
x: int
y: int
__slots__ bzw. slots=True?Normalerweise speichert Python Objektattribute in einem internen Dictionary namens __dict__. Das ist flexibel, jedoch nicht speichereffizient oder performant.
Wenn __slots__ bzw. slots=True verwendet wird, wird dieses Dictionary ersetzt durch ein festeres Layout, bei dem nur die im Slot definierten Felder erlaubt sind.
Warum verbessert das die Leistung?
Weniger Speicherverbrauch:
Jedes Objekt braucht weniger Speicher, weil kein __dict__ mehr angelegt wird.
Schnellerer Zugriff auf Attribute:
Statt eines Dictionary-Lookups wird ein schnellerer, indexbasierter Zugriff verwendet.
Bessere Caching-Effekte:
Schlankere Objekte passen besser in den Cache, was zu weiteren Geschwindigkeitsgewinnen führen kann – besonders bei großen Listen von Objekten.
frozen=TrueDas frozen=True-Argument macht die Instanz unveränderlich (immutable):
@dataclass(frozen=True)
class Point:
x: int
y: int
p = Point(1, 2)
print(p.x) # -> 1
p.x = 10 # -> Fehler: cannot assign to field 'x'
setattr, der Änderungen verhindert.dict oder als Elemente in einem set zu verwenden.frozen=True führt zu einer Leistungssteigerung, diese ist jedoch minimal.Ein namedtuple ist ein leichtgewichtiger, unveränderlicher Datenträger mit benannten Feldern.
from collections import namedtuple
Point = namedtuple('Point', ['x', 'y'])
p = Point(3, 4)
print(p.x, p.y)
Effizient und leserlich – eine gute Alternative zu kleinen Klassen.
Enum erlaubt es, symbolische Konstanten mit Namen zu versehen.
from enum import Enum
class Color(Enum):
RED = 1
GREEN = 2
BLUE = 3
Gleichbedeutend mit dem obigen Beispiel ist:
from enum import Enum
class Color(Enum):
RED = auto()
GREEN = auto()
BLUE = auto()
Enums verbessern die Lesbarkeit und Typsicherheit im Code.
Class Decorators modifizieren Klassen beim Erzeugen. Sie eignen sich für Registrierung, Debugging oder Vererbung.
def debug(cls):
original_init = cls.__init__
def new_init(self, *args, **kwargs):
print(f'Creating {cls.__name__} with {args}, {kwargs}')
original_init(self, *args, **kwargs)
cls.__init__ = new_init
return cls
@debug
class Product:
def __init__(self, name):
self.name = name
p = Product('Book')
Class Decorators sind ein mächtiges Meta-Programmierungstool.
Typprüfung und Instanzen
type(), isinstance() und issubclass() prüft man Objekttypen und Vererbungsbeziehungen.Objekt-Erzeugung
__new__ erzeugt das Objekt (besonders bei Immutable-Typen wichtig).__init__ initialisiert das Objekt nach der Erzeugung.Methodenarten
@staticmethod: Kein Zugriff auf Klassen- oder Instanzdaten.@classmethod: Zugriff auf die Klasse (cls).@property: Ermöglicht kontrollierten Zugriff auf Attribute wie bei einem Feld.Spezialmethoden (Dunder Methods)
__str__, __eq__, __getitem__ erlauben es, benutzerdefinierte Objekte wie eingebaute Typen zu behandeln.Abstraktion und Vererbung
ABC, @abstractmethod) erzwingen Implementierungen in Unterklassen.Iteration und Indexierung
__getitem__ und __setitem__ lassen sich Objekte wie Listen verwenden.Speicher- und Datenrepräsentation
@dataclass reduziert Boilerplate für Datenobjekte.
__slots__ spart Speicher durch festen Attributsatz.frozen=True macht die Dataclass unveränderbar.namedtuple ist eine kompakte, unveränderliche Datenstruktur mit Feldnamen.Enumerationen
Enum kann man symbolische Konstanten definieren, die lesbar und typsicher sind.Klassen-Dekoratoren