Niedrig-Level Klassenerstellung

Beginner

This tutorial is from open-source community. Access the source code

Einführung

In diesem Lab werden Sie die grundlegenden Schritte kennenlernen, die beim Erstellen einer Klasse in Python involviert sind. Das Verständnis, wie Klassen mit der type()-Funktion konstruiert werden, bietet tiefere Einblicke in die objektorientierten Eigenschaften von Python.

Sie werden auch benutzerdefinierte Techniken zur Klassenerstellung implementieren. Die Dateien validate.py und structure.py werden während dieses Labs modifiziert, sodass Sie Ihr neues Wissen in einer praktischen Umgebung anwenden können.

Manuelle Klassenerstellung

In der Python-Programmierung sind Klassen ein grundlegendes Konzept, das es Ihnen ermöglicht, Daten und Funktionen zusammenzufassen. Normalerweise definieren wir Klassen mit der Standard-Python-Syntax. Hier ist beispielsweise eine einfache Stock-Klasse. Diese Klasse repräsentiert eine Aktie mit Attributen wie name, shares und price, und sie verfügt über Methoden zur Berechnung der Kosten und zum Verkauf von Aktien.

class Stock:
    def __init__(self, name, shares, price):
        self.name = name
        self.shares = shares
        self.price = price

    def cost(self):
        return self.shares * self.price

    def sell(self, nshares):
        self.shares -= nshares

Aber haben Sie sich jemals gefragt, wie Python eine Klasse tatsächlich im Hintergrund erstellt? Was wäre, wenn wir diese Klasse ohne die Standard-Klassen-Syntax erstellen wollten? In diesem Abschnitt werden wir untersuchen, wie Python-Klassen auf einer tieferen Ebene konstruiert werden.

Starten der interaktiven Python-Shell

Um mit der manuellen Klassenerstellung zu experimentieren, müssen wir eine interaktive Python-Shell öffnen. Diese Shell ermöglicht es uns, Python-Code Zeile für Zeile auszuführen, was ideal für das Lernen und Testen ist.

Öffnen Sie ein Terminal in der WebIDE und starten Sie die interaktive Python-Shell, indem Sie die folgenden Befehle eingeben. Der erste Befehl cd ~/project wechselt das aktuelle Verzeichnis in das Projektverzeichnis, und der zweite Befehl python3 startet die interaktive Python 3-Shell.

cd ~/project
python3

Definieren von Methoden als normale Funktionen

Bevor wir eine Klasse manuell erstellen, müssen wir die Methoden definieren, die Teil der Klasse sein sollen. In Python sind Methoden einfach Funktionen, die einer Klasse zugeordnet sind. Lassen Sie uns daher die Methoden, die wir in unserer Klasse möchten, als normale Python-Funktionen definieren.

def __init__(self, name, shares, price):
    self.name = name
    self.shares = shares
    self.price = price

def cost(self):
    return self.shares * self.price

def sell(self, nshares):
    self.shares -= nshares

Hier ist die __init__-Funktion eine spezielle Methode in Python-Klassen. Sie wird Konstruktor genannt und wird verwendet, um die Attribute eines Objekts zu initialisieren, wenn eine Instanz der Klasse erstellt wird. Die cost-Methode berechnet die Gesamtkosten der Aktien, und die sell-Methode verringert die Anzahl der Aktien.

Erstellen eines Methoden-Dictionaries

Nachdem wir unsere Methoden als normale Funktionen definiert haben, müssen wir sie so organisieren, dass Python sie beim Erstellen der Klasse verstehen kann. Wir tun dies, indem wir ein Dictionary erstellen, das alle Methoden für unsere Klasse enthält.

methods = {
    '__init__': __init__,
    'cost': cost,
    'sell': sell
}

In diesem Dictionary sind die Schlüssel die Namen der Methoden, wie sie in der Klasse verwendet werden, und die Werte sind die eigentlichen Funktionsobjekte, die wir zuvor definiert haben.

Verwenden des type()-Konstruktors zur Klassenerstellung

