Refactoring von Python-Anwendungen zur Vereinfachung

Refactoring von Python-Anwendungen zur Vereinfachung

Möchten Sie einfacheren Python-Code? Sie starten ein Projekt immer mit den besten Absichten, einer sauberen Codebasis und einer schönen Struktur. Aber im Laufe der Zeit gibt es Änderungen an Ihren Apps und die Dinge können etwas chaotisch werden.

Wenn Sie sauberen, einfachen Python-Code schreiben und pflegen können, sparen Sie langfristig viel Zeit. Sie können weniger Zeit mit Testen, Auffinden von Fehlern und Vornehmen von Änderungen verbringen, wenn Ihr Code übersichtlich und einfach zu befolgen ist.

In diesem Tutorial lernen Sie:

  • So messen Sie die Komplexität von Python-Code und Ihren Anwendungen

  • So ändern Sie Ihren Code, ohne ihn zu beschädigen

  • Was sind die häufigsten Probleme in Python-Code, die zusätzliche Komplexität verursachen, und wie Sie sie beheben können

In diesem Tutorial werde ich das Thema unterirdische Eisenbahnnetze verwenden, um die Komplexität zu erklären, da das Navigieren in einem U-Bahn-System in einer Großstadt kompliziert sein kann! Einige sind gut gestaltet, andere wirken zu komplex.

Free Bonus:5 Thoughts On Python Mastery, ein kostenloser Kurs für Python-Entwickler, der Ihnen die Roadmap und die Denkweise zeigt, die Sie benötigen, um Ihre Python-Fähigkeiten auf die nächste Stufe zu bringen.

Codekomplexität in Python

Die Komplexität einer Anwendung und ihrer Codebasis hängt von der Aufgabe ab, die sie ausführt. Wenn Sie Code für das Jet-Antriebslabor der NASA schreiben (buchstäblichrocket science), wird dies kompliziert.

Die Frage ist nicht so sehr: "Ist mein Code kompliziert?" als "Ist mein Code komplizierter als es sein muss?"

Das Eisenbahnnetz von Tokio ist eines der umfangreichsten und kompliziertesten der Welt. Dies liegt zum Teil daran, dass Tokio eine Metropole mit über 30 Millionen Einwohnern ist, aber auch daran, dass sich drei Netzwerke überlappen.

Es gibt die Schnellverkehrsnetze Toei und Tokyo Metro sowie die Züge der Japan Rail East, die durch das Zentrum Tokios fahren. Selbst für den erfahrensten Reisenden kann die Navigation durch das Zentrum Tokios unglaublich kompliziert sein.

Hier ist eine Karte des Eisenbahnnetzes von Tokio, um Ihnen eine Perspektive zu geben:

Wenn Ihr Code ein bisschen wie diese Karte aussieht, ist dies das Tutorial für Sie.

Zunächst gehen wir vier Komplexitätsmetriken durch, anhand derer Sie Ihren relativen Fortschritt in der Mission messen können, um Ihren Code einfacher zu gestalten:

Code Complexity Metrics Table of Contents

Nachdem Sie die Metriken untersucht haben, lernen Sie ein Tool namenswily kennen, mit dem Sie die Berechnung dieser Metriken automatisieren können.

Metriken zur Messung der Komplexität

Es wurde viel Zeit und Forschung in die Analyse der Komplexität von Computersoftware gesteckt. Zu komplexe und nicht wartbare Anwendungen können sehr reale Kosten verursachen.

Die Komplexität der Software hängt von der Qualität ab. Code, der leicht zu lesen und zu verstehen ist, wird in Zukunft eher von Entwicklern aktualisiert.

Hier sind einige Metriken für Programmiersprachen. Sie gelten für viele Sprachen, nicht nur für Python.

Zeilen von Code

LOC (Code Lines) ist das gröbste Maß für die Komplexität. Es ist fraglich, ob es eine direkte Korrelation zwischen den Codezeilen und der Komplexität einer Anwendung gibt, aber die indirekte Korrelation ist klar. Schließlich ist ein Programm mit 5 Zeilen wahrscheinlich einfacher als eines mit 5 Millionen.

Bei der Betrachtung von Python-Metriken versuchen wir, Leerzeilen und Zeilen mit Kommentaren zu ignorieren.

Codezeilen können mit dem Befehlwc unter Linux und Mac OS berechnet werden, wobeifile.py der Name der Datei ist, die Sie messen möchten:

$ wc -l file.py

Wenn Sie die kombinierten Zeilen in einem Ordner hinzufügen möchten, indem Sie rekursiv nach allen.py-Dateien suchen, können Siewc mit dem Befehlfind kombinieren:

$ find . -name \*.py | xargs wc -l

Für Windows bietet PowerShell einen Befehl zum Zählen der Wörter inMeasure-Object und eine rekursive Dateisuche inGet-ChildItem:

$ Get-ChildItem -Path *.py -Recurse | Measure-Object –Line

In der Antwort sehen Sie die Gesamtzahl der Zeilen.

Warum werden Codezeilen verwendet, um die Codemenge in Ihrer Anwendung zu quantifizieren? Die Annahme ist, dass eine Codezeile ungefähr einer Anweisung entspricht. Linien sind ein besseres Maß als Zeichen, die Leerzeichen enthalten würden.

In Python wird empfohlen, in jede Zeile eine einzelne Anweisung einzufügen. Dieses Beispiel besteht aus 9 Codezeilen:

 1 x = 5
 2 value = input("Enter a number: ")
 3 y = int(value)
 4 if x < y:
 5     print(f"{x} is less than {y}")
 6 elif x == y:
 7     print(f"{x} is equal to {y}")
 8 else:
 9     print(f"{x} is more than {y}")

Wenn Sie nur Codezeilen als Maß für die Komplexität verwenden, kann dies zu falschen Verhaltensweisen führen.

Python-Code sollte leicht zu lesen und zu verstehen sein. Mit diesem letzten Beispiel können Sie die Anzahl der Codezeilen auf 3 reduzieren:

 1 x = 5; y = int(input("Enter a number:"))
 2 equality = "is equal to" if x == y else "is less than" if x < y else "is more than"
 3 print(f"{x} {equality} {y}")

Das Ergebnis ist jedoch schwer zu lesen, und PEP 8 enthält Richtlinien zur maximalen Zeilenlänge und zum Zeilenumbruch. Sie könnenHow to Write Beautiful Python Code With PEP 8 für weitere Informationen zu PEP 8 überprüfen.

Dieser Codeblock verwendet zwei Python-Sprachfunktionen, um den Code zu verkürzen:

  • Compound statements: unter Verwendung von;

  • Verkettete bedingte oder ternäre Anweisungen: name = value if condition else value if condition2 else value2

Wir haben die Anzahl der Codezeilen reduziert, aber eines der Grundgesetze von Python verletzt:

"Lesbarkeit zählt"

- Tim Peters, Zen von Python

