Klassendekoratoren kennenlernen

Beginner

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

Einleitung

In diesem Lab lernen Sie Klassendekoratoren in Python kennen und wiederholen und erweitern Python-Deskriptoren. Durch die Kombination dieser Konzepte können Sie leistungsstarke und saubere Code-Strukturen erstellen.

In diesem Lab bauen Sie auf früheren Deskriptorkonzepten auf und erweitern diese mithilfe von Klassendekoratoren. Diese Kombination ermöglicht es Ihnen, saubereren, wartbareren Code mit erweiterten Validierungsfunktionen zu erstellen. Die zu modifizierenden Dateien sind validate.py und structure.py.

Implementierung der Typüberprüfung mit Deskriptoren

In diesem Schritt erstellen wir eine Stock-Klasse, die Deskriptoren zur Typüberprüfung verwendet. Aber zuerst wollen wir verstehen, was Deskriptoren sind. Deskriptoren sind ein wirklich mächtiges Feature in Python. Sie geben Ihnen die Kontrolle darüber, wie auf Attribute in Klassen zugegriffen wird.

Deskriptoren sind Objekte, die definieren, wie auf Attribute anderer Objekte zugegriffen wird. Dies geschieht durch die Implementierung spezieller Methoden wie __get__, __set__ und __delete__. Diese Methoden ermöglichen es Deskriptoren, den Abruf, die Zuweisung und das Löschen von Attributen zu verwalten. Deskriptoren sind sehr nützlich für die Implementierung von Validierung, Typüberprüfung und berechneten Eigenschaften. Sie können beispielsweise einen Deskriptor verwenden, um sicherzustellen, dass ein Attribut immer eine positive Zahl oder ein String eines bestimmten Formats ist.

Die Datei validate.py enthält bereits Validator-Klassen (String, PositiveInteger, PositiveFloat). Wir können diese Klassen verwenden, um die Attribute unserer Stock-Klasse zu validieren.

Nun erstellen wir unsere Stock-Klasse mit Deskriptoren.

  1. Öffnen Sie zuerst die Datei stock.py in Ihrem Editor.

  2. Sobald die Datei geöffnet ist, ersetzen Sie den Platzhalterinhalt durch den folgenden Code:

## stock.py

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

class Stock(Structure):
    _fields = ('name', 'shares', 'price')
    name = String()
    shares = PositiveInteger()
    price = PositiveFloat()

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

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

## Create an __init__ method based on _fields
Stock.create_init()

Lassen Sie uns aufschlüsseln, was dieser Code tut. Das _fields-Tupel definiert die Attribute der Stock-Klasse. Dies sind die Namen der Attribute, die unsere Stock-Objekte haben werden.

Die Attribute name, shares und price sind als Deskriptorobjekte definiert. Der String()-Deskriptor stellt sicher, dass das name-Attribut ein String ist. Der PositiveInteger()-Deskriptor stellt sicher, dass das shares-Attribut eine positive Ganzzahl ist. Und der PositiveFloat()-Deskriptor garantiert, dass das price-Attribut eine positive Gleitkommazahl ist.

Die cost-Eigenschaft ist eine berechnete Eigenschaft. Sie berechnet die Gesamtkosten des Stocks basierend auf der Anzahl der Aktien und dem Preis pro Aktie.

Die sell-Methode wird verwendet, um die Anzahl der Aktien zu reduzieren. Wenn Sie diese Methode mit einer zu verkaufenden Aktienanzahl aufrufen, zieht sie diese Anzahl vom shares-Attribut ab.

Die Zeile Stock.create_init() erstellt dynamisch eine __init__-Methode für unsere Klasse. Diese Methode ermöglicht es uns, Stock-Objekte zu erstellen, indem wir die Werte für die Attribute name, shares und price übergeben.

  1. Nachdem Sie den Code hinzugefügt haben, speichern Sie die Datei. Dadurch wird sichergestellt, dass Ihre Änderungen gespeichert werden und bei der Ausführung der Tests verwendet werden können.

  2. Führen wir nun die Tests aus, um Ihre Implementierung zu überprüfen. Wechseln Sie zuerst mit dem folgenden Befehl in das Verzeichnis ~/project:

