Skip to content

Python Style Guide (PEP 8)

Einführung

Dieses Dokument beschreibt die Coding Conventions für Python-Code in der Standard-Library. Es ist wichtig zu beachten, dass viele Projekte ihre eigenen Style Guides haben, die im Konfliktfall Vorrang haben.

Wichtigste Prinzipien

"Code is read much more often than it is written." - Guido van Rossum

  • Konsistenz ist wichtig, aber nicht absolut
  • Konsistenz innerhalb eines Projekts ist wichtiger als die Einhaltung dieses Guides
  • Konsistenz innerhalb eines Moduls oder einer Funktion ist am wichtigsten
  • Manchmal ist es sinnvoll, den Guide zu ignorieren

Gründe, den Guide zu ignorieren:

  1. Die Anwendung würde den Code weniger lesbar machen
  2. Um mit umgebendem Code konsistent zu bleiben
  3. Der Code ist älter als die Guideline
  4. Kompatibilität mit älteren Python-Versionen erforderlich

Code Layout

Einrückung

Verwende 4 Leerzeichen pro Einrückungsebene.

Fortsetzungszeilen - Korrekte Beispiele:

# Mit öffnender Klammer ausgerichtet
foo = long_function_name(var_one, var_two,
                         var_three, var_four)

# Zusätzliche Einrückung zur Unterscheidung von Argumenten
def long_function_name(
        var_one, var_two, var_three,
        var_four):
    print(var_one)

# Hanging indent
foo = long_function_name(
    var_one, var_two,
    var_three, var_four)

Fortsetzungszeilen - Falsche Beispiele:

# ❌ Argumente in erster Zeile ohne vertikale Ausrichtung
foo = long_function_name(var_one, var_two,
    var_three, var_four)

# ❌ Weitere Einrückung erforderlich
def long_function_name(
    var_one, var_two, var_three,
    var_four):
    print(var_one)

Mehrzeilige if-Statements:

# Keine zusätzliche Einrückung
if (this_is_one_thing and
    that_is_another_thing):
    do_something()

# Kommentar hinzufügen
if (this_is_one_thing and
    that_is_another_thing):
    # Since both conditions are true, we can frobnicate.
    do_something()

# Zusätzliche Einrückung auf der Fortsetzungszeile
if (this_is_one_thing
        and that_is_another_thing):
    do_something()

Schließende Klammern:

# Option 1: Unter dem ersten Nicht-Whitespace-Zeichen
my_list = [
    1, 2, 3,
    4, 5, 6,
    ]

# Option 2: Unter dem ersten Zeichen der Zeile
my_list = [
    1, 2, 3,
    4, 5, 6,
]

Tabs oder Leerzeichen?

Leerzeichen sind die bevorzugte Einrückungsmethode.

  • Tabs nur verwenden, um mit bereits mit Tabs eingerücktem Code konsistent zu bleiben
  • Python erlaubt keine Mischung von Tabs und Leerzeichen

Maximale Zeilenlänge

Begrenze alle Zeilen auf maximal 79 Zeichen.

  • Für fließende Textblöcke (Docstrings, Kommentare): 72 Zeichen
  • Teams können sich auf bis zu 99 Zeichen einigen (Docstrings/Kommentare bleiben bei 72)
  • Python Standard Library verwendet strikt 79 Zeichen

Zeilenumbrüche:

# Bevorzugt: Implizite Zeilenfortsetzung mit Klammern
with open('/path/to/some/file/you/want/to/read') as file_1, \
     open('/path/to/some/file/being/written', 'w') as file_2:
    file_2.write(file_1.read())

Sollte vor oder nach einem binären Operator umgebrochen werden?

Neu: Vor dem Operator (Knuth-Stil)

# ✅ Korrekt: Einfach Operatoren mit Operanden zu verbinden
income = (gross_wages
          + taxable_interest
          + (dividends - qualified_dividends)
          - ira_deduction
          - student_loan_interest)
