Wie man Design by Contract in Python implementiert

PythonPythonBeginner
Jetzt üben

💡 Dieser Artikel wurde von AI-Assistenten übersetzt. Um die englische Version anzuzeigen, können Sie hier klicken

Einführung

Dieses Tutorial führt Sie durch den Prozess der Implementierung von Design by Contract (Entwurf durch Vertrag) in Python, einer Programmiersprache, die dabei hilft, die Zuverlässigkeit und Wartbarkeit des Codes sicherzustellen. Wir werden die Kernprinzipien von Design by Contract untersuchen und uns praktische Beispiele und Anwendungsfälle für Ihre Python - Projekte ansehen.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL python(("Python")) -.-> python/ObjectOrientedProgrammingGroup(["Object-Oriented Programming"]) python/ObjectOrientedProgrammingGroup -.-> python/classes_objects("Classes and Objects") python/ObjectOrientedProgrammingGroup -.-> python/constructor("Constructor") python/ObjectOrientedProgrammingGroup -.-> python/inheritance("Inheritance") python/ObjectOrientedProgrammingGroup -.-> python/polymorphism("Polymorphism") python/ObjectOrientedProgrammingGroup -.-> python/encapsulation("Encapsulation") subgraph Lab Skills python/classes_objects -.-> lab-398022{{"Wie man Design by Contract in Python implementiert"}} python/constructor -.-> lab-398022{{"Wie man Design by Contract in Python implementiert"}} python/inheritance -.-> lab-398022{{"Wie man Design by Contract in Python implementiert"}} python/polymorphism -.-> lab-398022{{"Wie man Design by Contract in Python implementiert"}} python/encapsulation -.-> lab-398022{{"Wie man Design by Contract in Python implementiert"}} end

Einführung in Design by Contract

Design by Contract (DbC) ist eine Software - Engineering - Methode, die die formale Spezifikation des Verhaltens von Softwarekomponenten durch die Verwendung von Verträgen betont. Ein Vertrag ist eine formale Vereinbarung zwischen einem Client (dem Aufrufer einer Funktion oder Methode) und einem Anbieter (der Implementierung der Funktion oder Methode), die die Rechte und Pflichten beider Parteien festlegt.

Die Schlüsselprinzipien von Design by Contract sind:

Vorbedingungen (Preconditions)

Vorbedingungen sind die Anforderungen, die der Client erfüllen muss, bevor er eine Funktion oder Methode aufruft. Sie definieren den gültigen Eingabebereich, den Zustand des Systems und alle anderen notwendigen Bedingungen für die korrekte Ausführung der Funktion oder Methode.

Nachbedingungen (Postconditions)

Nachbedingungen sind die Garantien, die der Anbieter dem Client nach der Ausführung der Funktion oder Methode gibt. Sie definieren die erwartete Ausgabe, den Zustand des Systems und alle anderen Eigenschaften, die nach Abschluss der Funktion oder Methode wahr sein werden.

Klasseninvarianten (Class Invariants)

Klasseninvarianten sind Bedingungen, die für alle Instanzen einer Klasse sowohl vor als auch nach der Ausführung jeder öffentlichen Methode wahr sein müssen. Sie gewährleisten die Gesamtkonsistenz und Gültigkeit des Zustands der Klasse.

Durch die Definition dieser Verträge können sowohl der Client als auch der Anbieter ein klares Verständnis des erwarteten Verhaltens der Softwarekomponenten haben, was zu robusterem, zuverlässigerem und wartbarerem Code führen kann.

graph LR A[Client] -- Preconditions --> B[Function/Method] B -- Postconditions --> A B -- Class Invariants --> B

Im nächsten Abschnitt werden wir untersuchen, wie man Design by Contract in Python implementiert.

Implementierung von Design by Contract in Python

Python bietet keine integrierte Unterstützung für Design by Contract, aber es gibt mehrere Drittanbieter - Bibliotheken und Frameworks, mit denen diese Methode implementiert werden kann. Eine beliebte Option ist die contracts - Bibliothek, die eine einfache und intuitive Möglichkeit bietet, Verträge in Python zu definieren und durchzusetzen.

Verwendung der contracts - Bibliothek

Um die contracts - Bibliothek zu verwenden, können Sie sie mit pip installieren:

pip install contracts

Nach der Installation können Sie Vorbedingungen (Preconditions), Nachbedingungen (Postconditions) und Klasseninvarianten (Class Invariants) mit dem von der Bibliothek bereitgestellten @contract - Dekorator definieren.

Vorbedingungen (Preconditions)

Hier ist ein Beispiel, wie man eine Vorbedingung mit dem @contract - Dekorator definiert:

from contracts import contract

@contract(x='int,>=0', y='int,>=0')
def add_numbers(x, y):
    return x + y

In diesem Beispiel gibt der @contract - Dekorator an, dass die add_numbers - Funktion zwei nicht-negative Ganzzahlargumente erwartet.

Nachbedingungen (Postconditions)

Sie können auch Nachbedingungen mit dem @contract - Dekorator definieren:

from contracts import contract

@contract(x='int,>=0', y='int,>=0', returns='int,>=0')
def add_numbers(x, y):
    return x + y

In diesem Beispiel gibt der @contract - Dekorator an, dass die add_numbers - Funktion eine nicht-negative Ganzzahl zurückgeben muss.

