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:
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:
-
Die Art der Analyse (
cc
für die zyklomatische Komplexität) -
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 wie
if
,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.py
treten 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:
-
if
-
(
-
)
-
>
-
:
Darüber hinaus gibt es 2 Operanden:
-
sys.argv
-
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:
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:
Sammeln von Daten zu Ihrem Projekt
Nachdem Sie den Quellcode vonrequests
analysiert 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.py
Folgendes 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:
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:
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:
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:
-
Mithilfe eines Editor-Plugins fürVisual Studio Code,Emacs oderVim
-
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 TypProject
und 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:
-
requests/api.py
gelöscht undrequests/new_api.py
erstellt -
requests/__init__.py
wurde geändert, um vonnew_api
anstelle vonapi
zu importieren -
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:
-
Extrahieren Sie Variablen aus einer Anweisung
-
Extrahieren Sie Methoden aus einem Codeblock
-
Sortieren Sie Importe in eine logische Reihenfolge
Hier ist ein Beispiel für die Verwendung des BefehlsExtract methods aus der Befehlspalette:
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:
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:
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:
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:
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:
-
Es ist nicht klar, ob
crop_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. -
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 Halstead
h2
unique 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, dass
thumbnail
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-methods
lautetR0903
. 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 Argumentdata
eine 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 wie
wily
. -
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 und
rope
.
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.