# ❌ Falsch: Operatoren weit von ihren Operanden entfernt
income = (gross_wages +
          taxable_interest +
          (dividends - qualified_dividends) -
          ira_deduction -
          student_loan_interest)

Leerzeilen

  • Zwei Leerzeilen umgeben Top-Level-Funktionen und Klassendefinitionen
  • Eine Leerzeile umgibt Methodendefinitionen innerhalb einer Klasse
  • Verwende Leerzeilen sparsam innerhalb von Funktionen, um logische Abschnitte zu kennzeichnen

Source File Encoding

  • Core Python Distribution sollte immer UTF-8 verwenden
  • Keine Encoding-Deklaration verwenden
  • Alle Bezeichner in der Python Standard Library MÜSSEN ASCII-only sein

Imports

Grundregeln

# ✅ Korrekt: Imports auf separaten Zeilen
import os
import sys

# ✅ Auch okay:
from subprocess import Popen, PIPE
# ❌ Falsch:
import sys, os

Import-Reihenfolge

Imports sollten in folgender Reihenfolge gruppiert werden (mit Leerzeile zwischen Gruppen):

  1. Standard Library Imports
  2. Related Third-Party Imports
  3. Local Application/Library Specific Imports

Absolute vs. Relative Imports

# ✅ Bevorzugt: Absolute Imports
import mypkg.sibling
from mypkg import sibling
from mypkg.sibling import example

# ✅ Auch akzeptabel: Explizite relative Imports
from . import sibling
from .sibling import example

Weitere Import-Regeln

# ✅ Korrekt: Klassen aus Modulen importieren
from myclass import MyClass
from foo.bar.yourclass import YourClass

# Bei Namenskonflikten:
import myclass
import foo.bar.yourclass
# Dann: myclass.MyClass und foo.bar.yourclass.YourClass verwenden
  • Wildcard Imports vermeiden: from <module> import *
  • Nur akzeptabel beim Republishing eines internen Interfaces als Teil einer öffentlichen API

Module Level Dunder Names

"""This is the example module.

This module does stuff.
"""

from __future__ import barry_as_FLUFL

__all__ = ['a', 'b', 'c']
__version__ = '0.1'
__author__ = 'Cardinal Biggles'

import os
import sys

Reihenfolge: 1. Module Docstring 2. from __future__ imports 3. Dunder names (__all__, __author__, __version__, etc.) 4. Andere Imports


String Quotes

  • Single-quoted und double-quoted Strings sind gleich
  • Wähle eine Regel und bleibe dabei
  • Bei Strings mit Quotes: Verwende die andere Quote-Art, um Backslashes zu vermeiden
# ✅ Für Triple-Quoted Strings: Immer doppelte Anführungszeichen
"""This is a docstring."""

Whitespace in Ausdrücken und Statements

Pet Peeves (Vermeiden)

# ✅ Korrekt:
spam(ham[1], {eggs: 2})
foo = (0,)
if x == 4: print(x, y); x, y = y, x
ham[1:9], ham[1:9:3], ham[:9:3], ham[1::3], ham[1:9:]
ham[lower:upper], ham[lower:upper:], ham[lower::step]
spam(1)
dct['key'] = lst[index]
x = 1
y = 2
long_variable = 3
# ❌ Falsch:
spam( ham[ 1 ], { eggs: 2 } )
bar = (0, )
if x == 4 : print(x , y) ; x , y = y , x
ham[lower + offset:upper + offset]
ham[1: 9], ham[1 :9], ham[1:9 :3]
spam (1)
dct ['key'] = lst [index]
x             = 1
y             = 2
long_variable = 3

Slicing

# ✅ Korrekt:
ham[lower+offset : upper+offset]
ham[: upper_fn(x) : step_fn(x)], ham[:: step_fn(x)]
ham[lower + offset : upper + offset]

Andere Empfehlungen