cd ~/project

Führen Sie dann die Tests mit dem folgenden Befehl aus:

python3 teststock.py

Wenn Ihre Implementierung korrekt ist, sollten Sie eine Ausgabe ähnlich dieser sehen:

.........
----------------------------------------------------------------------
Ran 9 tests in 0.001s

OK

Diese Ausgabe bedeutet, dass alle Tests bestanden wurden. Die Deskriptoren validieren erfolgreich die Typen jedes Attributs!

Versuchen wir, ein Stock-Objekt im Python-Interpreter zu erstellen. Stellen Sie zunächst sicher, dass Sie sich im Verzeichnis ~/project befinden. Führen Sie dann den folgenden Befehl aus:

cd ~/project
python3 -c "from stock import Stock; s = Stock('GOOG', 100, 490.1); print(s); print(f'Cost: {s.cost}')"

Sie sollten die folgende Ausgabe sehen:

Stock('GOOG', 100, 490.1)
Cost: 49010.0

Sie haben erfolgreich Deskriptoren für die Typüberprüfung implementiert! Lassen Sie uns diesen Code nun weiter verbessern.

Erstellung eines Klassendekorators für die Validierung

Im vorherigen Schritt funktionierte unsere Implementierung, aber es gab eine Redundanz. Wir mussten sowohl das _fields-Tupel als auch die Deskriptorattribute angeben. Das ist nicht sehr effizient und wir können es verbessern. In Python sind Klassendekoratoren ein mächtiges Werkzeug, das uns helfen kann, diesen Prozess zu vereinfachen. Ein Klassendekorator ist eine Funktion, die eine Klasse als Argument nimmt, sie auf eine bestimmte Weise modifiziert und dann die modifizierte Klasse zurückgibt. Durch die Verwendung eines Klassendekorators können wir Feldinformationen automatisch aus den Deskriptoren extrahieren, was unseren Code sauberer und wartbarer macht.

Erstellen wir einen Klassendekorator, um unseren Code zu vereinfachen. Hier sind die Schritte, die Sie befolgen müssen:

  1. Öffnen Sie zuerst die Datei structure.py in Ihrem Editor.

  2. Fügen Sie als Nächstes den folgenden Code am Anfang der Datei structure.py hinzu, direkt nach allen Importanweisungen. Dieser Code definiert unseren Klassendekorator:

from validate import Validator

def validate_attributes(cls):
    """
    Class decorator that extracts Validator instances
    and builds the _fields list automatically
    """
    validators = []
    for name, val in vars(cls).items():
        if isinstance(val, Validator):
            validators.append(val)

    ## Set _fields based on validator names
    cls._fields = [val.name for val in validators]

    ## Create initialization method
    cls.create_init()

    return cls

Lassen Sie uns aufschlüsseln, was dieser Dekorator tut:

  • Er erstellt zuerst eine leere Liste namens validators. Dann durchläuft er alle Attribute der Klasse mit vars(cls).items(). Wenn ein Attribut eine Instanz der Validator-Klasse ist, fügt er dieses Attribut der Liste validators hinzu.
  • Danach setzt er das Attribut _fields der Klasse. Er erstellt eine Liste von Namen aus den Validatoren in der Liste validators und weist sie cls._fields zu.
  • Schließlich ruft er die Methode create_init() der Klasse auf, um die __init__-Methode zu generieren, und gibt dann die modifizierte Klasse zurück.
  1. Nachdem Sie den Code hinzugefügt haben, speichern Sie die Datei structure.py. Das Speichern der Datei stellt sicher, dass Ihre Änderungen erhalten bleiben.

  2. Nun müssen wir unsere Datei stock.py ändern, um diesen neuen Dekorator zu verwenden. Öffnen Sie die Datei stock.py in Ihrem Editor.

  3. Aktualisieren Sie die Datei stock.py, um den validate_attributes-Dekorator zu verwenden. Ersetzen Sie den vorhandenen Code durch Folgendes:

## stock.py

from structure import Structure, validate_attributes
from validate import String, PositiveInteger, PositiveFloat

@validate_attributes
class Stock(Structure):
    name = String()
    shares = PositiveInteger()
    price = PositiveFloat()

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

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

Beachten Sie die Änderungen, die wir vorgenommen haben:

  • Wir haben den @validate_attributes-Dekorator direkt über der Stock-Klassendefinition hinzugefügt. Dies weist Python an, den validate_attributes-Dekorator auf die Stock-Klasse anzuwenden.
  • Wir haben die explizite _fields-Deklaration entfernt, da der Dekorator dies automatisch übernimmt.
  • Wir haben auch den Aufruf von Stock.create_init() entfernt, da der Dekorator die Erstellung der __init__-Methode übernimmt.

Dadurch ist die Klasse nun einfacher und sauberer. Der Dekorator kümmert sich um alle Details, die wir zuvor manuell erledigt haben.

  1. Nachdem Sie diese Änderungen vorgenommen haben, müssen wir überprüfen, ob alles weiterhin wie erwartet funktioniert. Führen Sie die Tests erneut mit den folgenden Befehlen aus:
cd ~/project
python3 teststock.py

Wenn alles korrekt funktioniert, sollten Sie die folgende Ausgabe sehen:

.........
----------------------------------------------------------------------
Ran 9 tests in 0.001s

OK

Diese Ausgabe zeigt an, dass alle Tests erfolgreich bestanden wurden.

Testen wir unsere Stock-Klasse auch interaktiv. Führen Sie den folgenden Befehl im Terminal aus:

cd ~/project
python3 -c "from stock import Stock; s = Stock('GOOG', 100, 490.1); print(s); print(f'Cost: {s.cost}')"

Sie sollten die folgende Ausgabe sehen:

Stock('GOOG', 100, 490.1)
Cost: 49010.0

Großartig! Sie haben erfolgreich einen Klassendekorator implementiert, der unseren Code vereinfacht, indem er automatisch Felddeklarationen und Initialisierungen übernimmt. Dies macht unseren Code effizienter und einfacher zu warten.

Anwenden von Dekoratoren durch Vererbung

In Schritt 2 haben wir einen Klassendekorator erstellt, der unseren Code vereinfacht. Ein Klassendekorator ist ein spezieller Funktionstyp, der eine Klasse als Argument nimmt und eine modifizierte Klasse zurückgibt. Es ist ein nützliches Werkzeug in Python, um Klassen Funktionalität hinzuzufügen, ohne ihren ursprünglichen Code zu ändern. Wir müssen jedoch weiterhin explizit den @validate_attributes-Dekorator auf jede Klasse anwenden. Das bedeutet, dass wir jedes Mal, wenn wir eine neue Klasse erstellen, die eine Validierung benötigt, daran denken müssen, diesen Dekorator hinzuzufügen, was etwas umständlich sein kann.

Wir können dies weiter verbessern, indem wir den Dekorator automatisch durch Vererbung anwenden. Vererbung ist ein grundlegendes Konzept in der objektorientierten Programmierung, bei dem eine Unterklasse Attribute und Methoden von einer Oberklasse erben kann. Die __init_subclass__-Methode von Python wurde in Python 3.6 eingeführt, um Oberklassen die Anpassung der Initialisierung von Unterklassen zu ermöglichen. Das bedeutet, dass die Oberklasse einige Aktionen auf die Unterklasse ausführen kann, wenn diese erstellt wird. Wir können diese Funktion nutzen, um unseren Dekorator automatisch auf jede Klasse anzuwenden, die von Structure erbt.

Lassen Sie uns dies implementieren:

  1. Öffnen Sie die Datei structure.py in Ihrem Editor. Diese Datei enthält die Definition der Structure-Klasse, und wir werden sie ändern, um die __init_subclass__-Methode zu verwenden.

  2. Fügen Sie die __init_subclass__-Methode zur Structure-Klasse hinzu:

class Structure:
    _fields = ()
    _types = ()

    def __init__(self, *args):
        if len(args) != len(self._fields):
            raise TypeError(f'Expected {len(self._fields)} arguments')
        for name, val in zip(self._fields, args):
            setattr(self, name, val)

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

    @classmethod
    def create_init(cls):
        '''
        Create an __init__ method from _fields
        '''
        body = 'def __init__(self, %s):\n' % ', '.join(cls._fields)
        for name in cls._fields:
            body += f'    self.{name} = {name}\n'

        ## Execute the function creation code
        namespace = {}
        exec(body, namespace)
        setattr(cls, '__init__', namespace['__init__'])

    @classmethod
    def __init_subclass__(cls):
        validate_attributes(cls)

Die __init_subclass__-Methode ist eine Klassenmethode, was bedeutet, dass sie auf der Klasse selbst und nicht auf einer Instanz der Klasse aufgerufen werden kann. Wenn eine Unterklasse von Structure erstellt wird, wird diese Methode automatisch aufgerufen. Innerhalb dieser Methode rufen wir den validate_attributes-Dekorator auf der Unterklasse cls auf. Auf diese Weise erhalten alle Unterklassen von Structure automatisch das Validierungsverhalten.

  1. Speichern Sie die Datei.

Nachdem Sie Änderungen an der Datei structure.py vorgenommen haben, müssen wir sie speichern, damit die Änderungen angewendet werden.

  1. Aktualisieren wir nun unsere Datei stock.py, um diese neue Funktion zu nutzen. Öffnen Sie die Datei stock.py in Ihrem Editor, um sie zu ändern. Diese Datei enthält die Definition der Stock-Klasse, und wir werden sie von der Structure-Klasse erben lassen, um die automatische Anwendung des Dekorators zu nutzen.

  2. Ändern Sie die Datei stock.py, um den expliziten Dekorator zu entfernen:

## stock.py

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

class Stock(Structure):
    name = String()
    shares = PositiveInteger()
    price = PositiveFloat()

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

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

Beachten Sie, dass wir:

  • Den Import von validate_attributes entfernt haben, da wir ihn nicht mehr explizit importieren müssen, da der Dekorator automatisch durch Vererbung angewendet wird.
  • Den @validate_attributes-Dekorator entfernt haben, da die __init_subclass__-Methode in der Structure-Klasse für die Anwendung zuständig ist.
  • Der Code verlässt sich nun ausschließlich auf die Vererbung von Structure, um das Validierungsverhalten zu erhalten.
  1. Führen Sie die Tests erneut aus, um zu überprüfen, ob alles noch funktioniert:
cd ~/project
python3 teststock.py

Das Ausführen der Tests ist wichtig, um sicherzustellen, dass unsere Änderungen nichts kaputt gemacht haben. Wenn alle Tests bestanden werden, bedeutet dies, dass die automatische Anwendung des Dekorators durch Vererbung korrekt funktioniert.

Sie sollten sehen, dass alle Tests bestanden werden:

.........
----------------------------------------------------------------------
Ran 9 tests in 0.001s

OK

Testen wir unsere Stock-Klasse erneut, um sicherzustellen, dass sie wie erwartet funktioniert:

cd ~/project
python3 -c "from stock import Stock; s = Stock('GOOG', 100, 490.1); print(s); print(f'Cost: {s.cost}')"

Dieser Befehl erstellt eine Instanz der Stock-Klasse und gibt ihre Darstellung und die Kosten aus. Wenn die Ausgabe wie erwartet ist, bedeutet dies, dass die Stock-Klasse mit der automatischen Dekoratoranwendung korrekt funktioniert.

Ausgabe:

Stock('GOOG', 100, 490.1)
Cost: 49010.0

Diese Implementierung ist noch sauberer! Durch die Verwendung von __init_subclass__ haben wir die Notwendigkeit eliminiert, Dekoratoren explizit anzuwenden. Jede Klasse, die von Structure erbt, erhält automatisch das Validierungsverhalten.

Hinzufügen von Zeilenkonvertierungsfunktionalität

