40. Sauberen und gut lesbaren Code schreiben
Im Verlauf dieses Buches haben Sie Pythons Syntax, Datenstrukturen, Kontrollfluss, Funktionen, Klassen und viele andere Programmierkonzepte gelernt. Sie können jetzt Programme schreiben, die funktionieren. Aber es gibt einen entscheidenden Unterschied zwischen Code, der funktioniert, und Code, der wartbar ist—Code, den Sie und andere Monate oder Jahre später noch verstehen, ändern und debuggen können.
Dieses Kapitel konzentriert sich auf das Schreiben von sauberem, lesbarem Code. Sie lernen die Konventionen und Praktiken kennen, die Python-Code professionell und wartbar machen. Das sind nicht einfach willkürliche Regeln—es sind praxiserprobte Richtlinien, die Zusammenarbeit erleichtern, Bugs reduzieren und Ihnen helfen, Ihren eigenen Code zu verstehen, wenn Sie später wieder darauf zurückkommen.
40.1) Warum Stil wichtig ist: Code lesen vs. Code schreiben
40.1.1) Code wird öfter gelesen als geschrieben
Wenn Sie Code schreiben, verbringen Sie Minuten oder Stunden damit, ihn zu erstellen. Aber dieser Code wird viele Male gelesen: wenn Sie ihn debuggen, wenn Sie Features hinzufügen, wenn andere Entwickler damit arbeiten und wenn Sie Monate später wieder darauf zurückkommen und versuchen, sich daran zu erinnern, was er tut.
Betrachten Sie diesen funktionierenden, aber schlecht formatierten Code:
# WARNUNG: Schlechter Stil – nur zur Demonstration
def c(l):
t=0
for i in l:
t=t+i
return t/len(l)
data=[85,92,78,90,88]
result=c(data)
print(result) # Output: 86.6Dieser Code funktioniert perfekt. Er berechnet den Durchschnitt einer Liste von Zahlen. Aber zu verstehen, was er tut, erfordert eine sorgfältige Analyse. Vergleichen Sie das nun mit dieser Version:
def calculate_average(numbers):
"""Calculate the arithmetic mean of a list of numbers."""
total = 0
for number in numbers:
total = total + number
return total / len(numbers)
test_scores = [85, 92, 78, 90, 88]
average_score = calculate_average(test_scores)
print(average_score) # Output: 86.6Was macht die zweite Version besser?
- Der Funktionsname (
calculate_average) benennt den Zweck eindeutig - Die Variablennamen (
numbers,total,test_scores) sind aussagekräftig - Der Docstring erklärt, was die Funktion tut
- Korrekte Abstände machen die Struktur klar
- Jeder kann diesen Code verstehen, ohne ihn studieren zu müssen
Beide Versionen liefern identische Ergebnisse, aber die zweite Version ist sofort verständlich.
Die zentrale Erkenntnis: Sie schreiben Code einmal, aber Sie lesen ihn dutzende oder hunderte Male. Ein paar zusätzliche Sekunden in klare Benennung und Formatierung zu investieren, spart später Stunden an Verwirrung.
40.1.2) Lesbarkeit reduziert Bugs
Klarer Code ist leichter zu debuggen, weil Sie schnell verstehen können, was jeder Teil macht. Wenn Variablennamen beschreibend sind und die Struktur sauber ist, erkennen Sie Logikfehler leichter.
# Schwer zu debuggen – wofür stehen diese Variablen?
# WARNUNG: Schlechter Stil – nur zur Demonstration
def process(x, y):
if x > y:
return x * (1 - y)
return x
result = process(100, 0.1)# Leicht zu debuggen – klar, was passiert
def apply_discount(price, discount_rate):
"""Calculate price after applying discount rate (0.0 to 1.0)."""
discount_amount = price * discount_rate
final_price = price - discount_amount
return final_price
original_price = 100
discount = 0.1 # 10% Rabatt
final_price = apply_discount(original_price, discount)
print(f"Final price: ${final_price}")
# Output: Final price: $90.0In der zweiten Version sehen Sie die Logik sofort: „Wir berechnen einen Rabattbetrag und ziehen ihn dann vom Preis ab.“ In der ersten Version müssen Sie gedanklich nachverfolgen, wofür x und y stehen, und herausfinden, was x * (1 - y) bedeutet.
40.1.3) Konsistenz ermöglicht Zusammenarbeit
Wenn alle in einem Team dieselben Stilkonventionen befolgen, wird Code vorhersehbar. Sie verschwenden keine mentale Energie damit, verschiedene Formatierungsstile zu entschlüsseln—Sie können sich darauf konzentrieren, die Logik zu verstehen.
Python hat einen offiziellen Styleguide namens PEP 8 (Python Enhancement Proposal 8). PEP 8 definiert Konventionen für:
- Wie man Variablen, Funktionen und Klassen benennt
- Wie man Code formatiert (Abstände, Zeilenlänge, Einrückung)
- Wann man Kommentare und Docstrings verwendet
- Wie man Imports organisiert
Wenn Sie PEP 8 folgen, sieht Ihr Code für andere Python-Programmierer vertraut aus, was die Zusammenarbeit reibungsloser macht. Wir behandeln die wichtigsten PEP-8-Richtlinien in den nächsten Abschnitten.
40.2) Benennungskonventionen: Variablen, Funktionen und Klassen (PEP 8)
40.2.1) Allgemeine Benennungsprinzipien
Gute Namen sind beschreibend und eindeutig. Sie sollten Ihnen sagen, was etwas darstellt oder tut, ohne dass Sie die Implementierung lesen müssen.
Wichtige Prinzipien:
- Verwenden Sie vollständige Wörter, keine Abkürzungen (außer sehr gängigen wie
id,url,html) - Seien Sie spezifisch:
user_countist besser alscount,calculate_total_priceist besser alscalculate - Vermeiden Sie einbuchstabige Namen außer bei sehr kurzen Schleifen(loop) oder mathematischen Formeln
- Nehmen Sie keine Typinformationen in Namen auf (Python ist dynamisch typisiert)
# Schlechte Namen – unklar, wofür sie stehen
# WARNUNG: Schlechter Stil – nur zur Demonstration
# Was ist 'n'? Eine Zahl? Ein Name? Ein Knoten?
# Was ist 'd'? Ein Datum? Eine Distanz? Eine Dauer?
# Was ist 'l'? Sieht aus wie die Zahl 1!
n = "Alice"
d = 25
l = [1, 2, 3]
calc = lambda x: x * 2
# Gute Namen – klar und beschreibend
student_name = "Alice"
age_in_years = 25
test_scores = [1, 2, 3]
double_value = lambda x: x * 2Ausnahme: Kurze Schleifen-Variablen(loop variables)
# Akzeptabel: sehr kurz, klarer Kontext
for i in range(10):
print(i)
for x, y in coordinates:
distance = (x**2 + y**2) ** 0.5
# Aber bevorzugen Sie beschreibende Namen für Klarheit
for student_index in range(len(students)):
print(students[student_index])
for point_x, point_y in coordinates:
distance = (point_x**2 + point_y**2) ** 0.540.2.2) Variablen- und Funktionsnamen: snake_case
In Python verwenden Variablen und Funktionen snake_case: alles klein geschrieben, Wörter durch Unterstriche getrennt.
# Variablen
user_name = "Bob"
total_price = 99.99
is_valid = True
max_retry_count = 3
# Funktionen
def calculate_tax(amount, rate):
"""Calculate tax on a given amount."""
return amount * rate
def send_email_notification(recipient, message):
"""Send an email to the specified recipient."""
print(f"Sending to {recipient}: {message}")
# Verwendung der Funktionen
tax_amount = calculate_tax(100, 0.08)
send_email_notification("user@example.com", "Welcome!")Warum snake_case? Es ist sehr gut lesbar. Die Unterstriche schaffen klare Wortgrenzen, wodurch Namen leicht zu überblicken sind. Vergleichen Sie calculatetotalprice (schwer zu lesen) mit calculate_total_price (sofort klar).
40.2.3) Konstantennamen: UPPER_SNAKE_CASE
Konstanten(constants)—Werte, die sich während der Programmausführung nicht ändern sollten—verwenden UPPER_SNAKE_CASE: alles in Großbuchstaben mit Unterstrichen.
# Konstanten auf Modulebene
MAX_LOGIN_ATTEMPTS = 3
DEFAULT_TIMEOUT_SECONDS = 30
PI = 3.14159
DATABASE_URL = "postgresql://localhost/mydb"
def validate_password_length(password):
"""Check if password meets minimum length requirement."""
MIN_PASSWORD_LENGTH = 8 # Konstante innerhalb der Funktion
return len(password) >= MIN_PASSWORD_LENGTH
# Verwendung von Konstanten
if login_attempts > MAX_LOGIN_ATTEMPTS:
print("Account locked")Wichtig: Python hat keine eingebaute Syntax für Konstanten. Anders als einige Sprachen (wie const in JavaScript oder final in Java) hat Python keine Möglichkeit zu deklarieren, dass eine Variable nicht verändert werden kann.
Stattdessen verwenden Python-Programmierer eine Benennungskonvention, um die Absicht zu signalisieren:
UPPER_SNAKE_CASEbedeutet: „Ich beabsichtige, dass dies eine Konstante ist—ändern Sie sie nicht“- Es ist ein Kommunikationswerkzeug zwischen Programmierern, kein Sprachfeature
# Python hat keine Konstanten-Syntax – das ist nur eine normale Variable
MAX_LOGIN_ATTEMPTS = 3
# Python hindert Sie nicht daran, sie zu ändern
MAX_LOGIN_ATTEMPTS = 5 # ❌ Funktioniert technisch, verletzt aber die Konvention
# Die Benennungskonvention ist ein Signal über die ABSICHT:
# "Ich habe das in GROSSBUCHSTABEN benannt, um zu zeigen, dass ich nicht möchte, dass es geändert wird"Best Practice: Wenn sich ein Wert während der Programmausführung wirklich ändern muss, benennen Sie ihn nicht wie eine Konstante:
# Dieser Wert wird sich ändern – verwenden Sie Kleinbuchstaben
max_login_attempts = 3
max_login_attempts = 5 # ✅ OK – der Name zeigt an, dass er sich ändern kann
# Dieser Wert sollte sich nie ändern – verwenden Sie GROSSBUCHSTABEN
MAX_LOGIN_ATTEMPTS = 3
# Weisen Sie ihn später im Code nicht erneut zuDie Konvention hilft Programmierern, Ihre Absicht zu verstehen und Bugs zu vermeiden. Wenn Sie MAX_LOGIN_ATTEMPTS sehen, wissen Sie, dass Sie es nicht ändern sollten.
40.2.4) Klassennamen: PascalCase
Klassennamen(classes) verwenden PascalCase (auch CapWords genannt): Jedes Wort beginnt mit einem Großbuchstaben, keine Unterstriche.
# Klassendefinitionen
class Student:
"""Represent a student with name and grades."""
def __init__(self, name):
self.name = name
self.grades = []
class ShoppingCart:
"""Manage items in a shopping cart."""
def __init__(self):
self.items = []
def add_item(self, item):
"""Add an item to the cart."""
self.items.append(item)
class DatabaseConnection:
"""Handle database connection and queries."""
def __init__(self, url):
self.url = url
# Instanzen erstellen (Hinweis: Instanzen verwenden snake_case-Variablennamen)
student = Student("Alice")
shopping_cart = ShoppingCart()
db_connection = DatabaseConnection("localhost")Warum PascalCase für Klassen? Es unterscheidet Klassen visuell von Funktionen und Variablen. Wenn Sie Student() sehen, wissen Sie sofort, dass eine Instanz einer Klasse erstellt wird. Wenn Sie calculate_average() sehen, wissen Sie, dass eine Funktion aufgerufen wird.
40.2.5) Private und interne Namen: Führender Unterstrich
Namen, die mit einem einzelnen Unterstrich (_name) beginnen, kennzeichnen interne Verwendung—sie sind zur Nutzung innerhalb des Moduls oder der Klasse gedacht, nicht durch externen Code.
Python hat keine Syntax, um Methoden oder Attribute als „private“ zu markieren (anders als private in Java oder C++). Stattdessen verwendet Python eine Benennungskonvention mit einem führenden Unterstrich (_name), um die Absicht mitzuteilen.
Was _name bedeutet:
- „Das ist nur für interne Verwendung“
- „Ich habe das für die Verwendung innerhalb dieser Klasse/dieses Moduls erstellt, nicht für externen Code“
- „Das könnte sich in zukünftigen Versionen jederzeit ändern—verlassen Sie sich nicht darauf“
class BankAccount:
"""Represent a bank account with balance tracking."""
def __init__(self, account_number, initial_balance):
self.account_number = account_number
self._balance = initial_balance # Internes Attribut
def deposit(self, amount):
"""Add money to the account."""
if self._validate_amount(amount): # Interne Methode
self._balance += amount
def _validate_amount(self, amount):
"""Internal helper to validate transaction amounts."""
return amount > 0
def get_balance(self):
"""Return the current balance."""
return self._balance
# Verwendung der Klasse
account = BankAccount("12345", 1000)
account.deposit(500)
print(account.get_balance()) # Output: 1500
# Funktioniert technisch, verletzt aber die Konvention
print(account._balance)
# Output: 1500 (works, but you shouldn't do this!)
# Funktioniert technisch, verletzt aber die Konvention
result = account._validate_amount(100)
# Output: True (works, but you shouldn't do this!)Kernpunkt: Python kann nicht verhindern, dass Sie auf _balance zugreifen oder _validate_amount() aufrufen. Der Unterstrich ist ein Signal zwischen Programmierern, kein Sicherheitsfeature.
Warum diese Konvention existiert
Da Python Privatsphäre nicht erzwingen kann, ist der Unterstrich die Art, wie Autoren von Klassen ihre Absicht kommunizieren:
Was der Unterstrich signalisiert:
- „Das ist interne Implementierung—sie könnte sich in zukünftigen Versionen ändern“
- „Verwenden Sie stattdessen die öffentlichen Methoden—die bleiben garantiert stabil“
- „Wenn Sie von internen Details abhängen, könnte Ihr Code brechen, wenn ich die Library aktualisiere“
Die Konvention schafft einen Vertrag: Autoren von Klassen können die interne Implementierung (alles mit _) frei ändern, müssen aber die öffentliche Schnittstelle stabil halten. So können sich Libraries weiterentwickeln, ohne User-Code zu brechen.
40.2.6) Spezielle Namen: Doppelte Unterstriche
Namen mit doppelten führenden und nachgestellten Unterstrichen (__name__) sind spezielle Methoden oder Magic Methods (magische Methoden) von Python. Erstellen Sie keine eigenen Namen mit diesem Muster—es ist für Pythons Verwendung reserviert.
class Point:
"""Represent a 2D point."""
def __init__(self, x, y): # Spezialmethode: Initialisierung
self.x = x
self.y = y
def __str__(self): # Spezialmethode: String-Darstellung
return f"Point({self.x}, {self.y})"
def __add__(self, other): # Spezialmethode: Additionsoperator
return Point(self.x + other.x, self.y + other.y)
p1 = Point(1, 2)
p2 = Point(3, 4)
print(p1) # Output: Point(1, 2)
print(p1 + p2) # Output: Point(4, 6)Wie wir in Kapitel 31 gelernt haben, ermöglichen diese Spezialmethoden Operator-Überladung und die Integration mit Pythons eingebauten Funktionen.
40.2.7) Zusammenfassung der Benennung in einer Tabelle
| Typ | Konvention | Beispiel |
|---|---|---|
| Variablen | snake_case | user_name, total_count |
| Funktionen | snake_case | calculate_tax(), send_email() |
| Konstanten | UPPER_SNAKE_CASE | MAX_SIZE, DEFAULT_TIMEOUT |
| Klassen | PascalCase | Student, ShoppingCart |
| Intern/Privat | _leading_underscore | _balance, _validate() |
| Spezial/Magic | double_underscore | __init__, __str__ |
40.3) Code-Layout: Einrückung, Abstände und Leerzeilen
40.3.1) Einrückung: Vier Leerzeichen
Python verwendet Einrückung, um Codeblöcke zu definieren. Verwenden Sie immer 4 Leerzeichen pro Einrückungsebene—niemals Tabs, und mischen Sie niemals Tabs und Leerzeichen.
def calculate_grade(score):
"""Determine letter grade from numeric score."""
if score >= 90:
return "A"
elif score >= 80:
return "B"
elif score >= 70:
return "C"
else:
return "F"
# Verschachtelte Einrückung: 4 Leerzeichen pro Ebene
def process_students(students):
"""Process a list of student records."""
for student in students:
if student["active"]:
grade = calculate_grade(student["score"])
print(f"{student['name']}: {grade}")
students = [
{"name": "Alice", "score": 92, "active": True},
{"name": "Bob", "score": 78, "active": True}
]
process_students(students)
# Output:
# Alice: A
# Bob: CWarum 4 Leerzeichen? Das ist der Standard in der Python-Community. Die meiste Python-Codebasis, die Sie antreffen, verwendet 4 Leerzeichen, und das Befolgen dieser Konvention macht Ihren Code konsistent mit dem Ökosystem.
Ihren Editor konfigurieren: Moderne Code-Editoren können so eingestellt werden, dass sie 4 Leerzeichen einfügen, wenn Sie Tab drücken. So bekommen Sie den Komfort der Tab-Taste und halten gleichzeitig den 4-Leerzeichen-Standard ein.
40.3.2) Maximale Zeilenlänge: 79 Zeichen
PEP 8 empfiehlt, Zeilen auf 79 Zeichen zu begrenzen (mit bis zu 99 Zeichen für Docstrings und Kommentare). Das kann einschränkend wirken, hat aber praktische Vorteile:
- Code bleibt auf kleineren Bildschirmen gut lesbar
- Sie können zwei Dateien nebeneinander anzeigen
- Es fördert das Aufteilen komplexer Ausdrücke in einfachere Teile
Hinweis: Viele moderne Projekte verwenden etwas längere Limits (88, 100 oder 120 Zeichen). Entscheidend ist die Konsistenz innerhalb Ihres Projekts. Wählen Sie ein Limit und halten Sie es ein.
# Zu lang – schwer zu lesen
# WARNUNG: Schlechter Stil – nur zur Demonstration
def calculate_monthly_payment(principal, annual_rate, years):
return principal * (annual_rate / 12) * (1 + annual_rate / 12) ** (years * 12) / ((1 + annual_rate / 12) ** (years * 12) - 1)
# Besser – in lesbare Zeilen aufgeteilt
def calculate_monthly_payment(principal, annual_rate, years):
"""Calculate monthly loan payment using amortization formula."""
monthly_rate = annual_rate / 12
num_payments = years * 12
numerator = principal * monthly_rate * (1 + monthly_rate) ** num_payments
denominator = (1 + monthly_rate) ** num_payments - 1
return numerator / denominator
payment = calculate_monthly_payment(200000, 0.045, 30)
print(f"Monthly payment: ${payment:.2f}") # Output: Monthly payment: $1013.37Lange Zeilen umbrechen: Wenn Sie eine Zeile umbrechen müssen, verwenden Sie implizite Zeilenfortsetzung innerhalb von Klammern, eckigen Klammern oder geschweiften Klammern:
# Langer Funktionsaufruf
result = some_function(
first_argument,
second_argument,
third_argument,
fourth_argument
)
# Lange Liste
colors = [
"red", "green", "blue",
"yellow", "orange", "purple",
"pink", "brown", "gray"
]
# Langer String
message = (
"This is a very long message that needs to be broken "
"across multiple lines for readability. Python automatically "
"concatenates adjacent string literals."
)
print(message)
# Output: This is a very long message that needs to be broken across multiple lines for readability. Python automatically concatenates adjacent string literals.40.3.3) Abstände um Operatoren und nach Kommas
Verwenden Sie Leerzeichen um Operatoren und nach Kommas, um die Lesbarkeit zu verbessern:
# Schlechte Abstände – gequetscht und schwer zu lesen
# WARNUNG: Schlechter Stil – nur zur Demonstration
x=5
y=x*2+3
result=calculate_tax(100,0.08)
data=[1,2,3,4,5]
# Gute Abstände – klar und lesbar
x = 5
y = x * 2 + 3
result = calculate_tax(100, 0.08)
data = [1, 2, 3, 4, 5]
# Abstände in Ausdrücken
total = (price * quantity) + shipping_cost
is_valid = (age >= 18) and (has_license == True)
# Abstände in Funktionsdefinitionen
def calculate_discount(price, discount_rate, minimum_purchase=0):
"""Calculate discounted price if minimum purchase is met."""
if price >= minimum_purchase:
return price * (1 - discount_rate)
return priceAusnahme: Verwenden Sie keine Leerzeichen um = in Keyword-Argumenten oder Default-Parameterwerten:
# Korrekte Abstände bei Keyword-Argumenten
result = calculate_discount(price=100, discount_rate=0.1, minimum_purchase=50)
# Korrekte Abstände bei Default-Parametern
def greet(name, greeting="Hello"):
return f"{greeting}, {name}!"40.3.4) Leerzeilen zur logischen Trennung
Verwenden Sie Leerzeilen, um logische Abschnitte des Codes zu trennen:
Zwei Leerzeilen zwischen Funktionen und Klassen auf Top-Level:
def first_function():
"""First function."""
pass
def second_function():
"""Second function."""
pass
class MyClass:
"""A class definition."""
passEine Leerzeile zwischen Methoden innerhalb einer Klasse:
class Student:
"""Represent a student with grades."""
def __init__(self, name):
self.name = name
self.grades = []
def add_grade(self, grade):
"""Add a grade to the student's record."""
self.grades.append(grade)
def get_average(self):
"""Calculate the student's grade average."""
if not self.grades:
return 0
return sum(self.grades) / len(self.grades)Leerzeilen innerhalb von Funktionen, um logische Schritte zu trennen:
def process_order(order_items, customer):
"""Process a customer order and calculate total."""
# Zwischensumme berechnen
subtotal = 0
for item in order_items:
subtotal += item["price"] * item["quantity"]
# Kundenrabatt anwenden
discount = 0
if customer["is_premium"]:
discount = subtotal * 0.1
# Steuer berechnen
tax = (subtotal - discount) * 0.08
# Endsumme berechnen
total = subtotal - discount + tax
return {
"subtotal": subtotal,
"discount": discount,
"tax": tax,
"total": total
}Diese Leerzeilen wirken wie visuelle „Absätze“ und machen die Code-Struktur sofort erkennbar.
40.3.5) Nachgestellte Leerzeichen vermeiden
Lassen Sie am Zeilenende keine Leerzeichen stehen—sie sind unsichtbar, können aber Probleme mit Versionskontrollsystemen und einigen Editoren verursachen.
# Schlecht – unsichtbare nachgestellte Leerzeichen (zur Illustration als · angezeigt)
# WARNUNG: Schlechter Stil – nur zur Demonstration
def calculate(x):···
return x * 2···
# Gut – keine nachgestellten Leerzeichen
def calculate(x):
return x * 2Die meisten modernen Editoren können so konfiguriert werden, dass sie nachgestellte Leerzeichen beim Speichern einer Datei automatisch entfernen.
40.4) Dokumentation: Hilfreiche Kommentare und Docstrings schreiben
40.4.1) Wann man Kommentare schreibt
Kommentare erklären, warum Code etwas tut, nicht was er tut. Gut benannte Variablen und Funktionen sollten das „was“ offensichtlich machen.
# Schlechter Kommentar – sagt das Offensichtliche
# WARNUNG: Schlechter Stil – nur zur Demonstration
x = x + 1 # Add 1 to x
# Guter Kommentar – erklärt warum
x = x + 1 # Anpassung für nullbasierte Indizierung
# Schlechter Kommentar – redundant zum Code
# WARNUNG: Schlechter Stil – nur zur Demonstration
# Prüfen, ob age größer oder gleich 18 ist
if age >= 18:
print("Adult")
# Guter Kommentar – erklärt die Business-Logik
# Gesetzliches Trinkalter in den USA
if age >= 21:
print("Can purchase alcohol")Wann Kommentare wertvoll sind:
- Komplexe Algorithmen erklären:
def binary_search(sorted_list, target):
"""Search for target in sorted list using binary search."""
left = 0
right = len(sorted_list) - 1
while left <= right:
# Mittelpunkt berechnen und Integer-Overflow vermeiden
# (right + left) // 2 könnte bei sehr großen Indizes überlaufen
mid = left + (right - left) // 2
if sorted_list[mid] == target:
return mid
elif sorted_list[mid] < target:
left = mid + 1 # Target liegt in der rechten Hälfte
else:
right = mid - 1 # Target liegt in der linken Hälfte
return -1 # Target nicht gefunden- Nicht offensichtliche Business-Regeln klären:
def calculate_shipping_cost(weight, distance):
"""Calculate shipping cost based on weight and distance."""
base_cost = 5.00
# Gratisversand-Aktion für schwere Artikel (Firmenrichtlinie ab 2024)
# Das fördert Sammelbestellungen und reduziert die Versandkosten pro Einheit
if weight > 50:
return 0
# Standardtarif: $0.50 pro Pfund plus $0.10 pro Meile
# Basierend auf dem im Q1 2024 ausgehandelten Vertrag mit dem Versanddienstleister
return base_cost + (weight * 0.50) + (distance * 0.10)- Workarounds oder temporäre Lösungen dokumentieren:
def process_data(data):
"""Process incoming data records."""
# TODO: Dies ist ein temporärer Fix für fehlerhafte Datensätze
# Entfernen, sobald die Datenvalidierung upstream implementiert ist
if not isinstance(data, list):
data = [data]
for record in data:
# Jeden Datensatz verarbeiten
pass40.4.2) Effektive Docstrings schreiben
Docstrings sind spezielle Kommentare, die Module, Klassen und Funktionen dokumentieren. Sie stehen in dreifachen Anführungszeichen und erscheinen als erste Anweisung in der Definition.
def calculate_bmi(weight_kg, height_m):
"""
Calculate Body Mass Index (BMI).
BMI is calculated as weight in kilograms divided by the square of height in meters.
Args:
weight_kg: Weight in kilograms (float or int)
height_m: Height in meters (float or int)
Returns:
float: The calculated BMI value
Example:
>>> calculate_bmi(70, 1.75)
22.857142857142858
"""
return weight_kg / (height_m ** 2)
# Auf Docstrings zugreifen
print(calculate_bmi.__doc__)
# Output:
# Calculate Body Mass Index (BMI).
#
# BMI is calculated as weight in kilograms divided by the square of height in meters.
# ...Einzeilige Docstrings für einfache Funktionen:
def square(x):
"""Return the square of x."""
return x * x
def is_even(n):
"""Return True if n is even, False otherwise."""
return n % 2 == 0Mehrzeilige Docstrings für komplexe Funktionen:
def find_prime_factors(n):
"""
Find all prime factors of a positive integer.
This function returns a list of prime numbers that, when multiplied
together, equal the input number. The factors are returned in ascending order.
Args:
n: A positive integer greater than 1
Returns:
list: Prime factors in ascending order
Raises:
ValueError: If n is less than 2
Example:
>>> find_prime_factors(12)
[2, 2, 3]
>>> find_prime_factors(17)
[17]
"""
if n < 2:
raise ValueError("n must be at least 2")
factors = []
divisor = 2
while n > 1:
while n % divisor == 0:
factors.append(divisor)
n = n // divisor
divisor += 1
return factorsKlassendocstrings:
class BankAccount:
"""
Represent a bank account with deposit and withdrawal operations.
This class maintains an account balance and provides methods for
depositing and withdrawing money. All transactions are validated to prevent negative balances.
Attributes:
account_number: Unique identifier for the account
balance: Current account balance in dollars
"""
def __init__(self, account_number, initial_balance=0):
"""
Initialize a new bank account.
Args:
account_number: Unique account identifier (string)
initial_balance: Starting balance (default: 0)
"""
self.account_number = account_number
self.balance = initial_balance
def deposit(self, amount):
"""
Add money to the account.
Args:
amount: Amount to deposit (must be positive)
Raises:
ValueError: If amount is not positive
"""
if amount <= 0:
raise ValueError("Deposit amount must be positive")
self.balance += amount40.4.3) Docstring-Konventionen
Erste Zeile: Kurze Zusammenfassung dessen, was die Funktion/Klasse tut. Sollte in eine Zeile passen.
Leerzeile: Trennt die Zusammenfassung von der detaillierten Beschreibung.
Detaillierte Beschreibung: Erklärt, was die Funktion macht, wichtige Details und wie man sie verwendet.
Args/Parameters: Listet jeden Parameter mit seinem Typ und Zweck auf.
Returns: Beschreibt, was die Funktion zurückgibt, und seinen Typ.
Raises: Dokumentiert alle Exceptions, die die Funktion auslösen kann.
Example: Zeigt typische Verwendung (optional, aber hilfreich).
def calculate_compound_interest(principal, rate, time, compounds_per_year=1):
"""
Calculate compound interest on an investment.
Uses the compound interest formula: A = P(1 + r/n)^(nt)
where A is the final amount, P is principal, r is annual rate,
n is compounds per year, and t is time in years.
Args:
principal: Initial investment amount (float)
rate: Annual interest rate as decimal (e.g., 0.05 for 5%)
time: Investment period in years (float)
compounds_per_year: Number of times interest compounds annually
(default: 1 for annual compounding)
Returns:
float: Final amount after compound interest
Example:
>>> calculate_compound_interest(1000, 0.05, 10, 12)
1647.0095
"""
return principal * (1 + rate / compounds_per_year) ** (compounds_per_year * time)40.4.4) TODO-Kommentare für zukünftige Arbeit
Verwenden Sie TODO-Kommentare, um Stellen zu markieren, die später Aufmerksamkeit benötigen:
def process_payment(amount, payment_method):
"""Process a payment transaction."""
# TODO: Unterstützung für Kryptowährungszahlungen hinzufügen
# TODO: Betrugserkennungsprüfungen implementieren
if payment_method == "credit_card":
return process_credit_card(amount)
elif payment_method == "paypal":
return process_paypal(amount)
else:
raise ValueError(f"Unsupported payment method: {payment_method}")Viele Editoren können nach TODO-Kommentaren suchen, wodurch es leicht ist, Stellen zu finden, die noch Arbeit brauchen.
40.5) Ihren Code organisieren: Imports, Konstanten, Funktionen und Main
40.5.1) Standard-Modulstruktur
Ein gut organisiertes Python-Modul folgt dieser Struktur:
- Modul-Docstring: Beschreibt, was das Modul tut
- Imports: Standardbibliothek, Third-Party, dann lokale Imports
- Konstanten: Konstanten auf Modulebene
- Funktionen und Klassen: Hauptcode
- Main-Ausführungsblock: Code, der läuft, wenn das Skript ausgeführt wird
"""
student_manager.py
Manage student records including grades and GPA calculations.
This module provides functions for adding students, recording grades,
and calculating grade point averages.
"""
# Imports aus der Standardbibliothek
import sys
from datetime import datetime
# Third-Party-Imports (falls vorhanden)
# import requests
# Lokale Imports (falls vorhanden)
# from .database import save_student
# Konstanten
MAX_GRADE = 100
MIN_GRADE = 0
PASSING_GRADE = 60
# Funktionen
def calculate_gpa(grades):
"""
Calculate GPA from a list of numeric grades.
Args:
grades: List of numeric grades (0-100)
Returns:
float: GPA on 4.0 scale
"""
if not grades:
return 0.0
average = sum(grades) / len(grades)
# Umrechnung auf 4.0-Skala
if average >= 90:
return 4.0
elif average >= 80:
return 3.0
elif average >= 70:
return 2.0
elif average >= 60:
return 1.0
else:
return 0.0
def validate_grade(grade):
"""
Check if a grade is within valid range.
Args:
grade: Numeric grade to validate
Returns:
bool: True if grade is valid, False otherwise
"""
return MIN_GRADE <= grade <= MAX_GRADE
# Main-Ausführung
if __name__ == "__main__":
# Code, der läuft, wenn das Skript direkt ausgeführt wird
test_grades = [85, 92, 78, 88]
gpa = calculate_gpa(test_grades)
print(f"GPA: {gpa}") # Output: GPA: 3.040.5.2) Import-Organisation
Gruppieren Sie Imports in drei Abschnitte, getrennt durch Leerzeilen:
- Standardbibliothek-Imports: Eingebaute Python-Module
- Third-Party-Imports: Installierte Pakete (wie
requests,numpy) - Lokale Imports: Ihre eigenen Module
# Imports aus der Standardbibliothek
import os
import sys
from datetime import datetime, timedelta
from pathlib import Path
# Third-Party-Imports
import requests
from flask import Flask, render_template
# Lokale Anwendungs-Imports
from myapp.database import connect_db
from myapp.models import User, Product
from myapp.utils import format_currencyImport-Stile:
# Ganzes Modul importieren
import math
result = math.sqrt(16) # Output: 4.0
# Spezifische Elemente importieren
from math import sqrt, pi
result = sqrt(16) # Output: 4.0
# Import mit Alias
import numpy as np
array = np.array([1, 2, 3])
# Mehrere Elemente importieren
from os import path, getcwd, listdirVermeiden Sie Wildcard-Imports (from module import *)—sie machen unklar, woher Namen kommen:
# Schlecht – unklar, woher sqrt kommt
# WARNUNG: Schlechter Stil – nur zur Demonstration
from math import *
result = sqrt(16)
# Gut – expliziter Import
from math import sqrt
result = sqrt(16)40.5.3) Konstanten organisieren
Platzieren Sie Konstanten auf Modulebene nahe am Anfang, nach den Imports:
"""Configuration settings for the application."""
import os
# Anwendungskonstanten
APP_NAME = "Student Manager"
VERSION = "1.0.0"
DEBUG_MODE = True
# Datenbankkonfiguration
DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///students.db")
MAX_CONNECTIONS = 10
# Business-Regeln
MAX_STUDENTS_PER_CLASS = 30
PASSING_GRADE = 60
GRADE_WEIGHTS = {
"homework": 0.3,
"midterm": 0.3,
"final": 0.4
}
def calculate_final_grade(homework, midterm, final):
"""Calculate weighted final grade."""
return (
homework * GRADE_WEIGHTS["homework"] +
midterm * GRADE_WEIGHTS["midterm"] +
final * GRADE_WEIGHTS["final"]
)40.5.4) Logische Reihenfolge von Funktionen
Organisieren Sie Funktionen in einer logischen Reihenfolge:
- Öffentliche Funktionen zuerst: Funktionen, die von anderen Modulen genutzt werden sollen
- Hilfsfunktionen danach: Interne Funktionen, die öffentliche unterstützen
- Zusammengehörige Funktionen zusammen: Gruppieren Sie Funktionen, die zusammenarbeiten
"""Order processing module."""
# Öffentliche API-Funktionen
def process_order(order_items, customer):
"""
Process a customer order.
This is the main entry point for order processing.
"""
subtotal = _calculate_subtotal(order_items)
discount = _calculate_discount(subtotal, customer)
tax = _calculate_tax(subtotal - discount)
total = subtotal - discount + tax
return {
"subtotal": subtotal,
"discount": discount,
"tax": tax,
"total": total
}
def validate_order(order_items):
"""Validate that an order contains valid items."""
if not order_items:
return False
for item in order_items:
if not _validate_item(item):
return False
return True
# Interne Hilfsfunktionen
def _calculate_subtotal(items):
"""Calculate order subtotal (internal use)."""
total = 0
for item in items:
total += item["price"] * item["quantity"]
return total
def _calculate_discount(subtotal, customer):
"""Calculate customer discount (internal use)."""
if customer.get("is_premium"):
return subtotal * 0.1
return 0
def _calculate_tax(amount):
"""Calculate sales tax (internal use)."""
TAX_RATE = 0.08
return amount * TAX_RATE
def _validate_item(item):
"""Validate a single order item (internal use)."""
required_fields = ["name", "price", "quantity"]
return all(field in item for field in required_fields)Beachten Sie, wie die öffentlichen Funktionen (process_order, validate_order) zuerst kommen und die Hilfsfunktionen (mit _ als Präfix) danach. Das macht deutlich, welche Funktionen die Haupt-API sind.
40.5.5) Klassenorganisation innerhalb von Modulen
Wenn ein Modul Klassen enthält, organisieren Sie sie logisch:
"""User management system."""
# Konstanten
DEFAULT_ROLE = "user"
ADMIN_ROLE = "admin"
# Basisklassen zuerst
class User:
"""Base user class."""
def __init__(self, username, email):
self.username = username
self.email = email
self.role = DEFAULT_ROLE
def can_edit(self, resource):
"""Check if user can edit a resource."""
return resource.owner == self.username
# Abgeleitete Klassen nach den Basisklassen
class AdminUser(User):
"""Administrator with elevated privileges."""
def __init__(self, username, email):
super().__init__(username, email)
self.role = ADMIN_ROLE
def can_edit(self, resource):
"""Admins can edit any resource."""
return True
# Zusammengehörige Klassen gruppieren
class Resource:
"""Represent a resource that can be owned and edited."""
def __init__(self, name, owner):
self.name = name
self.owner = owner
# Hilfsfunktionen im Zusammenhang mit Klassen
def create_user(username, email, is_admin=False):
"""Factory function to create appropriate user type."""
if is_admin:
return AdminUser(username, email)
return User(username, email)Prinzipien der Klassenorganisation:
- Basisklassen vor abgeleiteten Klassen (Leser müssen die Basis zuerst verstehen)
- Zusammengehörige Klassen zusammen gruppieren (User und Resource sind verwandt)
- Hilfsfunktionen, die mit Klassen arbeiten, kommen nach den Klassendefinitionen
- Jede Klasse sollte einen klaren Docstring haben, der ihren Zweck erklärt
40.6) Das Muster if name == "main"
40.6.1) Das Muster verstehen
Jede Python-Datei hat eine eingebaute Variable namens __name__. Python setzt den Wert dieser Variable automatisch, je nachdem, wie die Datei verwendet wird:
- Wenn Sie eine Datei direkt ausführen (z. B.
python my_script.py), setzt Python__name__auf"__main__" - Wenn Sie eine Datei als Modul importieren, setzt Python
__name__auf den Namen des Moduls (den Dateinamen ohne.py)
Damit können Sie Code schreiben, der nur läuft, wenn die Datei direkt ausgeführt wird, nicht wenn sie importiert wird:
"""math_utils.py - Mathematical utility functions."""
def add(a, b):
"""Add two numbers."""
return a + b
def multiply(a, b):
"""Multiply two numbers."""
return a * b
# Dieser Code läuft nur, wenn die Datei direkt ausgeführt wird
if __name__ == "__main__":
# Die Funktionen testen
print(f"5 + 3 = {add(5, 3)}") # Output: 5 + 3 = 8
print(f"5 * 3 = {multiply(5, 3)}") # Output: 5 * 3 = 15Wenn Sie python math_utils.py ausführen, sehen Sie die Ausgabe. Aber wenn Sie es in einer anderen Datei importieren:
# another_file.py
from math_utils import add, multiply
result = add(10, 20)
print(result) # Output: 30
# Der Testcode aus math_utils.py läuft NICHTBeachten Sie, dass der Testcode (innerhalb von if __name__ == "__main__":) beim Import NICHT ausgeführt wird!
40.6.2) Warum dieses Muster wichtig ist
Dieses Muster erfüllt mehrere wichtige Zwecke:
1. Testen und Demonstration: Sie können Beispielverwendung in derselben Datei wie Ihre Funktionen einbauen:
"""temperature.py - Temperature conversion utilities."""
def celsius_to_fahrenheit(celsius):
"""Convert Celsius to Fahrenheit."""
return (celsius * 9/5) + 32
def fahrenheit_to_celsius(fahrenheit):
"""Convert Fahrenheit to Celsius."""
return (fahrenheit - 32) * 5/9
if __name__ == "__main__":
# Die Funktionen demonstrieren
print("Temperature Conversion Examples:")
print(f"0°C = {celsius_to_fahrenheit(0)}°F") # Output: 0°C = 32.0°F
print(f"100°C = {celsius_to_fahrenheit(100)}°F") # Output: 100°C = 212.0°F
print(f"32°F = {fahrenheit_to_celsius(32)}°C") # Output: 32°F = 0.0°C2. Wiederverwendbare Module: Dieselbe Datei kann sowohl ein eigenständiges Skript als auch ein importierbares Modul sein:
"""data_processor.py - Process and analyze data files."""
import sys
def load_data(filename):
"""Load data from a file."""
with open(filename) as f:
return [line.strip() for line in f]
def analyze_data(data):
"""Perform analysis on data."""
return {
"count": len(data),
"average_length": sum(len(item) for item in data) / len(data)
}
if __name__ == "__main__":
# Wenn als Skript ausgeführt, Command-Line-Argumente verarbeiten
if len(sys.argv) < 2:
print("Usage: python data_processor.py <filename>")
sys.exit(1)
filename = sys.argv[1]
data = load_data(filename)
results = analyze_data(data)
print(f"Processed {results['count']} items")
print(f"Average length: {results['average_length']:.2f}")Sie können das als Skript ausführen:
$ python data_processor.py data.txt
Processed 42 items
Average length: 15.23Oder es in einer anderen Datei importieren:
# my_analysis.py
from data_processor import load_data, analyze_data
my_data = load_data("myfile.txt")
results = analyze_data(my_data)
print(f"Found {results['count']} items")40.6.3) Häufige Muster für Main-Blöcke
Muster 1: Einfache Testfälle
"""calculator.py - Basic calculator operations."""
def add(a, b):
"""Add two numbers."""
return a + b
def subtract(a, b):
"""Subtract b from a."""
return a - b
if __name__ == "__main__":
# Schnelle Tests
assert add(2, 3) == 5
assert subtract(10, 4) == 6
print("All tests passed!") # Output: All tests passed!Muster 2: Main-Funktion
Für komplexere Skripte definieren Sie eine main()-Funktion:
"""report_generator.py - Generate reports from data."""
import sys
def load_data(filename):
"""Load data from file."""
# Implementation here
pass
def generate_report(data):
"""Generate report from data."""
# Implementation here
pass
def save_report(report, output_file):
"""Save report to file."""
# Implementation here
pass
def main():
"""Main entry point for the script."""
if len(sys.argv) < 3:
print("Usage: python report_generator.py <input> <output>")
return 1
input_file = sys.argv[1]
output_file = sys.argv[2]
try:
data = load_data(input_file)
report = generate_report(data)
save_report(report, output_file)
print(f"Report saved to {output_file}")
return 0
except Exception as e:
print(f"Error: {e}")
return 1
if __name__ == "__main__":
# Exit with status code from main (0 = success, 1 = error)
sys.exit(main())Dieses Muster hat mehrere Vorteile:
- Die
main()-Funktion kann unabhängig getestet werden - Klarer Einstiegspunkt für das Skript
- Korrekte Exit-Codes (0 für Erfolg, ungleich 0 für Fehler)
- Saubere Trennung zwischen Skriptlogik und Modul-Funktionen
40.6.4) Best Practices für Main-Blöcke
Halten Sie Main-Blöcke fokussiert: Der Code innerhalb von if __name__ == "__main__" sollte hauptsächlich die Skriptausführung behandeln und keine komplexe Logik enthalten:
# Schlecht – komplexe Logik im Main-Block
# WARNUNG: Schlechter Stil – nur zur Demonstration
if __name__ == "__main__":
data = []
for i in range(100):
if i % 2 == 0:
data.append(i * 2)
result = sum(data) / len(data)
print(result)
# Gut – Logik in Funktionen, Main-Block koordiniert
def generate_even_doubles(limit):
"""Generate doubled even numbers up to limit."""
return [i * 2 for i in range(limit) if i % 2 == 0]
def calculate_average(numbers):
"""Calculate average of numbers."""
return sum(numbers) / len(numbers)
if __name__ == "__main__":
data = generate_even_doubles(100)
result = calculate_average(data)
print(result) # Output: 99.0Verwenden Sie eine main()-Funktion für komplexe Skripte: Wie zuvor gezeigt, macht das Definieren einer main()-Funktion Ihr Skript besser testbar und organisierter.
Dokumentieren Sie die Skriptverwendung: Wenn Ihr Skript Command-Line-Argumente akzeptiert, dokumentieren Sie sie im Modul-Docstring:
"""
file_processor.py - Process text files with various operations.
Usage:
python file_processor.py <input_file> <output_file> [--uppercase]
Arguments:
input_file: Path to input file
output_file: Path to output file
--uppercase: Convert text to uppercase (optional)
"""
import sys
def process_file(input_path, output_path, uppercase=False):
"""Process file with specified options."""
with open(input_path) as f:
content = f.read()
if uppercase:
content = content.upper()
with open(output_path, 'w') as f:
f.write(content)
if __name__ == "__main__":
if len(sys.argv) < 3:
print(__doc__) # Print the module docstring
sys.exit(1)
input_file = sys.argv[1]
output_file = sys.argv[2]
uppercase = "--uppercase" in sys.argv
process_file(input_file, output_file, uppercase)
print(f"Processed {input_file} -> {output_file}")Sauberen, gut lesbaren Code zu schreiben ist eine Fähigkeit, die sich mit Übung entwickelt. Die Konventionen und Muster in diesem Kapitel sind keine willkürlichen Regeln—sie sind bewährte Praktiken, die Code leichter verständlich, wartbar und debugbar machen. Wenn Sie mehr Python-Code schreiben, werden Ihnen diese Muster in Fleisch und Blut übergehen.
Denken Sie daran: Code wird weit öfter gelesen als geschrieben. Die paar zusätzlichen Sekunden, die Sie damit verbringen, einen klaren Namen zu wählen, einen hilfreichen Kommentar hinzuzufügen oder Ihre Imports ordentlich zu organisieren, sparen später Stunden an Verwirrung—für Sie selbst und für andere, die mit Ihrem Code arbeiten.
Im nächsten Kapitel schauen wir uns Debugging- und Testtechniken an, die auf diesen Clean-Code-Praktiken aufbauen und Ihnen helfen, nicht nur lesbaren Code zu schreiben, sondern korrekten und zuverlässigen Code.