Dieser verkürzte Code ist möglicherweise schwieriger zu pflegen, da Code-Betreuer Menschen sind und dieser Kurzcode schwerer zu lesen ist. Wir werden einige fortgeschrittenere und nützlichere Metriken für die Komplexität untersuchen.

Zyklomatische Komplexität

Die zyklomatische Komplexität ist das Maß dafür, wie viele unabhängige Codepfade in Ihrer Anwendung vorhanden sind. Ein Pfad ist eine Folge von Anweisungen, denen der Interpreter folgen kann, um zum Ende der Anwendung zu gelangen.

Eine Möglichkeit, sich zyklomatische Komplexität und Codepfade vorzustellen, besteht darin, sich vorzustellen, dass Ihr Code wie ein Eisenbahnnetz ist.

Für eine Reise müssen Sie möglicherweise umsteigen, um Ihr Ziel zu erreichen. Das Eisenbahnsystem der Metropole Lissabon in Portugal ist einfach und leicht zu navigieren. Die zyklomatische Komplexität für jede Reise entspricht der Anzahl der Linien, auf denen Sie fahren müssen:

Wenn Sie vonAlvalade nachAnjos gelangen müssten, würden Sie 5 Stopps auf derlinha verde (grüne Linie) fahren:

Diese Reise hat eine zyklomatische Komplexität von 1, da Sie nur 1 Zug nehmen. Es ist eine einfache Reise. Dieser Zug entspricht in dieser Analogie einem Code-Zweig.

Wenn Sie vonAeroporto (Flughafen) aus reisen mussten, umfood in the district of Belém zu testen, ist die Reise komplizierter. Sie müssten beiAlameda undCais do Sodré umsteigen:

Diese Reise hat eine zyklomatische Komplexität von 3, da Sie 3 Züge nehmen. Vielleicht ist es besser, ein Taxi zu nehmen!

Da Sie nicht durch Lissabon navigieren, sondern Code schreiben, werden die Änderungen der Zuglinie zu einem Zweig in der Ausführung, wie eineif-Anweisung. Lassen Sie uns dieses Beispiel untersuchen:

x = 1

Es gibt nur einen Weg, wie dieser Code ausgeführt werden kann, daher hat er eine zyklomatische Komplexität von 1.

Wenn wir eine Entscheidung hinzufügen oder dem Code alsif-Anweisung verzweigen, erhöht sich die Komplexität:

x = 1
if x < 2:
    x += 1

Obwohl es nur eine Möglichkeit gibt, diesen Code auszuführen, dax eine Konstante ist, hat dies eine zyklomatische Komplexität von 2. Alle zyklomatischen Komplexitätsanalysatoren behandeln eineif-Anweisung als Verzweigung.

Dies ist auch ein Beispiel für zu komplexen Code. Die Anweisungif ist nutzlos, dax einen festen Wert hat. Sie können dieses Beispiel einfach wie folgt umgestalten:

x = 2

Das war ein Spielzeugbeispiel. Lassen Sie uns also etwas Realeres erkunden.

main() hat eine zyklomatische Komplexität von 5. Ich werde jeden Zweig im Code kommentieren, damit Sie sehen können, wo er sich befindet:

# cyclomatic_example.py
import sys

def main():
    if len(sys.argv) > 1:  # 1
        filepath = sys.argv[1]
    else:
        print("Provide a file path")
        exit(1)
    if filepath:  # 2
        with open(filepath) as fp:  # 3
            for line in fp.readlines():  # 4
                if line != "\n":  # 5
                    print(line, end="")

if __name__ == "__main__":  # Ignored.
    main()

Es gibt sicherlich Möglichkeiten, Code in eine weitaus einfachere Alternative umzuwandeln. Wir werden später darauf zurückkommen.

Note: Das Maß für die zyklomatische Komplexität betrug 1976developed by Thomas J. McCabe, Sr. Möglicherweise wird es alsMcCabe metric oderMcCabe number bezeichnet.

In den folgenden Beispielen verwenden wir dieradon-Bibliothekfrom PyPi, um Metriken zu berechnen. Sie können es jetzt installieren:

$ pip install radon

Um die zyklomatische Komplexität mitradon zu berechnen, können Sie das Beispiel in einer Datei mit dem Namencyclomatic_example.py speichern undradon über die Befehlszeile verwenden.

Der Befehlradon akzeptiert zwei Hauptargumente:

  1. Die Art der Analyse (cc für die zyklomatische Komplexität)

  2. Ein Pfad zu der zu analysierenden Datei oder dem zu analysierenden Ordner

Führen Sie den Befehlradon mit der Analysecc für die Dateicyclomatic_example.py aus. Das Hinzufügen von-s ergibt die zyklomatische Komplexität in der Ausgabe:

$ radon cc cyclomatic_example.py -s
cyclomatic_example.py
    F 4:0 main - B (6)

Die Ausgabe ist etwas kryptisch. Folgendes bedeutet jeder Teil:

  • F bedeutet Funktion,M bedeutet Methode undC bedeutet Klasse.

  • main ist der Name der Funktion.

  • 4 ist die Zeile, in der die Funktion beginnt.

  • B ist die Bewertung von A bis F. A ist die beste Note, was die geringste Komplexität bedeutet.

  • Die Zahl in Klammern,6, ist die zyklomatische Komplexität des Codes.

Halstead-Metriken

Die Halstead-Komplexitätsmetriken beziehen sich auf die Größe der Codebasis eines Programms. Sie wurden von Maurice H. entwickelt. Halstead im Jahr 1977. Die Halstead-Gleichungen enthalten 4 Maße:

  • Operands sind Werte und Namen von Variablen.

  • Operators sind alle integrierten Schlüsselwörter wieif,else,for oderwhile.

  • Length (N) ist die Anzahl der Operatoren plus die Anzahl der Operanden in Ihrem Programm.

  • Vocabulary (h) ist die Anzahl derunique-Operatoren plus die Anzahl derunique-Operanden in Ihrem Programm.

Es gibt dann 3 zusätzliche Metriken mit diesen Maßnahmen:

  • Volume (V) repräsentiert ein Produkt auslength undvocabulary.

  • Difficulty (D) repräsentiert ein Produkt aus der Hälfte der eindeutigen Operanden und der Wiederverwendung von Operanden.

  • Effort (E) ist die Gesamtmetrik, die ein Produkt ausvolume unddifficulty ist.

All dies ist sehr abstrakt, also lassen Sie es uns relativ ausdrücken:

  • Der Aufwand Ihrer Anwendung ist am höchsten, wenn Sie viele Operatoren und eindeutige Operanden verwenden.

  • Der Aufwand Ihrer Anwendung ist geringer, wenn Sie einige Operatoren und weniger Variablen verwenden.

Für das Beispiel voncyclomatic_complexity.pytreten Operatoren und Operanden beide in der ersten Zeile auf:

import sys  # import (operator), sys (operand)

