Python & AI Tutorials Logo
Python Programmierung

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:

python
# 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.6

Dieser 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:

python
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.6

Was 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.

python
# 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)
python
# 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.0

In 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.

Ja

Nein

Code schreiben

Styleguide befolgen?

Konsistenter, lesbarer Code

Inkonsistenter Code

Leicht zu verstehen

Leicht zu warten

Leicht zusammenzuarbeiten

Mentale Zusatzbelastung

Verwirrung

Bugs

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_count ist besser als count, calculate_total_price ist besser als calculate
  • 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)
python
# 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 * 2

Ausnahme: Kurze Schleifen-Variablen(loop variables)

python
# 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.5

40.2.2) Variablen- und Funktionsnamen: snake_case

In Python verwenden Variablen und Funktionen snake_case: alles klein geschrieben, Wörter durch Unterstriche getrennt.

python
# 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.

python
# 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_CASE bedeutet: „Ich beabsichtige, dass dies eine Konstante ist—ändern Sie sie nicht“
  • Es ist ein Kommunikationswerkzeug zwischen Programmierern, kein Sprachfeature
python
# 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:

python
# 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 zu

Die 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.

python
# 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“
python
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.

python
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

TypKonventionBeispiel
Variablensnake_caseuser_name, total_count
Funktionensnake_casecalculate_tax(), send_email()
KonstantenUPPER_SNAKE_CASEMAX_SIZE, DEFAULT_TIMEOUT
KlassenPascalCaseStudent, ShoppingCart
Intern/Privat_leading_underscore_balance, _validate()
Spezial/Magicdouble_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.

python
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: C

Warum 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.

python
# 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.37

Lange Zeilen umbrechen: Wenn Sie eine Zeile umbrechen müssen, verwenden Sie implizite Zeilenfortsetzung innerhalb von Klammern, eckigen Klammern oder geschweiften Klammern:

python
# 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:

python
# 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 price

Ausnahme: Verwenden Sie keine Leerzeichen um = in Keyword-Argumenten oder Default-Parameterwerten:

python
# 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:

python
def first_function():
    """First function."""
    pass
 
 
def second_function():
    """Second function."""
    pass
 
 
class MyClass:
    """A class definition."""
    pass

Eine Leerzeile zwischen Methoden innerhalb einer Klasse:

python
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:

python
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.

python
# 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 * 2

Die 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.

python
# 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:

  1. Komplexe Algorithmen erklären:
python
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
  1. Nicht offensichtliche Business-Regeln klären:
python
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)
  1. Workarounds oder temporäre Lösungen dokumentieren:
python
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
        pass

40.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.

python
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:

python
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 == 0

Mehrzeilige Docstrings für komplexe Funktionen:

python
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 factors

Klassendocstrings:

python
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 += amount

40.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).

python
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:

python
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:

  1. Modul-Docstring: Beschreibt, was das Modul tut
  2. Imports: Standardbibliothek, Third-Party, dann lokale Imports
  3. Konstanten: Konstanten auf Modulebene
  4. Funktionen und Klassen: Hauptcode
  5. Main-Ausführungsblock: Code, der läuft, wenn das Skript ausgeführt wird
python
"""
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.0

40.5.2) Import-Organisation

Gruppieren Sie Imports in drei Abschnitte, getrennt durch Leerzeilen:

  1. Standardbibliothek-Imports: Eingebaute Python-Module
  2. Third-Party-Imports: Installierte Pakete (wie requests, numpy)
  3. Lokale Imports: Ihre eigenen Module
python
# 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_currency

Import-Stile:

python
# 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, listdir

Vermeiden Sie Wildcard-Imports (from module import *)—sie machen unklar, woher Namen kommen:

python
# 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:

python
"""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:

  1. Öffentliche Funktionen zuerst: Funktionen, die von anderen Modulen genutzt werden sollen
  2. Hilfsfunktionen danach: Interne Funktionen, die öffentliche unterstützen
  3. Zusammengehörige Funktionen zusammen: Gruppieren Sie Funktionen, die zusammenarbeiten
python
"""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:

python
"""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:

python
"""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 = 15

Wenn Sie python math_utils.py ausführen, sehen Sie die Ausgabe. Aber wenn Sie es in einer anderen Datei importieren:

python
# 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 NICHT

Beachten Sie, dass der Testcode (innerhalb von if __name__ == "__main__":) beim Import NICHT ausgeführt wird!

python math_utils.py

import math_utils

Python-Datei wird ausgeführt

Wie wird sie gestartet?

name = 'main'

name = 'math_utils'

Code in if name == 'main' läuft

Code in if name == 'main' wird übersprungen

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:

python
"""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°C

2. Wiederverwendbare Module: Dieselbe Datei kann sowohl ein eigenständiges Skript als auch ein importierbares Modul sein:

python
"""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:

bash
$ python data_processor.py data.txt
Processed 42 items
Average length: 15.23

Oder es in einer anderen Datei importieren:

python
# 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

python
"""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:

python
"""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:

python
# 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.0

Verwenden 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:

python
"""
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.

© 2025. Primesoft Co., Ltd.
support@primesoft.ai