In Python ist die type()-Funktion eine eingebaute Funktion, die verwendet werden kann, um Klassen auf einer tieferen Ebene zu erstellen. Die type()-Funktion nimmt drei Argumente:

  1. Der Name der Klasse: Dies ist eine Zeichenkette, die den Namen der Klasse darstellt, die wir erstellen möchten.
  2. Ein Tupel von Basisklassen: In Python können Klassen von anderen Klassen erben. Hier verwenden wir (object,), was bedeutet, dass unsere Klasse von der Basisklasse object erbt, die die Basisklasse für alle Klassen in Python ist.
  3. Ein Dictionary, das Methoden und Attribute enthält: Dies ist das Dictionary, das wir zuvor erstellt haben und das alle Methoden unserer Klasse enthält.
Stock = type('Stock', (object,), methods)

Diese Codezeile erstellt eine neue Klasse namens Stock mit der type()-Funktion. Die Klasse erbt von der object-Klasse und verfügt über die in dem methods-Dictionary definierten Methoden.

Testen unserer manuell erstellten Klasse

Nachdem wir unsere Klasse manuell erstellt haben, lassen Sie uns sie testen, um sicherzustellen, dass sie wie erwartet funktioniert. Wir werden eine Instanz unserer neuen Klasse erstellen und ihre Methoden aufrufen.

s = Stock('GOOG', 100, 490.10)
print(s.name)
print(s.cost())
s.sell(25)
print(s.shares)

In der ersten Zeile erstellen wir eine Instanz der Stock-Klasse mit dem Namen GOOG, 100 Aktien und einem Preis von 490,10. Dann geben wir den Namen der Aktie aus, berechnen und geben die Kosten aus, verkaufen 25 Aktien und geben schließlich die verbleibende Anzahl der Aktien aus.

Sie sollten die folgende Ausgabe sehen:

GOOG
49010.0
75

Diese Ausgabe zeigt, dass unsere manuell erstellte Klasse genau wie eine Klasse funktioniert, die mit der Standard-Python-Syntax erstellt wurde. Sie zeigt, dass eine Klasse im Grunde nur ein Name, ein Tupel von Basisklassen und ein Dictionary von Methoden und Attributen ist. Die type()-Funktion konstruiert einfach ein Klassenobjekt aus diesen Komponenten.

Beenden Sie die Python-Shell, wenn Sie fertig sind:

exit()

Erstellen eines Hilfsprogramms für typisierte Strukturen

In diesem Schritt werden wir ein praktischeres Beispiel erstellen. Wir implementieren eine Funktion, die Klassen mit Typüberprüfung (type validation) erstellt. Typüberprüfung ist von entscheidender Bedeutung, da sie sicherstellt, dass die den Klassenattributen zugewiesenen Daten bestimmten Kriterien entsprechen, wie z. B. einem bestimmten Datentyp oder einem bestimmten Wertebereich. Dies hilft, Fehler frühzeitig zu erkennen und macht unseren Code robuster.

Verständnis der Structure-Klasse

Zunächst müssen wir die Datei structure.py im WebIDE-Editor öffnen. Diese Datei enthält eine grundlegende Structure-Klasse. Diese Klasse bietet die grundlegende Funktionalität zur Initialisierung und Darstellung von strukturierten Objekten. Initialisierung bedeutet, dass das Objekt mit den bereitgestellten Daten eingerichtet wird, und Darstellung bezieht sich darauf, wie das Objekt angezeigt wird, wenn wir es ausgeben.

Um die Datei zu öffnen, verwenden wir den folgenden Befehl im Terminal:

cd ~/project

Nachdem Sie diesen Befehl ausgeführt haben, befinden Sie sich im richtigen Verzeichnis, in dem sich die Datei structure.py befindet. Wenn Sie die Datei öffnen, werden Sie die grundlegende Structure-Klasse bemerken. Unser Ziel ist es, diese Klasse zu erweitern, um Typüberprüfung zu unterstützen.

Implementieren der typed_structure-Funktion