In der Programmierung ist es oft nützlich, Instanzen einer Klasse aus Datenzeilen zu erstellen, insbesondere wenn mit Daten aus Quellen wie CSV-Dateien gearbeitet wird. In diesem Abschnitt fügen wir die Möglichkeit hinzu, Instanzen der Structure-Klasse aus Datenzeilen zu erstellen. Dies tun wir, indem wir eine from_row-Klassenmethode in der Structure-Klasse implementieren.

  1. Öffnen Sie zuerst die Datei structure.py in Ihrem Editor. Hier werden wir unsere Codeänderungen vornehmen.

  2. Als Nächstes ändern wir die Funktion validate_attributes. Diese Funktion ist ein Klassendekorator, der Validator-Instanzen extrahiert und die Listen _fields und _types automatisch erstellt. Wir werden sie aktualisieren, um auch Typinformationen zu sammeln.

def validate_attributes(cls):
    """
    Class decorator that extracts Validator instances
    and builds the _fields and _types lists automatically
    """
    validators = []
    for name, val in vars(cls).items():
        if isinstance(val, Validator):
            validators.append(val)

    ## Set _fields based on validator names
    cls._fields = [val.name for val in validators]

    ## Set _types based on validator expected_types
    cls._types = [getattr(val, 'expected_type', lambda x: x) for val in validators]

    ## Create initialization method
    cls.create_init()

    return cls

In dieser aktualisierten Funktion sammeln wir das Attribut expected_type von jedem Validator und speichern es in der Klassenvariable _types. Dies wird später nützlich sein, wenn wir Daten aus Zeilen in die richtigen Typen konvertieren.

  1. Nun fügen wir die from_row-Klassenmethode zur Structure-Klasse hinzu. Diese Methode ermöglicht es uns, eine Instanz der Klasse aus einer Datenzeile zu erstellen, die eine Liste oder ein Tupel sein kann.
@classmethod
def from_row(cls, row):
    """
    Create an instance from a data row (list or tuple)
    """
    rowdata = [func(val) for func, val in zip(cls._types, row)]
    return cls(*rowdata)

So funktioniert diese Methode:

  • Sie nimmt eine Datenzeile entgegen, die in Form einer Liste oder eines Tupels vorliegen kann.
  • Sie konvertiert jeden Wert in der Zeile mithilfe der entsprechenden Funktion aus der Liste _types in den erwarteten Typ.
  • Anschließend erstellt und gibt sie eine neue Instanz der Klasse mit den konvertierten Werten zurück.
  1. Nachdem Sie diese Änderungen vorgenommen haben, speichern Sie die Datei structure.py. Dies stellt sicher, dass Ihre Codeänderungen erhalten bleiben.

  2. Testen wir unsere from_row-Methode, um sicherzustellen, dass sie wie erwartet funktioniert. Wir erstellen einen einfachen Test mit der Stock-Klasse. Führen Sie den folgenden Befehl in Ihrem Terminal aus:

cd ~/project
python3 -c "from stock import Stock; s = Stock.from_row(['GOOG', '100', '490.1']); print(s); print(f'Cost: {s.cost}')"

Sie sollten eine Ausgabe ähnlich dieser sehen:

Stock('GOOG', 100, 490.1)
Cost: 49010.0

Beachten Sie, dass die Zeichenkettenwerte '100' und '490.1' automatisch in die richtigen Typen (Integer und Float) konvertiert wurden. Dies zeigt, dass unsere from_row-Methode korrekt funktioniert.

  1. Zum Schluss versuchen wir, Daten aus einer CSV-Datei mit unserem reader.py-Modul zu lesen. Führen Sie den folgenden Befehl in Ihrem Terminal aus:
cd ~/project
python3 -c "from stock import Stock; import reader; portfolio = reader.read_csv_as_instances('portfolio.csv', Stock); print(portfolio); print(f'Total value: {sum(s.cost for s in portfolio)}')"

Sie sollten eine Ausgabe sehen, die die Aktien aus der CSV-Datei anzeigt:

[Stock('GOOG', 100, 490.1), Stock('AAPL', 50, 545.75), Stock('MSFT', 200, 30.47)]
Total value: 82391.5