Binäre Operatoren: Immer ein Leerzeichen auf beiden Seiten

# Assignments (=), Augmented Assignments (+=, -=, etc.)
# Comparisons (==, <, >, !=, <=, >=, in, not in, is, is not)
# Booleans (and, or, not)

Operatoren mit unterschiedlicher Priorität

# ✅ Korrekt:
i = i + 1
submitted += 1
x = x*2 - 1
hypot2 = x*x + y*y
c = (a+b) * (a-b)
# ❌ Falsch:
i=i+1
submitted +=1
x = x * 2 - 1
hypot2 = x * x + y * y
c = (a + b) * (a - b)

Funktionsannotationen

# ✅ Korrekt:
def munge(input: AnyStr): ...
def munge() -> PosInt: ...
# ❌ Falsch:
def munge(input:AnyStr): ...
def munge()->PosInt: ...

Keyword-Argumente und Default-Werte

# ✅ Korrekt: Keine Leerzeichen bei unannotated
def complex(real, imag=0.0):
    return magic(r=real, i=imag)

# ✅ Korrekt: Leerzeichen bei annotated mit Default
def munge(sep: AnyStr = None): ...
def munge(input: AnyStr, sep: AnyStr = None, limit=1000): ...
# ❌ Falsch:
def complex(real, imag = 0.0):
    return magic(r = real, i = imag)

def munge(input: AnyStr=None): ...
def munge(input: AnyStr, limit = 1000): ...

Compound Statements

# ✅ Korrekt:
if foo == 'blah':
    do_blah_thing()
do_one()
do_two()
do_three()
# ❌ Falsch:
if foo == 'blah': do_blah_thing()
do_one(); do_two(); do_three()

# ❌ Definitiv falsch:
if foo == 'blah': do_blah_thing()
else: do_non_blah_thing()

try: something()
finally: cleanup()

Trailing Commas

Wann verwenden?

# ✅ Korrekt: Singleton Tuple
FILES = ('setup.cfg',)
# ❌ Falsch:
FILES = 'setup.cfg',

Bei Version Control hilfreich:

# ✅ Korrekt:
FILES = [
    'setup.cfg',
    'tox.ini',
    ]
initialize(FILES,
           error=True,
           )
# ❌ Falsch:
FILES = ['setup.cfg', 'tox.ini',]
initialize(FILES, error=True,)

Kommentare

Wichtig: Widersprüchliche Kommentare sind schlimmer als keine Kommentare!

Allgemeine Regeln

  • Kommentare sollten vollständige Sätze sein
  • Erstes Wort großschreiben (außer bei Identifiern mit Kleinbuchstaben)
  • Block-Kommentare bestehen aus vollständigen Sätzen, die mit Punkt enden
  • Ein oder zwei Leerzeichen nach Satzende-Punkt in mehrsätzigen Kommentaren
  • Schreibe Kommentare auf Englisch, außer du bist 120% sicher, dass der Code nie von Nicht-Muttersprachlern gelesen wird

Block-Kommentare

# Block-Kommentare gelten für den folgenden Code
# Sie sind auf das gleiche Level wie der Code eingerückt
# Jede Zeile beginnt mit # und einem Leerzeichen

# Absätze innerhalb eines Block-Kommentars werden durch
# eine Zeile mit nur einem # getrennt
#
# Wie hier.

Inline-Kommentare

# Sparsam verwenden!
# Mindestens zwei Leerzeichen vom Statement trennen
x = x + 1  # Compensate for border

# ❌ Nicht so:
x = x + 1                 # Increment x

Documentation Strings (Docstrings)

Nach PEP 257:

# ✅ Für alle öffentlichen Module, Funktionen, Klassen und Methoden
def public_function():
    """Return a foobang.

    Optional plotz says to frobnicate the bizbaz first.
    """
    pass