Jetzt fügen wir die typed_structure-Funktion zur Datei structure.py hinzu. Diese Funktion wird eine neue Klasse erstellen, die von der Structure-Klasse erbt und die angegebenen Validatoren enthält. Vererbung bedeutet, dass die neue Klasse alle Funktionen der Structure-Klasse hat und auch ihre eigenen Features hinzufügen kann. Validatoren werden verwendet, um zu überprüfen, ob die den Klassenattributen zugewiesenen Werte gültig sind.

Hier ist der Code für die typed_structure-Funktion:

def typed_structure(clsname, **validators):
    """
    Create a Structure class with type validation.

    Parameters:
    - clsname: Name of the class to create
    - validators: Keyword arguments mapping attribute names to validator objects

    Returns:
    - A new class with the specified name and validators
    """
    cls = type(clsname, (Structure,), validators)
    return cls

Der Parameter clsname ist der Name, den wir der neuen Klasse geben möchten. Der Parameter validators ist ein Dictionary, in dem die Schlüssel die Attributnamen und die Werte die Validator-Objekte sind. Die type()-Funktion wird verwendet, um eine neue Klasse dynamisch zu erstellen. Sie nimmt drei Argumente: den Klassennamen, ein Tupel von Basisklassen (in diesem Fall nur die Structure-Klasse) und ein Dictionary von Klassenattributen (die Validatoren).

Nachdem Sie diese Funktion hinzugefügt haben, sollte Ihre structure.py-Datei wie folgt aussehen:

## Structure class definition

class Structure:
    _fields = ()

    def __init__(self, *args, **kwargs):
        if len(args) > len(self._fields):
            raise TypeError(f'Expected {len(self._fields)} arguments')

        ## Set all of the positional arguments
        for name, value in zip(self._fields, args):
            setattr(self, name, value)

        ## Set the remaining keyword arguments
        for name, value in kwargs.items():
            setattr(self, name, value)

    def __repr__(self):
        attrs = ', '.join(f'{name}={getattr(self, name)!r}' for name in self._fields)
        return f'{type(self).__name__}({attrs})'

def typed_structure(clsname, **validators):
    """
    Create a Structure class with type validation.

    Parameters:
    - clsname: Name of the class to create
    - validators: Keyword arguments mapping attribute names to validator objects

    Returns:
    - A new class with the specified name and validators
    """
    cls = type(clsname, (Structure,), validators)
    return cls

Testen der typed_structure-Funktion

Lassen Sie uns unsere typed_structure-Funktion mit den Validatoren aus der Datei validate.py testen. Diese Validatoren werden verwendet, um zu überprüfen, ob die den Klassenattributen zugewiesenen Werte den richtigen Typ haben und anderen Kriterien entsprechen.

Zunächst öffnen wir eine interaktive Python-Shell. Wir verwenden die folgenden Befehle im Terminal:

cd ~/project
python3

Der erste Befehl bringt uns in das richtige Verzeichnis, und der zweite Befehl startet die interaktive Python-Shell.

Jetzt importieren wir die erforderlichen Komponenten und erstellen eine typisierte Struktur:

from validate import String, PositiveInteger, PositiveFloat
from structure import typed_structure

## Create a Stock class with type validation
Stock = typed_structure('Stock', name=String(), shares=PositiveInteger(), price=PositiveFloat())

## Create a stock instance
s = Stock('GOOG', 100, 490.1)

## Test the instance
print(s.name)
print(s)

## Test validation
try:
    invalid_stock = Stock('AAPL', -10, 150.25)  ## Should raise an error
except ValueError as e:
    print(f"Validation error: {e}")

Wir importieren die Validatoren String, PositiveInteger und PositiveFloat aus der Datei validate.py. Dann verwenden wir die typed_structure-Funktion, um eine Stock-Klasse mit Typüberprüfung zu erstellen. Wir erstellen eine Instanz der Stock-Klasse und testen sie, indem wir ihre Attribute ausgeben. Schließlich versuchen wir, eine ungültige Aktieninstanz zu erstellen, um die Überprüfung zu testen.

Sie sollten eine Ausgabe ähnlich der folgenden sehen:

GOOG
Stock('GOOG', 100, 490.1)
Validation error: Expected a positive value

Wenn Sie mit dem Testen fertig sind, beenden Sie die Python-Shell:

exit()

Dieses Beispiel zeigt, wie wir die type()-Funktion verwenden können, um benutzerdefinierte Klassen mit bestimmten Überprüfungsregeln zu erstellen. Dieser Ansatz ist sehr leistungsstark, da er es uns ermöglicht, Klassen programmgesteuert zu generieren, was viel Zeit sparen und unseren Code flexibler machen kann.

Effiziente Klassengenerierung

Nachdem Sie nun verstehen, wie Sie Klassen mit der type()-Funktion erstellen können, werden wir uns eine effizientere Methode ansehen, um mehrere ähnliche Klassen zu generieren. Diese Methode spart Ihnen Zeit und reduziert Code-Duplizierung, wodurch Ihr Programmierungsprozess reibungsloser wird.

Verständnis der bestehenden Validator-Klassen

Zunächst müssen wir die Datei validate.py im WebIDE öffnen. Diese Datei enthält bereits mehrere Validator-Klassen, die verwendet werden, um zu überprüfen, ob Werte bestimmten Bedingungen entsprechen. Zu diesen Klassen gehören Validator, Positive, PositiveInteger und PositiveFloat. Wir werden dieser Datei eine Basisklasse Typed und mehrere typspezifische Validatoren hinzufügen.

Um die Datei zu öffnen, führen Sie den folgenden Befehl im Terminal aus:

cd ~/project

Hinzufügen der Typed-Validator-Klasse

Beginnen wir damit, die Typed-Validator-Klasse hinzuzufügen. Diese Klasse wird verwendet, um zu überprüfen, ob ein Wert den erwarteten Typ hat.

class Typed(Validator):
    expected_type = object  ## Default, will be overridden in subclasses

    @classmethod
    def check(cls, value):
        if not isinstance(value, cls.expected_type):
            raise TypeError(f'Expected {cls.expected_type}')
        super().check(value)

In diesem Code ist expected_type standardmäßig auf object gesetzt. Subklassen werden dies mit dem spezifischen Typ überschreiben, den sie überprüfen. Die check-Methode verwendet die isinstance-Funktion, um zu überprüfen, ob der Wert den erwarteten Typ hat. Wenn nicht, wird ein TypeError ausgelöst.

Traditionell würden wir typspezifische Validatoren so erstellen:

class Integer(Typed):
    expected_type = int

class Float(Typed):
    expected_type = float

class String(Typed):
    expected_type = str

Dieser Ansatz ist jedoch wiederholend. Wir können es besser machen, indem wir den type()-Konstruktor verwenden, um diese Klassen dynamisch zu generieren.

Dynamische Generierung von Typ-Validatoren

Wir ersetzen die einzelnen Klassendefinitionen durch einen effizienteren Ansatz.

_typed_classes = [
    ('Integer', int),
    ('Float', float),
    ('String', str)
]

globals().update((name, type(name, (Typed,), {'expected_type': ty}))
                 for name, ty in _typed_classes)

Was dieser Code macht:

  1. Er definiert eine Liste von Tupeln. Jedes Tupel enthält einen Klassennamen und den entsprechenden Python-Typ.
  2. Er verwendet einen Generator-Ausdruck mit der type()-Funktion, um jede Klasse zu erstellen. Die type()-Funktion nimmt drei Argumente: den Klassennamen, ein Tupel von Basisklassen und ein Dictionary von Klassenattributen.
  3. Er verwendet globals().update(), um die neu erstellten Klassen in den globalen Namensraum hinzuzufügen. Dadurch sind die Klassen im gesamten Modul zugänglich.

Ihre fertige validate.py-Datei sollte in etwa so aussehen:

## Basic validator classes

class Validator:
    def __set_name__(self, owner, name):
        self.name = name

    def __get__(self, instance, owner):
        if instance is None:
            return self
        return instance.__dict__[self.name]

    def __set__(self, instance, value):
        self.check(value)
        instance.__dict__[self.name] = value

    @classmethod
    def check(cls, value):
        pass