Die from_row-Methode ermöglicht es uns, CSV-Daten einfach in Instanzen der Stock-Klasse zu konvertieren. In Kombination mit der Funktion read_csv_as_instances haben wir eine leistungsstarke Möglichkeit, strukturierte Daten zu laden und damit zu arbeiten.

Hinzufügen von Validierungsfunktionalität für Methodenargumente

In Python ist die Validierung von Daten ein wichtiger Bestandteil der Erstellung robuster Codes. In diesem Abschnitt erweitern wir unsere Validierung, indem wir Methodenargumente automatisch validieren. Die Datei validate.py enthält bereits einen @validated-Dekorator. Ein Dekorator in Python ist eine spezielle Funktion, die eine andere Funktion modifizieren kann. Der @validated-Dekorator hier kann Funktionsargumente anhand ihrer Annotationen überprüfen. Annotationen in Python sind eine Möglichkeit, Metadaten zu Funktionsparametern und Rückgabewerten hinzuzufügen.

Lassen Sie uns unseren Code ändern, um diesen Dekorator auf Methoden mit Annotationen anzuwenden:

  1. Zuerst müssen wir verstehen, wie der validated-Dekorator funktioniert. Öffnen Sie die Datei validate.py in Ihrem Editor, um sie zu überprüfen.

Der validated-Dekorator verwendet Funktionsannotationen zur Validierung von Argumenten. Bevor die Funktion ausgeführt werden kann, erstellt er für jeden annotierten Parameter eine Instanz der Validator-Klasse und ruft die validate-Methode auf, um das Argument zu überprüfen. Wenn ein Argument beispielsweise mit PositiveInteger annotiert ist, erstellt der Dekorator eine PositiveInteger-Instanz und validiert, dass der übergebene Wert tatsächlich eine positive Ganzzahl ist. Wenn die Validierung fehlschlägt, sammelt er alle Fehler und löst einen TypeError mit detaillierten Fehlermeldungen aus.

  1. Nun ändern wir die Funktion validate_attributes in structure.py, um annotierte Methoden mit dem validated-Dekorator zu umschließen. Das bedeutet, dass jede Methode mit Annotationen in der Klasse ihre Argumente automatisch validiert bekommt. Öffnen Sie die Datei structure.py in Ihrem Editor.

  2. Aktualisieren Sie die Funktion validate_attributes:

def validate_attributes(cls):
    """
    Class decorator that:
    1. Extracts Validator instances and builds _fields and _types lists
    2. Applies @validated decorator to methods with annotations
    """
    ## Import the validated decorator
    from validate import validated

    ## Process validator descriptors
    validators = []
    for name, val in vars(cls).items():
        if isinstance(val, Validator):
            validators.append(val)

    ## Set _fields based on validator names
    cls._fields = [val.name for val in validators]

    ## Set _types based on validator expected_types
    cls._types = [getattr(val, 'expected_type', lambda x: x) for val in validators]

    ## Apply @validated decorator to methods with annotations
    for name, val in vars(cls).items():
        if callable(val) and hasattr(val, '__annotations__'):
            setattr(cls, name, validated(val))

    ## Create initialization method
    cls.create_init()

    return cls

Diese aktualisierte Funktion führt nun Folgendes aus:

  1. Sie verarbeitet wie bisher Validator-Deskriptoren. Validator-Deskriptoren werden verwendet, um Validierungsregeln für Klassenattribute zu definieren.

  2. Sie findet alle Methoden mit Annotationen in der Klasse. Annotationen werden zu Methodenparametern hinzugefügt, um den erwarteten Typ des Arguments anzugeben.

  3. Sie wendet den @validated-Dekorator auf diese Methoden an. Dies stellt sicher, dass die an diese Methoden übergebenen Argumente gemäß ihren Annotationen validiert werden.

  4. Speichern Sie die Datei nach diesen Änderungen. Das Speichern der Datei ist wichtig, da es sicherstellt, dass unsere Änderungen gespeichert und später verwendet werden können.

  5. Aktualisieren wir nun die sell-Methode in der Stock-Klasse, um eine Annotation hinzuzufügen. Annotationen helfen bei der Angabe des erwarteten Typs des Arguments, der vom @validated-Dekorator zur Validierung verwendet wird. Öffnen Sie die Datei stock.py in Ihrem Editor.

  6. Ändern Sie die sell-Methode, um eine Typannotation hinzuzufügen:

## stock.py

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

class Stock(Structure):
    name = String()
    shares = PositiveInteger()
    price = PositiveFloat()

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

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

Die wichtige Änderung ist das Hinzufügen von : PositiveInteger zum Parameter nshares. Dies teilt Python (und unserem @validated-Dekorator) mit, dieses Argument mit dem PositiveInteger-Validator zu validieren. Wenn wir also die sell-Methode aufrufen, muss das Argument nshares eine positive Ganzzahl sein.

  1. Führen Sie die Tests erneut aus, um zu überprüfen, ob alles noch funktioniert. Das Ausführen von Tests ist eine gute Möglichkeit, sicherzustellen, dass unsere Änderungen keine bestehende Funktionalität beeinträchtigt haben.
cd ~/project
python3 teststock.py

Sie sollten sehen, dass alle Tests bestanden werden:

.........
----------------------------------------------------------------------
Ran 9 tests in 0.001s

OK
  1. Testen wir unsere neue Argumentvalidierung. Wir werden versuchen, die sell-Methode mit gültigen und ungültigen Argumenten aufzurufen, um zu sehen, ob die Validierung wie erwartet funktioniert.
cd ~/project
python3 -c "
from stock import Stock
s = Stock('GOOG', 100, 490.1)
s.sell(25)
print(s)
try:
    s.sell(-25)
except Exception as e:
    print(f'Error: {e}')
"

Sie sollten eine Ausgabe ähnlich der folgenden sehen:

Stock('GOOG', 75, 490.1)
Error: Bad Arguments
  nshares: nshares must be >= 0

Dies zeigt, dass unsere Methodenargumentvalidierung funktioniert! Der erste Aufruf von sell(25) ist erfolgreich, da 25 eine positive Ganzzahl ist. Der zweite Aufruf von sell(-25) schlägt jedoch fehl, da -25 keine positive Ganzzahl ist.

Sie haben nun ein vollständiges System implementiert für:

  1. Validierung von Klassenattributen mithilfe von Deskriptoren. Deskriptoren werden verwendet, um Validierungsregeln für Klassenattribute zu definieren.
  2. Automatische Sammlung von Feldinformationen mithilfe von Klassendekoratoren. Klassendekoratoren können das Verhalten einer Klasse modifizieren, z. B. die Sammlung von Feldinformationen.
  3. Konvertierung von Zeilendaten in Instanzen. Dies ist nützlich, wenn mit Daten aus externen Quellen gearbeitet wird.
  4. Validierung von Methodenargumenten mithilfe von Annotationen. Annotationen helfen bei der Angabe des erwarteten Typs des Arguments für die Validierung.

Dies demonstriert die Leistungsfähigkeit der Kombination von Deskriptoren und Dekoratoren in Python, um ausdrucksstarke, sich selbst validierende Klassen zu erstellen.

Zusammenfassung

In diesem Lab haben Sie gelernt, wie Sie leistungsstarke Python-Funktionen kombinieren, um sauberen, sich selbst validierenden Code zu erstellen. Sie haben Schlüsselkonzepte gemeistert, wie die Verwendung von Deskriptoren zur Attributvalidierung, die Erstellung von Klassendekoratoren zur Automatisierung der Codeerstellung und die automatische Anwendung von Dekoratoren durch Vererbung.

Diese Techniken sind leistungsstarke Werkzeuge zur Erstellung robuster und wartbarer Python-Codes. Sie ermöglichen es Ihnen, Validierungsanforderungen klar auszudrücken und sie in Ihrer gesamten Codebasis durchzusetzen. Sie können diese Muster nun in Ihren eigenen Python-Projekten anwenden, um die Codequalität zu verbessern und Boilerplate-Code zu reduzieren.