import ist ein Operator undsys ist der Name des Moduls, also ein Operand.

In einem etwas komplexeren Beispiel gibt es eine Reihe von Operatoren und Operanden:

if len(sys.argv) > 1:
    ...

In diesem Beispiel gibt es 5 Operatoren:

  1. if

  2. (

  3. )

  4. >

  5. :

Darüber hinaus gibt es 2 Operanden:

  1. sys.argv

  2. 1

Beachten Sie, dassradon nur eine Teilmenge von Operatoren zählt. Beispielsweise werden Klammern in Berechnungen ausgeschlossen.

Um die Halstead-Kennzahlen inradon zu berechnen, können Sie den folgenden Befehl ausführen:

$ radon hal cyclomatic_example.py
cyclomatic_example.py:
    h1: 3
    h2: 6
    N1: 3
    N2: 6
    vocabulary: 9
    length: 9
    calculated_length: 20.264662506490406
    volume: 28.529325012980813
    difficulty: 1.5
    effort: 42.793987519471216
    time: 2.377443751081734
    bugs: 0.009509775004326938

Warum gibtradon eine Metrik für Zeit und Fehler an?

Halstead vermutete, dass Sie die Zeit, die zum Codieren in Sekunden benötigt wird, schätzen könnten, indem Sie den Aufwand (E) durch 18 teilen.

Halstead gab auch an, dass die erwartete Anzahl von Fehlern geschätzt werden könnte, indem das Volumen (V) durch 3000 geteilt wird. Denken Sie daran, dass dies 1977 geschrieben wurde, bevor Python überhaupt erfunden wurde! Also keine Panik und erst jetzt nach Fehlern suchen.

Wartbarkeitsindex

Der Wartbarkeitsindex bringt die McCabe Cyclomatic Complexity- und das Halstead Volume-Maß auf eine Skala zwischen ungefähr null und einhundert.

Wenn Sie interessiert sind, lautet die ursprüngliche Gleichung wie folgt:

MI Equation

In der Gleichung istV die Halstead-Volumenmetrik,C die zyklomatische Komplexität undL die Anzahl der Codezeilen.

Wenn Sie genauso verblüfft sind wie ich, als ich diese Gleichung zum ersten Mal sah, bedeutet dies Folgendes: Sie berechnet eine Skala, die die Anzahl der Variablen, Operationen, Entscheidungspfade und Codezeilen enthält.

Es wird in vielen Tools und Sprachen verwendet und ist daher eine der Standardmetriken. Es gibt jedoch zahlreiche Überarbeitungen der Gleichung, sodass die genaue Anzahl nicht als Tatsache angesehen werden sollte. radon,wily und Visual Studio begrenzen die Zahl zwischen 0 und 100.

Auf der Wartbarkeitsindexskala müssen Sie nur darauf achten, wenn Ihr Code deutlich niedriger wird (in Richtung 0). Die Skala betrachtet alles unter 25 alshard to maintain und alles über 75 alseasy to maintain. Der Wartbarkeitsindex wird auch alsMI bezeichnet.

Der Wartbarkeitsindex kann als Maß verwendet werden, um die aktuelle Wartbarkeit Ihrer Anwendung abzurufen und festzustellen, ob Sie bei der Überarbeitung Fortschritte erzielen.

Führen Sie den folgenden Befehl aus, um den Wartbarkeitsindex ausradon zu berechnen:

$ radon mi cyclomatic_example.py -s
cyclomatic_example.py - A (87.42)

In diesem Ergebnis istA die Note, dieradon auf die Anzahl87.42 auf einer Skala angewendet hat. Auf dieser Skala istA am wartbarsten undF am wenigsten wartbar.

Verwenden vonwily zum Erfassen und Verfolgen der Komplexität Ihrer Projekte

wily is an open-source software project zum Sammeln von Kennzahlen zur Codekomplexität, einschließlich der bisher behandelten Kennzahlen wie Halstead, Cyclomatic und LOC. wily lässt sich in Git integrieren und kann die Erfassung von Metriken über Git-Zweige und Revisionen hinweg automatisieren.

Der Zweck vonwily besteht darin, Ihnen die Möglichkeit zu geben, Trends und Änderungen in der Komplexität Ihres Codes im Laufe der Zeit zu erkennen. Wenn Sie versuchen, ein Auto zu optimieren oder Ihre Fitness zu verbessern, beginnen Sie mit der Messung einer Basislinie und der Verfolgung von Verbesserungen im Laufe der Zeit.

wily installieren

wily ist verfügbaron PyPi und kann mit pip installiert werden:

$ pip install wily

Sobaldwily installiert ist, stehen Ihnen einige Befehle in Ihrer Befehlszeile zur Verfügung:

  • wily build: durchlaufen den Git-Verlauf und analysieren die Metriken für jede Datei

  • wily report: sehen den historischen Trend in Metriken für eine bestimmte Datei oder einen bestimmten Ordner

  • wily graph: Diagramm eine Reihe von Metriken in einer HTML-Datei

Cache erstellen

Bevor Siewily verwenden können, müssen Sie Ihr Projekt analysieren. Dies erfolgt mit dem Befehlwily build.

In diesem Abschnitt des Tutorials analysieren wir das sehr beliebte Paketrequests, das für die Kommunikation mit HTTP-APIs verwendet wird. Da dieses Projekt Open Source ist und auf GitHub verfügbar ist, können wir problemlos auf eine Kopie des Quellcodes zugreifen und diese herunterladen:

$ git clone https://github.com/requests/requests
$ cd requests
$ ls
AUTHORS.rst        CONTRIBUTING.md    LICENSE            Makefile
Pipfile.lock       _appveyor          docs               pytest.ini
setup.cfg          tests              CODE_OF_CONDUCT.md HISTORY.md
MANIFEST.in        Pipfile            README.md          appveyor.yml
ext                requests           setup.py           tox.ini

Note: Windows-Benutzer sollten die PowerShell-Eingabeaufforderung für die folgenden Beispiele anstelle der herkömmlichen MS-DOS-Befehlszeile verwenden. Um die PowerShell-CLI zu starten, drücken SieWin[.kbd .key-r]#R## and type `+powershell` then [.keys] [.kbd .key-enter]Enter #.

Hier sehen Sie eine Reihe von Ordnern für Tests, Dokumentation und Konfiguration. Wir interessieren uns nur für den Quellcode desrequests Python-Paketes, das sich in einem Ordner namensrequests befindet.

Rufen Sie den Befehlwily build aus dem geklonten Quellcode auf und geben Sie als erstes Argument den Namen des Quellcodeordners an:

$ wily build requests

Die Analyse dauert einige Minuten, je nachdem, wie viel CPU-Leistung Ihr Computer hat:

Screenshot capture of Wily build command

Sammeln von Daten zu Ihrem Projekt