# ✅ One-liner: Schließendes """ auf der gleichen Zeile
def short_function():
    """Return an ex-parrot."""
    pass
  • Private Methoden brauchen keine Docstrings, aber sollten einen Kommentar nach def haben
  • Das schließende """ bei mehrzeiligen Docstrings sollte auf einer eigenen Zeile stehen

Namenskonventionen

Naming Styles Übersicht

Style Verwendung
b Einzelner Kleinbuchstabe
B Einzelner Großbuchstabe
lowercase Kleinbuchstaben
lower_case_with_underscores Kleinbuchstaben mit Unterstrichen
UPPERCASE Großbuchstaben
UPPER_CASE_WITH_UNDERSCORES Großbuchstaben mit Unterstrichen
CapitalizedWords oder CapWords PascalCase / CamelCase
mixedCase camelCase (initial lowercase!)
Capitalized_Words_With_Underscores ⚠️ Hässlich!

Spezielle Formen mit Unterstrichen

  • _single_leading_underscore: Schwacher "internal use" Indikator
  • single_trailing_underscore_: Vermeidung von Konflikten mit Python Keywords
  • __double_leading_underscore: Name Mangling bei Klassen-Attributen
  • __double_leading_and_trailing_underscore__: "Magic" Objekte (z.B. __init__, __file__)

Zu vermeidende Namen

Niemals verwenden: l (lowercase L), O (uppercase O), oder I (uppercase i) als einzelne Zeichen-Variablennamen.

In manchen Schriften sind diese nicht von 1 und 0 zu unterscheiden.

Konkrete Benennungsregeln

Package und Module Namen

# ✅ Korrekt:
import mypackage
import my_module

# Module: kurz, kleingeschrieben, Unterstriche erlaubt
# Packages: kurz, kleingeschrieben, Unterstriche vermeiden

Klassennamen

# ✅ CapWords Convention
class MyClass:
    pass

class HTTPServerError:  # Akronyme komplett groß
    pass

Type Variable Namen

from typing import TypeVar

# ✅ CapWords, kurze Namen bevorzugt
T = TypeVar('T')
AnyStr = TypeVar('AnyStr')
Num = TypeVar('Num')

# Mit Co/Contravariance
VT_co = TypeVar('VT_co', covariant=True)
KT_contra = TypeVar('KT_contra', contravariant=True)

Exception Namen

# ✅ Klassen-Convention + "Error" Suffix
class ValidationError(Exception):
    pass

class DatabaseConnectionError(Exception):
    pass

Globale Variablennamen

# Gleiche Konvention wie Funktionen
global_variable = 42
GLOBAL_CONSTANT = 100

# Bei "from M import *": __all__ verwenden oder _ prefix
__all__ = ['public_function', 'PublicClass']
_internal_helper = "nicht exportiert"

Funktions- und Variablennamen

# ✅ lowercase mit Unterstrichen
def my_function():
    pass

my_variable = 42
another_variable = "test"

# mixedCase nur in bereits bestehenden mixedCase-Kontexten (z.B. threading.py)

Funktions- und Methoden-Argumente

# ✅ Immer 'self' für erste Instanzmethode
class MyClass:
    def instance_method(self, arg):
        pass

# ✅ Immer 'cls' für erste Klassenmethode
    @classmethod
    def class_method(cls, arg):
        pass

# Bei Keyword-Konflikt: Trailing Underscore
def function(class_):  # Besser als 'clss'
    pass

Methoden und Instance Variables

class MyClass:
    # ✅ Public: Lowercase mit Unterstrichen
    def public_method(self):
        pass

    public_attribute = "visible"

    # ✅ Non-public: Ein führender Unterstrich
    def _internal_method(self):
        pass

    _internal_attribute = "not for external use"

    # ✅ Name Mangling: Zwei führende Unterstriche
    # Verwendet um Konflikte mit Subklassen zu vermeiden
    def __private_method(self):
        pass

Konstanten

# ✅ Module-level: UPPERCASE mit Unterstrichen
MAX_OVERFLOW = 100
TOTAL = 42
DEFAULT_TIMEOUT = 30

Design für Vererbung

Entscheide immer, ob Attribute public oder non-public sein sollen:

  • Public Attributes: Keine führenden Unterstriche
  • Für externe Clients gedacht
  • Commitment zu Backward-Compatibility

  • Non-public Attributes: Ein führender Unterstrich

  • Nicht für Dritte gedacht
  • Keine Garantie für Stabilität

  • Subclass API: Möglicherweise zwei führende Unterstriche

  • Nur für spezielle Fälle (Name-Mangling)
  • Vermeidet Attribute-Namenskonflikte
class BaseClass:
    # Public
    public_attribute = "everyone can use"

    # Non-public (internal)
    _internal_attribute = "implementation detail"

    # Name mangling (vermeidet Subclass-Konflikte)
    __private_attribute = "very private"

    def public_method(self):
        """Public API method."""
        pass

    def _internal_method(self):
        """Internal helper method."""
        pass

Package- und Projektstruktur

Projekt-Layout Best Practices

my_project/
├── README.md
├── LICENSE
├── setup.py / pyproject.toml
├── requirements.txt
├── .gitignore
├── docs/
│   ├── conf.py
│   └── index.md
├── tests/
│   ├── __init__.py
│   ├── test_module1.py
│   └── test_module2.py
├── src/                    # Optional: "src layout"
│   └── mypackage/
│       ├── __init__.py
│       ├── module1.py
│       ├── module2.py
│       └── subpackage/
│           ├── __init__.py
│           └── submodule.py
└── mypackage/              # Alternative: "flat layout"
    ├── __init__.py
    ├── module1.py
    └── module2.py

Ordner- und Package-Namenskonventionen

Package-Namen (Verzeichnisse mit __init__.py)

# ✅ KORREKT:
mypackage/
my_package/
myapp/
dataprocessing/

# ❌ VERMEIDEN:
MyPackage/       # Keine Großbuchstaben
my-package/      # Keine Bindestriche (nicht importierbar)
My_Package/      # Keine gemischte Schreibweise

Regeln für Package-Namen: - Kurz und aussagekräftig - Nur Kleinbuchstaben - Unterstriche nur wenn nötig für Lesbarkeit - Keine Bindestriche (würde Python-Syntax brechen) - Muss ein gültiger Python-Identifier sein

Modul-Dateinamen

# ✅ KORREKT:
user_authentication.py
data_processor.py
api_client.py
utils.py
config.py

# ❌ VERMEIDEN:
UserAuthentication.py    # Keine CapWords für Module
data-processor.py        # Keine Bindestriche
API_Client.py           # Gemischte Schreibweise

Verzeichnisstruktur-Beispiele

Beispiel 1: Einfache Package-Struktur
myproject/
├── myproject/
│   ├── __init__.py
│   ├── core.py
│   ├── utils.py
│   ├── config.py
│   └── models/
│       ├── __init__.py
│       ├── user.py
│       └── product.py
├── tests/
│   ├── __init__.py
│   ├── test_core.py
│   └── test_utils.py
├── setup.py
└── README.md
Beispiel 2: Größeres Projekt mit Subpackages
webapp/
├── webapp/
│   ├── __init__.py
│   ├── api/
│   │   ├── __init__.py
│   │   ├── routes.py
│   │   ├── auth.py
│   │   └── validators.py
│   ├── database/
│   │   ├── __init__.py
│   │   ├── models.py
│   │   ├── connection.py
│   │   └── migrations/
│   │       ├── __init__.py
│   │       └── version_001.py
│   ├── services/
│   │   ├── __init__.py
│   │   ├── email_service.py
│   │   └── payment_service.py
│   └── utils/
│       ├── __init__.py
│       ├── logging.py
│       └── decorators.py
├── tests/
│   ├── unit/
│   │   └── test_services.py
│   └── integration/
│       └── test_api.py
├── config/
│   ├── development.py
│   ├── production.py
│   └── testing.py
├── docs/
├── requirements/
│   ├── base.txt
│   ├── dev.txt
│   └── prod.txt
└── setup.py
Beispiel 3: Src-Layout (empfohlen für Bibliotheken)
mylib/
├── src/
│   └── mylib/
│       ├── __init__.py
│       ├── core.py
│       └── utils.py
├── tests/
│   └── test_core.py
├── docs/
├── pyproject.toml
└── README.md

Vorteile des src-Layouts: - Verhindert versehentlichen Import aus dem Arbeitsverzeichnis - Zwingt zur korrekten Installation vor Tests - Klarere Trennung zwischen Source und Tests

Spezielle Verzeichnisse

Tests-Verzeichnis

tests/
├── __init__.py
├── conftest.py              # pytest fixtures
├── unit/
│   ├── __init__.py
│   ├── test_models.py
│   └── test_utils.py
├── integration/
│   ├── __init__.py
│   └── test_api.py
└── fixtures/
    ├── data.json
    └── sample_files/

Namenskonvention für Tests: - Dateien: test_*.py oder *_test.py - Test-Funktionen: test_* - Test-Klassen: Test*

Docs-Verzeichnis

docs/
├── conf.py                  # Sphinx config
├── index.rst
├── api/
│   ├── core.rst
│   └── utils.rst
├── tutorials/
│   └── getting_started.rst
└── _static/
    └── custom.css

Scripts/Tools-Verzeichnis

scripts/
├── setup_database.py
├── generate_docs.sh
└── deploy.py

Konfigurationsdateien

project/
├── .gitignore
├── .editorconfig
├── .flake8
├── pyproject.toml           # Modern (PEP 518)
├── setup.py                 # Falls benötigt
├── setup.cfg                # Falls benötigt
├── requirements.txt         # Oder:
├── requirements/
│   ├── base.txt
│   ├── dev.txt
│   └── prod.txt
├── Makefile
└── tox.ini

Private/Internal Packages

mypackage/
├── __init__.py
├── public_module.py
└── _internal/              # Unterstrich = internal
    ├── __init__.py
    ├── _helpers.py
    └── _vendored/          # Vendored dependencies
        └── thirdparty/

Namespace Packages (PEP 420)

# Ohne __init__.py für Namespace-Packages
namespace/
├── package1/
│   ├── __init__.py         # Reguläres Package
│   └── module.py
└── package2/
    ├── __init__.py
    └── module.py

# Beide können als namespace.package1 und namespace.package2 importiert werden

Häufige Fehler vermeiden

# ❌ VERMEIDEN:

# Bindestriche im Package-Namen
my-package/                 # Nicht importierbar!

# Konflikt mit Standard-Library
email/                      # Überschreibt stdlib 'email'
test/                       # Überschreibt stdlib 'test'

# Zu generische Namen
utils/                      # Zu vage
helpers/                    # Zu vage
common/                     # Zu vage

# Zu lange Namen
my_extremely_long_and_descriptive_package_name/

# ✅ BESSER:

# Klare, kurze Namen
myapp_email/               # Kein Konflikt
myapp_testing/             # Kein Konflikt

# Spezifischere Namen
string_utils/              # Was für Utils?
auth_helpers/              # Was für Helpers?
shared_models/             # Was ist gemeinsam?

# Angemessene Länge
myapp/

Import-Pfade optimieren

# Package-Struktur:
myapp/
├── __init__.py
├── models/
   ├── __init__.py
   ├── user.py
   └── product.py
└── services/
    ├── __init__.py
    └── auth.py

# In myapp/models/__init__.py:
from .user import User
from .product import Product

__all__ = ['User', 'Product']

# Erlaubt dann:
from myapp.models import User, Product
# Statt:
from myapp.models.user import User
from myapp.models.product import Product

Programming Recommendations

Python-Implementierung-Agnostisch

# ❌ Nicht verlassen auf CPython-Optimierung:
a = a + b  # Nur effizient in CPython bei bestimmten Typen

# ✅ Stattdessen für Performance-kritische Teile:
result = ''.join(string_list)

Vergleiche mit None

# ✅ Korrekt:
if x is None:
    pass

if x is not None:
    pass

# ❌ Falsch:
if x == None:
    pass

if not x is None:
    pass

Vorsicht bei truthiness-Tests

# ❌ Gefährlich:
if x:  # Was wenn x ein leerer Container ist?
    pass

# ✅ Wenn None gemeint ist, explizit sein:
if x is not None:
    pass

Rich Comparisons

# ✅ Alle sechs implementieren:
class MyClass:
    def __eq__(self, other):
        pass

    def __ne__(self, other):
        pass

    def __lt__(self, other):
        pass

    def __le__(self, other):
        pass

    def __gt__(self, other):
        pass

    def __ge__(self, other):
        pass

# Oder verwende functools.total_ordering Decorator
from functools import total_ordering

@total_ordering
class MyClass:
    def __eq__(self, other):
        pass

    def __lt__(self, other):
        pass

Lambda vs. def

# ✅ Korrekt:
def f(x):
    return 2*x

# ❌ Falsch:
f = lambda x: 2*x  # Name ist '<lambda>' in Tracebacks

Exception Hierarchien

# ✅ Von Exception ableiten, nicht BaseException
class ValidationError(Exception):
    pass

class ConfigError(Exception):
    pass

# Exception-Hierarchie basierend auf dem "Was ging schief?"
class DatabaseError(Exception):
    """Base class for database errors."""
    pass

class ConnectionError(DatabaseError):
    """Could not connect to database."""
    pass

class QueryError(DatabaseError):
    """Query execution failed."""
    pass

Exception Chaining

# ✅ Explizite Verkettung
try:
    do_something()
except SomeError as e:
    raise NewError("Context information") from e

# ✅ Explizites Unterdrücken
try:
    do_something()
except SomeError:
    raise NewError("New context") from None

Spezifische Exceptions fangen

# ✅ Korrekt:
try:
    import platform_specific_module
except ImportError:
    platform_specific_module = None

# ❌ Falsch:
try:
    import platform_specific_module
except:  # Fängt auch KeyboardInterrupt!
    platform_specific_module = None

Try/Except-Klauseln minimieren

# ✅ Korrekt:
try:
    value = collection[key]
except KeyError:
    return key_not_found(key)
else:
    return handle_value(value)

# ❌ Falsch:
try:
    # Zu breit!
    return handle_value(collection[key])
except KeyError:
    # Würde auch KeyError von handle_value() fangen
    return key_not_found(key)

Context Manager

# ✅ Für Ressourcen
with open('file.txt') as f:
    data = f.read()

# ✅ Explizit bei mehr als Resource Management
with conn.begin_transaction():
    do_stuff_in_transaction(conn)

# ❌ Implizit ist verwirrend
with conn:
    do_stuff_in_transaction(conn)

Return Statements

# ✅ Konsistent:
def foo(x):
    if x >= 0:
        return math.sqrt(x)
    else:
        return None

def bar(x):
    if x < 0:
        return None
    return math.sqrt(x)

# ❌ Inkonsistent:
def foo(x):
    if x >= 0:
        return math.sqrt(x)
    # Implizites return None

def bar(x):
    if x < 0:
        return  # Explizit ohne Wert
    return math.sqrt(x)

String-Methoden

# ✅ Korrekt:
if foo.startswith('bar'):
    pass

if foo.endswith('.txt'):
    pass

# ❌ Fehleranfällig:
if foo[:3] == 'bar':
    pass

if foo[-4:] == '.txt':
    pass

Typ-Vergleiche

# ✅ Korrekt:
if isinstance(obj, int):
    pass

# ❌ Falsch:
if type(obj) is type(1):
    pass

Leere Sequenzen

# ✅ Korrekt: Nutze die Truthiness
if not seq:
    pass

if seq:
    pass

# ❌ Falsch:
if len(seq):
    pass

if not len(seq):
    pass

Boolean-Vergleiche

# ✅ Korrekt:
if greeting:
    pass

# ❌ Falsch:
if greeting == True:
    pass

# ❌ Noch schlimmer:
if greeting is True:
    pass

Flow Control in finally

# ❌ VERMEIDEN: return/break/continue in finally
def foo():
    try:
        1 / 0
    finally:
        return 42  # Unterdrückt die Exception!

Function und Variable Annotations

Type Hints (PEP 484)

# ✅ Korrekt:
def greeting(name: str) -> str:
    return f'Hello {name}'

def process_data(items: List[int], threshold: float = 0.5) -> Dict[str, Any]:
    pass

# Variable Annotations
code: int
count: int = 0

class Point:
    coords: Tuple[int, int]
    label: str = '<unknown>'
# ❌ Falsch:
code:int  # Kein Leerzeichen nach Doppelpunkt
code : int  # Leerzeichen vor Doppelpunkt

class Test:
    result: int=0  # Keine Leerzeichen um =

Type Ignore

# Wenn andere Annotation-Verwendung gewünscht:
# type: ignore

# Am Anfang der Datei, wenn Type Hints nicht verwendet werden sollen

Tools für Code-Qualität

Linter und Formatter

# flake8: PEP 8 Checker
pip install flake8
flake8 myproject/

# black: Auto-Formatter (opinionated)
pip install black
black myproject/

# pylint: Umfassender Linter
pip install pylint
pylint myproject/

# mypy: Type Checker
pip install mypy
mypy myproject/

# isort: Import Sortierer
pip install isort
isort myproject/

Pre-commit Hooks

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/psf/black
    rev: 23.3.0
    hooks:
      - id: black

  - repo: https://github.com/pycqa/flake8
    rev: 6.0.0
    hooks:
      - id: flake8

  - repo: https://github.com/pycqa/isort
    rev: 5.12.0
    hooks:
      - id: isort

Zusammenfassung - Die wichtigsten Punkte

Code Layout

  • ✅ 4 Leerzeichen für Einrückung
  • ✅ Maximal 79 Zeichen pro Zeile (99 für Teams nach Absprache)
  • ✅ Umbruch vor binären Operatoren (Knuth-Stil)
  • ✅ 2 Leerzeilen zwischen Top-Level-Definitionen
  • ✅ 1 Leerzeile zwischen Methoden

Naming

  • lowercase_with_underscores für Funktionen und Variablen
  • CapWords für Klassen
  • UPPER_CASE_WITH_UNDERSCORES für Konstanten
  • _leading_underscore für non-public
  • ✅ Packages: kurz, lowercase, Unterstriche nur wenn nötig

Imports

  • ✅ Standard Library → Third-Party → Local
  • ✅ Absolute Imports bevorzugen
  • ✅ Ein Import pro Zeile
  • ✅ Keine Wildcard-Imports

Strings und Whitespace

  • ✅ Konsistent bei Quotes bleiben
  • ✅ """ für Docstrings
  • ✅ Kein Whitespace vor Kommas/Klammern
  • ✅ Leerzeichen um Operatoren (außer bei Prioritäten)

Best Practices

  • is / is not für None-Vergleiche
  • ✅ Spezifische Exceptions
  • ✅ Context Manager für Ressourcen
  • ✅ Type Hints für neue Projekte
  • ✅ Docstrings für Public API

Referenzen

  • PEP 8: https://peps.python.org/pep-0008/
  • PEP 257: Docstring Conventions
  • PEP 484: Type Hints
  • PEP 526: Variable Annotations
  • PEP 518: pyproject.toml
  • Python Packaging Guide: https://packaging.python.org/