Klasseninvarianten (Class Invariants)

Um Klasseninvarianten zu definieren, können Sie den von der contracts - Bibliothek bereitgestellten @invariant - Dekorator verwenden:

from contracts import contract, invariant

class BankAccount:
    @invariant('balance >= 0')
    def __init__(self, initial_balance):
        self.balance = initial_balance

    @contract(amount='int,>=0')
    def deposit(self, amount):
        self.balance += amount

    @contract(amount='int,>=0', returns='bool')
    def withdraw(self, amount):
        if self.balance >= amount:
            self.balance -= amount
            return True
        else:
            return False

In diesem Beispiel stellt der @invariant - Dekorator sicher, dass das balance - Attribut der BankAccount - Klasse immer nicht-negativ ist.

Durch die Verwendung der contracts - Bibliothek können Sie Design by Contract effektiv in Ihren Python - Projekten implementieren, was zu robusterem und wartbarerem Code führt.

Praktische Beispiele und Anwendungsfälle

Design by Contract kann in einer Vielzahl von Python - Projekten angewendet werden, von kleinen Skripten bis hin zu großen Anwendungen. Hier sind einige praktische Beispiele und Anwendungsfälle:

Datenvalidierung

Ein häufiger Anwendungsfall für Design by Contract ist die Datenvalidierung. Indem Sie Vorbedingungen (Preconditions) und Nachbedingungen (Postconditions) definieren, können Sie sicherstellen, dass Ihre Funktionen und Methoden nur auf gültige Eingabedaten operieren und dass die Ausgabedaten bestimmte Anforderungen erfüllen.

Beispielsweise betrachten Sie eine Funktion, die den Durchschnitt einer Liste von Zahlen berechnet:

from contracts import contract

@contract(numbers='list[N](float,>=0)', returns='float,>=0')
def calculate_average(numbers):
    return sum(numbers) / len(numbers)

In diesem Beispiel gibt der @contract - Dekorator an, dass die calculate_average - Funktion eine nicht-leere Liste von nicht-negativen Fließkommazahlen erwartet und dass sie eine nicht-negative Fließkommazahl zurückgeben muss.

API - Design

Design by Contract kann auch beim Design von APIs nützlich sein, da es hilft, das erwartete Verhalten der Funktionen und Methoden der API klar zu definieren. Dies kann die API intuitiver und einfacher zu verwenden machen und auch helfen, Fehler und Randfälle früh im Entwicklungsprozess zu erkennen.

Beispielsweise betrachten Sie eine einfache API für eine To-Do-Liste - Anwendung:

from contracts import contract

class TodoList:
    @invariant('len(tasks) >= 0')
    def __init__(self):
        self.tasks = []

    @contract(task='str,len(x)>0')
    def add_task(self, task):
        self.tasks.append(task)

    @contract(index='int,>=0,<len(tasks)', returns='str,len(x)>0')
    def get_task(self, index):
        return self.tasks[index]

    @contract(index='int,>=0,<len(tasks)')
    def remove_task(self, index):
        del self.tasks[index]

In diesem Beispiel definiert die TodoList - Klasse mehrere Methoden mit Vorbedingungen und Nachbedingungen, die sicherstellen, dass die API wie erwartet funktioniert. Beispielsweise erfordert die add_task - Methode einen nicht-leeren String als Argument, und die get_task - Methode gibt einen nicht-leeren String zurück.

Unit - Testing

Design by Contract kann auch beim Schreiben effektiverer Unit - Tests nützlich sein. Indem Sie das erwartete Verhalten Ihrer Funktionen und Methoden mit Verträgen definieren, können Sie leichter Testfälle schreiben, die den gesamten Bereich möglicher Eingaben und Ausgaben abdecken.

Beispielsweise betrachten Sie den folgenden Unit - Test für die calculate_average - Funktion:

from contracts import new_contract
from unittest import TestCase

new_contract('non_empty_list', 'list[N](float,>=0) and len(x) > 0')

class TestCalculateAverage(TestCase):
    @contract(numbers='non_empty_list')
    def test_calculate_average(self, numbers):
        expected_average = sum(numbers) / len(numbers)
        actual_average = calculate_average(numbers)
        self.assertAlmostEqual(expected_average, actual_average)

In diesem Beispiel wird die new_contract - Funktion verwendet, um einen benutzerdefinierten Vertragstyp namens non_empty_list zu definieren, der dann in der test_calculate_average - Methode verwendet wird, um sicherzustellen, dass die Eingabeliste von Zahlen nicht leer ist.

Durch die Verwendung von Design by Contract in Ihren Python - Projekten können Sie robusteren, zuverlässigeren und wartbareren Code erstellen und die Gesamtqualität und Testbarkeit Ihrer Software verbessern.

Zusammenfassung

In diesem umfassenden Python - Tutorial haben Sie gelernt, wie Sie Design by Contract implementieren können, eine leistungsstarke Programmiersprache, die dabei hilft, die Zuverlässigkeit und Wartbarkeit des Codes sicherzustellen. Indem Sie die Prinzipien von Design by Contract verstehen und es auf Ihre Python - Projekte anwenden, können Sie robusteren und besser dokumentierten Code schreiben, was die Zusammenarbeit, das Debugging und die Wartung im Laufe der Zeit erleichtert.