Nachdem Sie den Quellcode vonrequestsanalysiert haben, können Sie jede Datei oder jeden Ordner abfragen, um wichtige Metriken anzuzeigen. Zu Beginn des Tutorials haben wir Folgendes besprochen:

  • Zeilen von Code

  • Wartbarkeitsindex

  • Zyklomatische Komplexität

Dies sind die 3 Standardmetriken inwily. Führen Sie den folgenden Befehl aus, um diese Metriken für eine bestimmte Datei (z. B.requests/api.py) anzuzeigen:

$ wily report requests/api.py

wily druckt einen tabellarischen Bericht über die Standardmetriken für jedes Git-Commit in umgekehrter Datumsreihenfolge. Oben sehen Sie das letzte Commit und unten das älteste:

Revision Autor Date MI Zeilen von Code Zyklomatische Komplexität

f37daf2

Nate Prewitt

2019-01-13

100 (0,0)

158 (0)

9 (0)

6dd410f

Ofek Lev

2019-01-13

100 (0,0)

158 (0)

9 (0)

5c1f72e

Nate Prewitt

2018-12-14

100 (0,0)

158 (0)

9 (0)

c4d7680

Matthieu Moy

2018-12-14

100 (0,0)

158 (0)

9 (0)

c452e3b

Nate Prewitt

2018-12-11

100 (0,0)

158 (0)

9 (0)

5a1e738

Nate Prewitt

2018-12-10

100 (0,0)

158 (0)

9 (0)

Dies sagt uns, dass die Dateirequests/api.pyFolgendes hat:

  • 158 Codezeilen

  • Ein perfekter Wartbarkeitsindex von 100

  • Eine zyklomatische Komplexität von 9

Um andere Metriken anzuzeigen, müssen Sie zuerst deren Namen kennen. Sie können dies sehen, indem Sie den folgenden Befehl ausführen:

$ wily list-metrics

Sie sehen eine Liste der Operatoren, Module, die den Code analysieren, und der von ihnen bereitgestellten Metriken.

Um alternative Metriken für den Berichtsbefehl abzufragen, fügen Sie deren Namen nach dem Dateinamen hinzu. Sie können beliebig viele Metriken hinzufügen. Hier ist ein Beispiel mit dem Wartbarkeitsrang und den Quellcodezeilen:

$ wily report requests/api.py maintainability.rank raw.sloc

Sie werden sehen, dass die Tabelle jetzt 2 verschiedene Spalten mit den alternativen Metriken enthält.

Diagramme grafisch darstellen

Nachdem Sie die Namen der Metriken kennen und wissen, wie Sie sie in der Befehlszeile abfragen können, können Sie sie auch in Diagrammen visualisieren. wily unterstützt HTML und interaktive Diagramme mit einer ähnlichen Oberfläche wie der Befehl report:

$ wily graph requests/sessions.py maintainability.mi

Ihr Standardbrowser wird mit einem interaktiven Diagramm wie dem folgenden geöffnet:

Screenshot capture of Wily graph command

Sie können den Mauszeiger über bestimmte Datenpunkte bewegen und die Git-Commit-Nachricht sowie die Daten werden angezeigt.

Wenn Sie die HTML-Datei in einem Ordner oder Repository speichern möchten, können Sie das Flag-o mit dem Pfad zu einer Datei hinzufügen:

$ wily graph requests/sessions.py maintainability.mi -o my_report.html

Es wird nun eine Datei namensmy_report.html geben, die Sie mit anderen teilen können. Dieser Befehl ist ideal für Team-Dashboards.

wily alspre-commit Hook

wily kann so konfiguriert werden, dass Sie vor dem Festschreiben von Änderungen an Ihrem Projekt auf Verbesserungen oder Verschlechterungen der Komplexität hingewiesen werden.

wily verfügt über einen Befehlwily diff, der die zuletzt indizierten Daten mit der aktuellen Arbeitskopie einer Datei vergleicht.

Geben Sie die Namen der Dateien an, die Sie geändert haben, um den Befehlwily diff auszuführen. Wenn ich beispielsweise einige Änderungen anrequests/api.py vorgenommen habe, sehen Sie die Auswirkungen auf die Metriken, indem Siewily diff mit dem Dateipfad ausführen:

$ wily diff requests/api.py

In der Antwort sehen Sie alle geänderten Metriken sowie die Funktionen oder Klassen, die sich aufgrund der zyklomatischen Komplexität geändert haben:

Screenshot of the wily diff command

Der Befehldiff kann mit einem Tool namenspre-commit gekoppelt werden. pre-commit fügt einen Hook in Ihre Git-Konfiguration ein, der jedes Mal ein Skript aufruft, wenn Sie den Befehlgit commit ausführen.

Umpre-commit zu installieren, können Sie von PyPI installieren:

$ pip install pre-commit

Fügen Sie einem.pre-commit-config.yaml im Stammverzeichnis Ihres Projekts Folgendes hinzu:

repos:
-   repo: local
    hooks:
    -   id: wily
        name: wily
        entry: wily diff
        verbose: true
        language: python
        additional_dependencies: [wily]

Sobald Sie dies eingestellt haben, führen Sie den Befehlpre-commit install aus, um die Dinge abzuschließen:

$ pre-commit install

Wenn Sie den Befehlgit commit ausführen, wirdwily diff zusammen mit der Liste der Dateien aufgerufen, die Sie zu Ihren bereitgestellten Änderungen hinzugefügt haben.

wily ist ein nützliches Dienstprogramm, um die Komplexität Ihres Codes zu ermitteln und die Verbesserungen zu messen, die Sie vornehmen, wenn Sie mit der Umgestaltung beginnen.

Refactoring in Python

Refactoring ist die Technik zum Ändern einer Anwendung (entweder des Codes oder der Architektur), sodass sie sich äußerlich genauso verhält, sich jedoch intern verbessert hat. Diese Verbesserungen können Stabilität, Leistung oder Verringerung der Komplexität sein.

Eine der ältesten U-Bahnen der Welt, die Londoner U-Bahn, begann 1863 mit der Eröffnung der Metropolitan Line. Es hatte gasbeleuchtete Holzwagen, die von Dampflokomotiven gezogen wurden. Bei der Eröffnung der Eisenbahn war es zweckmäßig. 1900 brachte die Erfindung der elektrischen Eisenbahnen.

Bis 1908 wurde die Londoner U-Bahn auf 8 Eisenbahnen erweitert. Während des Zweiten Weltkriegs wurden die Londoner U-Bahn-Stationen für Züge gesperrt und als Luftschutzbunker genutzt. Die moderne Londoner U-Bahn befördert täglich Millionen von Passagieren mit über 270 Stationen:

Es ist fast unmöglich, beim ersten Mal perfekten Code zu schreiben, und die Anforderungen ändern sich häufig. Wenn Sie die ursprünglichen Konstrukteure der Eisenbahn gebeten hätten, ein Netz für 10 Millionen Fahrgäste pro Tag im Jahr 2020 zu entwerfen, würden sie das heute bestehende Netz nicht entwerfen.

