HOWTO · Python

Python-Klassenfabrik

Dieses Tutorial erklärt Klassenfabriken in Python.

Auf dieser Seite

Eine Klassenfabrik ist ein aufrufbares Objekt, das ein Klassenobjekt erstellt und zurückgibt. Der Aufruf der Fabrik erzeugt keine Instanz; er liefert eine Klasse, die anschließend instanziiert werden kann. Dieser Unterschied ist nützlich, wenn die Definition einer Klasse von Konfiguration abhängt, die erst während der Ausführung eines Programms verfügbar ist.

Die meisten Python-Programme sollten eine normale Klasse auf Modulebene definieren. Das ist die klarste Option, wenn die Klasse feststeht und auf eine vorhersehbare Weise verwendet wird. Verwenden Sie eine Fabrik, wenn ein Aufrufer eine angepasste Klasse benötigt, etwa mit einem ausgewählten Standardwert, einem generierten Namen oder Methoden, die aus einer kleinen Konfiguration zusammengesetzt werden. Halten Sie die Fabrik überschaubar: Viele nicht zusammenhängende Klassenformen können Typen schwer verständlich und testbar machen.

Dieses Tutorial zeigt eine verschachtelte class-Definition und die Variante von Pythons type() mit drei Argumenten. Beide Techniken geben ein Klassenobjekt zurück. Der Ansatz mit verschachtelter Klasse ist gewöhnlich leichter zu lesen; type() hilft, wenn Klassenname, Basisklassen oder Namensraum dynamisch bereitgestellt werden müssen.

So erstellen Sie eine Klassenfabrik in Python

Bei der üblichen Verwendung einer Klassenfabrik gibt es zwei Aufrufe. Rufen Sie zuerst die Fabrik auf und speichern Sie die zurückgegebene Klasse. Rufen Sie anschließend diese Klasse auf, um ein Objekt zu erzeugen. Beispielsweise weist Ball = create_ball_class() der Variablen Ball eine Klasse zu, während Ball("green") eine Ball-Instanz erstellt.

Das Schlüsselwort class erstellt die Klasse, wenn Python den Funktionskörper ausführt. Die Form type(name, bases, namespace) erstellt ebenfalls an dieser Stelle eine Klasse: name ist eine Zeichenkette, bases ist ein Tupel von Elternklassen und namespace ordnet Attributnamen Werte oder Funktionen zu. Funktionen im Namensraum werden zu Instanzmethoden, wenn auf sie über eine Instanz zugegriffen wird.

Wählen Sie Namen und Konstruktorargumente, die die zurückgegebene Klasse erwartbar machen. Wenn jeder Fabrikaufruf eine eigene Klasse erzeugt, haben Instanzen aus getrennten Aufrufen nicht dieselbe Klassenidentität, auch wenn ihre Definitionen gleich aussehen. Das kann für isinstance()-Prüfungen, Serialisierung und Fehlersuche wichtig sein. Benötigen Sie einen stabilen öffentlichen Typ, definieren Sie ihn einmal mit normaler Klassensyntax und übergeben Sie die Konfiguration an seine Instanzen.

Verwenden Sie das Schlüsselwort class, um eine Klassenfabrik in Python zu erstellen

Platzieren Sie eine Klassendefinition in einer Funktion und geben Sie den Klassennamen zurück. Die verschachtelte Klasse kann Werte aus dem Gültigkeitsbereich der Fabrik verwenden; dadurch eignet sich dieses Muster für kleine Konfigurationsentscheidungen. In diesem Beispiel gibt die Fabrik Ball zurück; die Instanz speichert die beim Konstruktor übergebene Farbe.

def create_ball_class():
    class Ball:
        def __init__(self, color):
            self.color = color

        def get_color(self):
            return self.color

    return Ball


Ball = create_ball_class()
ball = Ball("green")
print(ball.get_color())

Die zurückgegebene Klasse verhält sich wie jede andere Klasse; ihre Methoden und Attribute stehen daher auf der Instanz zur Verfügung.

green

Für eine Fabrik, die Konfiguration annimmt, validieren Sie diese vor der Rückgabe der Klasse oder im erzeugten Konstruktor. Durch die Validierung schlägt eine ungültige Eingabe an einer dokumentierten Grenze fehl, statt ein unbrauchbares Objekt weiterzugeben. Der folgende Grenzfall weist eine leere Farbe zurück und behandelt die Ausnahme, sodass das Programm eine deterministische Ausgabe hat.

def create_ball_class(default_color):
    class Ball:
        def __init__(self, color=default_color):
            if not color:
                raise ValueError("color must not be empty")
            self.color = color

    return Ball


Ball = create_ball_class("")
try:
    Ball()
except ValueError as error:
    print(error)
color must not be empty

Diese Fabrik schließt default_color ein, sodass jede zurückgegebene Klasse den Wert ihres jeweiligen Aufrufs speichert. Das ist für kleine, unveränderliche Einstellungen praktisch. Vermeiden Sie es, veränderliche gemeinsam genutzte Objekte einzuschließen, sofern ein gemeinsamer Zustand nicht beabsichtigt ist; andernfalls kann eine Instanz unerwartet eine andere beeinflussen. Gehört die Einstellung zu jeder Instanz, soll aber leicht prüfbar bleiben, ist auch ein Klassenattribut eine Möglichkeit.

Verwenden Sie die eingebaute Drei-Argumente-Form von type(), um eine Klassenfabrik in Python zu erstellen

Die eingebaute Funktion type() kann eine Klasse direkt zur Laufzeit erzeugen. Ihre Form mit drei Argumenten lautet type(name, bases, namespace). Das Namensraum-Wörterbuch enthält Klassenattribute; eine unter "get_color" gespeicherte Funktion wird beispielsweise als Instanzmethode verwendet. Ein direkter Aufruf ist knapp, wenn all diese Bestandteile bereits Daten sind, doch daneben definierte Hilfsfunktionen bleiben Namen auf Modulebene.

def init(self, color):
    self.color = color


def get_color(self):
    return self.color


Ball = type(
    "Ball",
    (object,),
    {"__init__": init, "get_color": get_color},
)
ball = Ball("green")
print(ball.get_color())
green

Kapseln Sie die Hilfsfunktionen in einer Fabrik, wenn sie Implementierungsdetails sind und die Fabrik eine konfigurierte Laufzeitklasse zurückgeben muss. Der folgende Code hat dasselbe beobachtbare Verhalten, während init und get_color auf den Gültigkeitsbereich der Fabrik beschränkt sind, statt zum Modulnamensraum hinzugefügt zu werden.

def create_ball_class(class_name):
    def init(self, color):
        self.color = color

    def get_color(self):
        return self.color

    return type(
        class_name,
        (object,),
        {"__init__": init, "get_color": get_color},
    )


Ball = create_ball_class("Ball")
ball = Ball("green")
print(Ball.__name__)
print(ball.get_color())
Ball
green

Verwenden Sie diesen Ansatz, wenn Name, Vererbungstupel oder Attributmenge tatsächlich datengesteuert sind. Für eine statische Klasse bietet die gewöhnliche class-Syntax bessere Lesbarkeit, Unterstützung durch Typprüfer und Tracebacks. Unabhängig vom gewählten Ansatz testen Sie die zurückgegebene Klasse, indem Sie eine Instanz erzeugen und das vom Aufrufer benötigte Verhalten ausführen, statt nur zu prüfen, ob die Fabrik einen Wert zurückgegeben hat.