class Positive(Validator):
    @classmethod
    def check(cls, value):
        if value <= 0:
            raise ValueError('Expected a positive value')
        super().check(value)

class PositiveInteger(Positive):
    @classmethod
    def check(cls, value):
        if not isinstance(value, int):
            raise TypeError('Expected an integer')
        super().check(value)

class PositiveFloat(Positive):
    @classmethod
    def check(cls, value):
        if not isinstance(value, float):
            raise TypeError('Expected a float')
        super().check(value)

class Typed(Validator):
    expected_type = object  ## Default, will be overridden in subclasses

    @classmethod
    def check(cls, value):
        if not isinstance(value, cls.expected_type):
            raise TypeError(f'Expected {cls.expected_type}')
        super().check(value)

## Generate type validators dynamically
_typed_classes = [
    ('Integer', int),
    ('Float', float),
    ('String', str)
]

globals().update((name, type(name, (Typed,), {'expected_type': ty}))
                 for name, ty in _typed_classes)

Testen der dynamisch generierten Klassen

Jetzt testen wir unsere dynamisch generierten Validator-Klassen. Zunächst öffnen wir eine interaktive Python-Shell.

cd ~/project
python3

Sobald Sie sich in der Python-Shell befinden, importieren und testen Sie unsere Validatoren.

from validate import Integer, Float, String

## Test the Integer validator
i = Integer()
i.__set_name__(None, 'test_int')
try:
    i.check("not an integer")
    print("Error: Check passed when it should have failed")
except TypeError as e:
    print(f"Integer validation: {e}")

## Test the String validator
s = String()
s.__set_name__(None, 'test_str')
try:
    s.check(123)
    print("Error: Check passed when it should have failed")
except TypeError as e:
    print(f"String validation: {e}")

## Add a new validator class to the list
import sys
print("Current validator classes:", [cls for cls in dir() if cls in ['Integer', 'Float', 'String']])

Sie sollten eine Ausgabe sehen, die die Typüberprüfungsfehler anzeigt. Dies zeigt, dass unsere dynamisch generierten Klassen korrekt funktionieren.

Wenn Sie mit dem Testen fertig sind, beenden Sie die Python-Shell:

exit()

Erweitern der dynamischen Klassengenerierung

Wenn Sie mehr Typ-Validatoren hinzufügen möchten, können Sie einfach die _typed_classes-Liste in validate.py aktualisieren.

_typed_classes = [
    ('Integer', int),
    ('Float', float),
    ('String', str),
    ('List', list),
    ('Dict', dict),
    ('Bool', bool)
]

Dieser Ansatz bietet eine leistungsstarke und effiziente Möglichkeit, mehrere ähnliche Klassen zu generieren, ohne wiederholenden Code zu schreiben. Er ermöglicht es Ihnen, Ihre Anwendung einfach zu skalieren, wenn sich Ihre Anforderungen ändern.

Zusammenfassung

In diesem Lab haben Sie die niedrig - level Mechanismen der Klassenerstellung in Python kennengelernt. Zunächst haben Sie gelernt, wie Sie eine Klasse manuell mit dem type() - Konstruktor erstellen können, der einen Klassennamen, ein Tupel von Basisklassen und ein Dictionary von Methoden erfordert. Zweitens haben Sie eine typed_structure - Funktion implementiert, um Klassen mit Überprüfungskapazitäten dynamisch zu erstellen.

Darüber hinaus haben Sie den type() - Konstruktor zusammen mit globals().update() verwendet, um mehrere ähnliche Klassen effizient zu generieren und so wiederholenden Code zu vermeiden. Diese Techniken bieten leistungsstarke Möglichkeiten, Klassen programmgesteuert zu erstellen und anzupassen, was in Frameworks, Bibliotheken und Metaprogrammierung nützlich ist. Das Verständnis dieser zugrunde liegenden Mechanismen vertieft Ihr Verständnis der objektorientierten Features von Python und ermöglicht fortgeschrittene Programmierung.