Stattdessen hat die Eisenbahn eine Reihe kontinuierlicher Änderungen erfahren, um ihren Betrieb, ihr Design und ihr Layout an die Veränderungen in der Stadt anzupassen. Es wurde überarbeitet.

In diesem Abschnitt erfahren Sie, wie Sie mithilfe von Tests und Tools sicher umgestalten können. Sie werden auch sehen, wie Sie die Refactoring-Funktionalität inVisual Studio Code undPyCharm verwenden:

Refactoring Section Table of Contents

Vermeiden von Risiken durch Refactoring: Einsatz von Tools und Durchführung von Tests

Wie können Sie sicherstellen, dass sich die externen Elemente nicht geändert haben, wenn der Zweck des Refactorings darin besteht, die Interna einer Anwendung zu verbessern, ohne die externen Effekte zu beeinträchtigen?

Bevor Sie ein großes Refactoring-Projekt starten, müssen Sie sicherstellen, dass Sie über eine solide Testsuite für Ihre Anwendung verfügen. Im Idealfall sollte diese Testsuite größtenteils automatisiert sein, damit Sie beim Vornehmen von Änderungen die Auswirkungen auf den Benutzer erkennen und diese schnell beheben können.

Wenn Sie mehr über das Testen in Python erfahren möchten, istGetting Started With Testing in Python ein guter Ausgangspunkt.

Es gibt keine perfekte Anzahl von Tests für Ihre Anwendung. Je robuster und gründlicher die Testsuite ist, desto aggressiver können Sie Ihren Code umgestalten.

Die zwei häufigsten Aufgaben, die Sie beim Refactoring ausführen, sind:

  • Umbenennen von Modulen, Funktionen, Klassen und Methoden

  • Suchen nach Verwendungen von Funktionen, Klassen und Methoden, um zu sehen, wo sie aufgerufen werden

Sie können dies einfach von Hand mitsearch and replace tun, dies ist jedoch sowohl zeitaufwändig als auch riskant. Stattdessen gibt es einige großartige Tools, um diese Aufgaben auszuführen.

Verwenden vonrope für das Refactoring

rope ist ein kostenloses Python-Dienstprogramm zum Refactoring von Python-Code. Es enthält einenextensive-Satz von APIs zum Refactoring und Umbenennen von Komponenten in Ihrer Python-Codebasis.

rope kann auf zwei Arten verwendet werden:

  1. Mithilfe eines Editor-Plugins fürVisual Studio Code,Emacs oderVim

  2. Direkt durch Schreiben von Skripten zur Umgestaltung Ihrer Anwendung

Um Seil als Bibliothek zu verwenden, installieren Sie zuerstrope, indem Siepip ausführen:

$ pip install rope

Es ist nützlich, mitrope auf der REPL zu arbeiten, damit Sie das Projekt erkunden und Änderungen in Echtzeit sehen können. Importieren Sie zunächst den TypProjectund instanziieren Sie ihn mit dem Pfad zum Projekt:

>>>

>>> from rope.base.project import Project

>>> proj = Project('requests')

Die Variableproj kann jetzt eine Reihe von Befehlen wieget_files undget_file ausführen, um eine bestimmte Datei abzurufen. Holen Sie sich die Dateiapi.py und weisen Sie sie einer Variablen namensapi zu:

>>>

>>> [f.name for f in proj.get_files()]
['structures.py', 'status_codes.py', ...,'api.py', 'cookies.py']

>>> api = proj.get_file('api.py')

Wenn Sie diese Datei umbenennen möchten, können Sie sie einfach im Dateisystem umbenennen. Alle anderen Python-Dateien in Ihrem Projekt, die den alten Namen importiert haben, sind jetzt fehlerhaft. Benennen wir dieapi.py innew_api.py um:

>>>

>>> from rope.refactor.rename import Rename

>>> change = Rename(proj, api).get_changes('new_api')

>>> proj.do(change)

Wenn Siegit status ausführen, werden Sie feststellen, dassrope einige Änderungen am Repository vorgenommen hat:

$ git status
On branch master
Your branch is up to date with 'origin/master'.

Changes not staged for commit:
  (use "git add/rm ..." to update what will be committed)
  (use "git checkout -- ..." to discard changes in working directory)

    modified:   requests/__init__.py
    deleted:    requests/api.py

Untracked files:
  (use "git add ..." to include in what will be committed)

   requests/.ropeproject/
   requests/new_api.py

no changes added to commit (use "git add" and/or "git commit -a")

Die drei durchrope vorgenommenen Änderungen sind folgende:

  1. requests/api.py gelöscht undrequests/new_api.py erstellt

  2. requests/__init__.py wurde geändert, um vonnew_api anstelle vonapi zu importieren

  3. Erstellt einen Projektordner mit dem Namen.ropeproject

Führen Siegit reset aus, um die Änderung zurückzusetzen.

Es gibthundreds of other refactorings, die mitrope durchgeführt werden können.

Verwenden von Visual Studio-Code für das Refactoring

Visual Studio Code öffnet eine kleine Teilmenge der Refactoring-Befehle, die inrope über eine eigene Benutzeroberfläche verfügbar sind.

Du kannst:

  1. Extrahieren Sie Variablen aus einer Anweisung

  2. Extrahieren Sie Methoden aus einem Codeblock

  3. Sortieren Sie Importe in eine logische Reihenfolge

Hier ist ein Beispiel für die Verwendung des BefehlsExtract methods aus der Befehlspalette:

Screenshot of Visual Studio Code refactoring

Verwenden von PyCharm für das Refactoring

Wenn Sie PyCharm als Python-Editor verwenden oder in Betracht ziehen, sollten Sie die leistungsstarken Refactoring-Funktionen beachten.

Sie können mitCtrl[.kbd .key-t]#T## command on Windows and macOS. The shortcut to access refactoring in Linux is [.keys]#[.kbd .key-control]##Ctrl##Shift[.kbd .key-alt]##Alt##[.kbd .key-t]#T # auf alle Refactoring-Verknüpfungen zugreifen.

Anrufer finden und Funktionen und Klassen nutzen

Bevor Sie eine Methode oder Klasse entfernen oder ihr Verhalten ändern, müssen Sie wissen, welcher Code davon abhängt. PyCharm kann nach allen Verwendungen einer Methode, Funktion oder Klasse in Ihrem Projekt suchen.

Um auf diese Funktion zuzugreifen, wählen Sie eine Methode, Klasse oder Variable aus, indem Sie mit der rechten Maustaste klicken undFind Usages auswählen:

Finding usages in PyCharm

Der gesamte Code, der Ihre Suchkriterien verwendet, wird in einem Bereich unten angezeigt. Sie können auf ein Element doppelklicken, um direkt zu der betreffenden Zeile zu navigieren.

Verwenden der PyCharm Refactoring-Tools

Einige der anderen Refactoring-Befehle umfassen die folgenden Funktionen:

  • Extrahieren Sie Methoden, Variablen und Konstanten aus vorhandenem Code

  • Extrahieren Sie abstrakte Klassen aus vorhandenen Klassensignaturen, einschließlich der Möglichkeit, abstrakte Methoden anzugeben

  • Benennen Sie praktisch alles um, von einer Variablen in eine Methode, Datei, Klasse oder ein Modul

Hier ist ein Beispiel für das Umbenennen des gleichenapi.py-Moduls, das Sie zuvor mit demrope-Modul innew_api.py umbenannt haben:

How to rename methods in pycharm

Der Befehl zum Umbenennen wird in die Benutzeroberfläche kontextualisiert, wodurch das Refactoring schnell und einfach erfolgt. Die Importe wurden automatisch in__init__.py mit dem neuen Modulnamen aktualisiert.

Ein weiterer nützlicher Refaktor ist der BefehlChange Signature. Dies kann verwendet werden, um einer Funktion oder Methode Argumente hinzuzufügen, zu entfernen oder umzubenennen. Es wird nach Verwendungen suchen und diese für Sie aktualisieren:

Changing method signatures in PyCharm

Sie können Standardwerte festlegen und auch entscheiden, wie das Refactoring mit den neuen Argumenten umgehen soll.

Zusammenfassung

Refactoring ist eine wichtige Fähigkeit für jeden Entwickler. Wie Sie in diesem Kapitel erfahren haben, sind Sie nicht allein. Die Tools und IDEs verfügen bereits über leistungsstarke Refactoring-Funktionen, um Änderungen schnell vornehmen zu können.

Komplexitäts-Anti-Muster

Nachdem Sie nun wissen, wie Komplexität gemessen werden kann, wie sie gemessen werden kann und wie Sie Ihren Code umgestalten können, ist es an der Zeit, fünf gängige Anti-Patterns zu lernen, die Code komplexer machen, als er sein muss:

Anti-Patterns Table of Contents

Wenn Sie diese Muster beherrschen und wissen, wie Sie sie umgestalten können, sind Sie bald auf dem richtigen Weg (Wortspiel beabsichtigt) zu einer besser zu wartenden Python-Anwendung.

1. Funktionen, die Objekte sein sollten

Python unterstütztprocedural programming mithilfe von Funktionen undinheritable classes. Beide sind sehr mächtig und sollten auf verschiedene Probleme angewendet werden.

Nehmen Sie dieses Beispiel eines Moduls zum Arbeiten mit Bildern. Die Logik in den Funktionen wurde der Kürze halber entfernt:

# imagelib.py

def load_image(path):
    with open(path, "rb") as file:
        fb = file.load()
    image = img_lib.parse(fb)
    return image

def crop_image(image, width, height):
    ...
    return image

def get_image_thumbnail(image, resolution=100):
    ...
    return image

Es gibt einige Probleme mit diesem Design:

  1. Es ist nicht klar, obcrop_image() undget_image_thumbnail() die ursprüngliche Variableimageändern oder neue Bilder erstellen. Wenn Sie ein Bild laden und dann sowohl ein zugeschnittenes als auch ein Miniaturbild erstellen möchten, müssten Sie zuerst die Instanz kopieren? Sie können den Quellcode in den Funktionen lesen, können sich jedoch nicht darauf verlassen, dass jeder Entwickler dies tut.

  2. Sie müssen die Bildvariable bei jedem Aufruf der Bildfunktionen als Argument übergeben.

So könnte der aufrufende Code aussehen:

from imagelib import load_image, crop_image, get_image_thumbnail

image = load_image('~/face.jpg')
image = crop_image(image, 400, 500)
thumb = get_image_thumbnail(image)

Hier sind einige Symptome von Code, der Funktionen verwendet, die in Klassen umgestaltet werden könnten:

  • Ähnliche Argumente über Funktionen hinweg

  • Höhere Anzahl von Halsteadh2unique operands

  • Mischung aus veränderlichen und unveränderlichen Funktionen

  • Funktionen, die auf mehrere Python-Dateien verteilt sind

Hier ist eine überarbeitete Version dieser 3 Funktionen, bei denen Folgendes passiert:

  • .__init__() ersetztload_image().

  • crop() wird zu einer Klassenmethode.

  • get_image_thumbnail() wird eine Eigenschaft.

Die Miniaturbildauflösung ist zu einer Klasseneigenschaft geworden, sodass sie global oder in dieser bestimmten Instanz geändert werden kann:

# imagelib.py

class Image(object):
    thumbnail_resolution = 100
    def __init__(self, path):
        ...

    def crop(self, width, height):
        ...

    @property
    def thumbnail(self):
        ...
        return thumb

Wenn dieser Code viel mehr bildbezogene Funktionen enthält, kann das Refactoring einer Klasse eine drastische Änderung bewirken. Die nächste Überlegung wäre die Komplexität des konsumierenden Codes.

So würde das überarbeitete Beispiel aussehen:

from imagelib import Image

image = Image('~/face.jpg')
image.crop(400, 500)
thumb = image.thumbnail

Im resultierenden Code haben wir die ursprünglichen Probleme gelöst:

  • Es ist klar, dassthumbnail eine Miniaturansicht zurückgibt, da es sich um eine Eigenschaft handelt, und dass die Instanz nicht geändert wird.

  • Für den Code müssen keine neuen Variablen mehr für den Zuschneidevorgang erstellt werden.

2. Objekte, die Funktionen sein sollten

Manchmal ist das Gegenteil der Fall. Es gibt objektorientierten Code, der für ein oder zwei einfache Funktionen besser geeignet wäre.

Hier sind einige verräterische Anzeichen für eine falsche Verwendung von Klassen:

  • Klassen mit 1 Methode (außer.__init__())

  • Klassen, die nur statische Methoden enthalten

Nehmen Sie dieses Beispiel einer Authentifizierungsklasse:

# authenticate.py

class Authenticator(object):
    def __init__(self, username, password):
        self.username = username
        self.password = password

    def authenticate(self):
        ...
        return result

Es wäre sinnvoller, nur eine einfache Funktion namensauthenticate() zu haben, dieusername undpassword als Argumente verwendet:

# authenticate.py

def authenticate(username, password):
    ...
    return result

Sie müssen sich nicht hinsetzen und manuell nach Klassen suchen, die diesen Kriterien entsprechen:pylint enthält die Regel, dass Klassen mindestens zwei öffentliche Methoden haben sollten. Weitere Informationen zu PyLint und anderen Tools zur Codequalität finden Sie unterPython Code Quality.

Führen Sie zum Installieren vonpylint den folgenden Befehl in Ihrer Konsole aus:

$ pip install pylint

pylint verwendet eine Reihe optionaler Argumente und dann den Pfad zu einer oder mehreren Dateien und Ordnern. Wenn Siepylint mit den Standardeinstellungen ausführen, wird viel ausgegeben, dapylint eine große Anzahl von Regeln enthält. Stattdessen können Sie bestimmte Regeln ausführen. Die Regel-ID vontoo-few-public-methodslautetR0903. Sie können dies auf dendocumentation website nachschlagen:

$ pylint --disable=all --enable=R0903 requests
************* Module requests.auth
requests/auth.py:72:0: R0903: Too few public methods (1/2) (too-few-public-methods)
requests/auth.py:100:0: R0903: Too few public methods (1/2) (too-few-public-methods)
************* Module requests.models
requests/models.py:60:0: R0903: Too few public methods (1/2) (too-few-public-methods)

-----------------------------------
Your code has been rated at 9.99/10

Diese Ausgabe sagt uns, dassauth.py 2 Klassen enthält, die nur 1 öffentliche Methode haben. Diese Klassen befinden sich in den Zeilen 72 und 100. Es gibt auch eine Klasse in Zeile 60 vonmodels.py mit nur einer öffentlichen Methode.

3. Konvertieren von "dreieckigem" Code in Flatcode

Wenn Sie Ihren Quellcode verkleinern und Ihren Kopf um 90 Grad nach rechts neigen, sieht das Leerzeichen flach aus wie Holland oder bergig wie der Himalaya? Gebirgscode ist ein Zeichen dafür, dass Ihr Code viel Verschachtelung enthält.

Hier ist eines der Prinzipien inZen of Python:

"Wohnung ist besser als verschachtelt"

- Tim Peters, Zen von Python

Warum sollte Flatcode besser sein als verschachtelter Code? Weil verschachtelter Code das Lesen und Verstehen des Geschehens erschwert. Der Leser muss die Bedingungen verstehen und auswendig lernen, wenn sie durch die Zweige gehen.

Dies sind die Symptome von stark verschachteltem Code:

  • Eine hohe zyklomatische Komplexität aufgrund der Anzahl der Codezweige

  • Ein niedriger Wartbarkeitsindex aufgrund der hohen zyklomatischen Komplexität im Verhältnis zur Anzahl der Codezeilen

Nehmen Sie dieses Beispiel, in dem das Argumentdata nach Zeichenfolgen durchsucht wird, die mit dem Worterror übereinstimmen. Zunächst wird geprüft, ob das Argumentdataeine Liste ist. Dann durchläuft es jeden einzelnen und prüft, ob das Element eine Zeichenfolge ist. Wenn es sich um eine Zeichenfolge handelt und der Wert"error" ist, wirdTrue zurückgegeben. Andernfalls wirdFalse zurückgegeben:

def contains_errors(data):
    if isinstance(data, list):
        for item in data:
            if isinstance(item, str):
                if item == "error":
                    return True
    return False

Diese Funktion hätte einen niedrigen Wartbarkeitsindex, weil sie klein ist, aber eine hohe zyklomatische Komplexität aufweist.

Stattdessen können wir diese Funktion umgestalten, indem wir "früh zurückkehren", um eine Verschachtelungsebene zu entfernen undFalse zurückzugeben, wenn der Wert vondata nicht aufgelistet ist. Verwenden Sie dann.count() für das Listenobjekt, um für Instanzen von"error" zu zählen. Der Rückgabewert ist dann eine Bewertung, dass.count() größer als Null ist:

def contains_errors(data):
    if not isinstance(data, list):
        return False
    return data.count("error") > 0

Eine andere Technik zur Reduzierung der Verschachtelung besteht darin, das Listenverständnis zu nutzen. Dieses allgemeine Muster besteht darin, eine neue Liste zu erstellen, jedes Element in einer Liste zu durchsuchen, um festzustellen, ob es einem Kriterium entspricht, und dann alle Übereinstimmungen zur neuen Liste hinzuzufügen:

results = []
for item in iterable:
    if item == match:
        results.append(item)

Dieser Code kann durch ein schnelleres und effizienteres Listenverständnis ersetzt werden.

Refaktorieren Sie das letzte Beispiel in ein Listenverständnis und eineif-Anweisung:

results = [item for item in iterable if item == match]

Dieses neue Beispiel ist kleiner, weniger komplex und leistungsfähiger.

Wenn Ihre Daten keine einzelne Dimensionsliste sind, können Sie das Paketitertoolsin der Standardbibliothek nutzen, die Funktionen zum Erstellen von Iteratoren aus Datenstrukturen enthält. Sie können es zum Verketten von Iterables, zum Zuordnen von Strukturen, zum Wechseln oder Wiederholen vorhandener Iterables verwenden.

Itertools enthält auch Funktionen zum Filtern von Daten wiefilterfalse(). Weitere Informationen zu Itertools finden Sie unterItertools in Python 3, By Example.

4. Umgang mit komplexen Wörterbüchern mit Abfrage-Tools

Einer der leistungsstärksten und am häufigsten verwendeten Kerntypen von Python ist das Wörterbuch. Es ist schnell, effizient, skalierbar und hochflexibel.

Wenn Sie neu in Wörterbüchern sind oder glauben, Sie könnten sie besser nutzen, können SieDictionaries in Python lesen, um weitere Informationen zu erhalten.

Es hat einen großen Nebeneffekt: Wenn Wörterbücher stark verschachtelt sind, wird auch der Code, der sie abfragt, verschachtelt.

Nehmen Sie dieses Beispieldaten, ein Beispiel der Metro-Linien in Tokio, die Sie zuvor gesehen haben:

data = {
 "network": {
  "lines": [
    {
     "name.en": "Ginza",
     "name.jp": "銀座線",
     "color": "orange",
     "number": 3,
     "sign": "G"
    },
    {
     "name.en": "Marunouchi",
     "name.jp": "丸ノ内線",
     "color": "red",
     "number": 4,
     "sign": "M"
    }
  ]
 }
}

Wenn Sie die Zeile erhalten möchten, die einer bestimmten Zahl entspricht, kann dies in einer kleinen Funktion erreicht werden:

def find_line_by_number(data, number):
    matches = [line for line in data if line['number'] == number]
    if len(matches) > 0:
        return matches[0]
    else:
        raise ValueError(f"Line {number} does not exist.")

Obwohl die Funktion selbst klein ist, ist das Aufrufen der Funktion unnötig kompliziert, da die Daten so verschachtelt sind:

>>>

>>> find_line_by_number(data["network"]["lines"], 3)

Es gibt Tools von Drittanbietern zum Abfragen von Wörterbüchern in Python. Einige der beliebtesten sindJMESPath,glom,asq undflupy.

JMESPath kann mit unserem Zugnetz helfen. JMESPath ist eine für JSON entwickelte Abfragesprache mit einem für Python verfügbaren Plugin, das mit Python-Wörterbüchern funktioniert. Gehen Sie wie folgt vor, um JMESPath zu installieren:

$ pip install jmespath

Öffnen Sie dann eine Python-REPL, um die JMESPath-API zu erkunden, und kopieren Sie sie in dasdata-Wörterbuch. Importieren Sie zunächstjmespath und rufen Siesearch() mit einer Abfragezeichenfolge als erstem Argument und den Daten als zweitem auf. Die Abfragezeichenfolge"network.lines" bedeutet Rückgabedata['network']['lines']:

>>>

>>> import jmespath

>>> jmespath.search("network.lines", data)
[{'name.en': 'Ginza', 'name.jp': '銀座線',
  'color': 'orange', 'number': 3, 'sign': 'G'},
 {'name.en': 'Marunouchi', 'name.jp': '丸ノ内線',
  'color': 'red', 'number': 4, 'sign': 'M'}]

Wenn Sie mit Listen arbeiten, können Sie eckige Klammern verwenden und eine Abfrage darin bereitstellen. Die Abfrage "Alles" lautet einfach*. Sie können dann den Namen des Attributs in jedes übereinstimmende Element einfügen, das zurückgegeben werden soll. Wenn Sie die Zeilennummer für jede Zeile erhalten möchten, können Sie dies tun:

>>>

>>> jmespath.search("network.lines[*].number", data)
[3, 4]

Sie können komplexere Abfragen wie== oder< bereitstellen. Die Syntax ist für Python-Entwickler etwas ungewöhnlich. Halten Sie daher diedocumentationals Referenz bereit.

Wenn wir die Zeile mit der Zahl3 finden wollten, kann dies in einer einzigen Abfrage erfolgen:

>>>

>>> jmespath.search("network.lines[?number==`3`]", data)
[{'name.en': 'Ginza', 'name.jp': '銀座線', 'color': 'orange', 'number': 3, 'sign': 'G'}]

Wenn wir die Farbe dieser Zeile erhalten möchten, können Sie das Attribut am Ende der Abfrage hinzufügen:

>>>

>>> jmespath.search("network.lines[?number==`3`].color", data)
['orange']

JMESPath kann verwendet werden, um Code zu reduzieren und zu vereinfachen, der komplexe Wörterbücher abfragt und durchsucht.

5. Verwenden vonattrs unddataclasses zum Reduzieren des Codes

Ein weiteres Ziel beim Refactoring besteht darin, einfach die Codemenge in der Codebasis zu reduzieren und gleichzeitig das gleiche Verhalten zu erzielen. Die bisher gezeigten Techniken können einen großen Beitrag zur Umgestaltung von Code in kleinere und einfachere Module leisten.

Einige andere Techniken erfordern Kenntnisse der Standardbibliothek und einiger Bibliotheken von Drittanbietern.

Was ist Boilerplate?

Boilerplate-Code ist Code, der an vielen Stellen mit geringen oder keinen Änderungen verwendet werden muss.

Wenn wir unser Zugnetz als Beispiel nehmen und es mithilfe von Python-Klassen und Python 3-Typhinweisen in Typen konvertieren, könnte es ungefähr so ​​aussehen:

from typing import List

class Line(object):
    def __init__(self, name_en: str, name_jp: str, color: str, number: int, sign: str):
        self.name_en = name_en
        self.name_jp = name_jp
        self.color = color
        self.number = number
        self.sign = sign

    def __repr__(self):
        return f""

    def __str__(self):
        return f"The {self.name_en} line"

class Network(object):
    def __init__(self, lines: List[Line]):
        self._lines = lines

    @property
    def lines(self) -> List[Line]:
        return self._lines

Jetzt möchten Sie vielleicht auch andere magische Methoden hinzufügen, wie.__eq__(). Dieser Code ist Boilerplate. Hier gibt es keine Geschäftslogik oder andere Funktionen: Wir kopieren nur Daten von einem Ort an einen anderen.

Ein Fall fürdataclasses

Das Datenklassenmodul wurde in die Standardbibliothek in Python 3.7 mit einem Backport-Paket für Python 3.6 auf PyPI eingeführt und kann dabei helfen, viele Boilerplates für diese Arten von Klassen zu entfernen, in denen Sie nur Daten speichern.

Um die oben genannteLine-Klasse in eine Datenklasse zu konvertieren, konvertieren Sie alle Felder in Klassenattribute und stellen Sie sicher, dass sie Typanmerkungen haben:

from dataclasses import dataclass

@dataclass
class Line(object):
    name_en: str
    name_jp: str
    color: str
    number: int
    sign: str

Sie können dann eine Instanz vom TypLine mit denselben Argumenten wie zuvor und denselben Feldern erstellen, und sogar.__str__(),.__repr__() und.__eq__() werden implementiert:

>>>

>>> line = Line('Marunouchi', "丸ノ内線", "red", 4, "M")

>>> line.color
red

>>> line2 = Line('Marunouchi', "丸ノ内線", "red", 4, "M")

>>> line == line2
True

Datenklassen sind eine großartige Möglichkeit, Code mit einem einzigen Import zu reduzieren, der bereits in der Standardbibliothek verfügbar ist. Für eine vollständige exemplarische Vorgehensweise können SieThe Ultimate Guide to Data Classes in Python 3.7 auschecken.

Einigeattrs Anwendungsfälle

attrs ist ein Paket von Drittanbietern, das viel länger als Datenklassen existiert. attrs bietet viel mehr Funktionen und ist in Python 2.7 und 3.4+ verfügbar.

Wenn Sie Python 3.5 oder niedriger verwenden, istattrs eine großartige Alternative zudataclasses. Außerdem bietet es viele weitere Funktionen.

Das Beispiel für äquivalente Datenklassen inattrs würde ähnlich aussehen. Anstatt Typanmerkungen zu verwenden, werden die Klassenattribute mit einem Wert vonattrib() zugewiesen. Dies kann zusätzliche Argumente wie Standardwerte und Rückrufe zum Überprüfen der Eingabe erfordern:

from attr import attrs, attrib

@attrs
class Line(object):
    name_en = attrib()
    name_jp = attrib()
    color = attrib()
    number = attrib()
    sign = attrib()

attrs kann ein nützliches Paket zum Entfernen von Boilerplate-Code und zur Eingabevalidierung für Datenklassen sein.

Fazit

Nachdem Sie gelernt haben, wie Sie komplizierten Code identifizieren und angehen können, denken Sie an die Schritte zurück, die Sie jetzt ausführen können, um Ihre Anwendung einfacher zu ändern und zu verwalten:

  • Erstellen Sie zunächst eine Basislinie Ihres Projekts mit einem Tool wiewily.

  • Schauen Sie sich einige der Metriken an und beginnen Sie mit dem Modul mit dem niedrigsten Wartbarkeitsindex.

  • Refaktorieren Sie dieses Modul mithilfe der in Tests bereitgestellten Sicherheit und der Kenntnis von Tools wie PyCharm undrope.

Wenn Sie diese Schritte und die Best Practices in diesem Artikel befolgen, können Sie andere aufregende Dinge an Ihrer Anwendung tun, z. B. neue Funktionen hinzufügen und die Leistung verbessern.