Skip to content

Python Spickzettel

Vielseitige, lesbare Sprache für Web, Daten, KI und Automatisierung.

01

Erste Schritte

Hello World & Kommentare

Python verwendet # für Kommentare und dreifache Anführungszeichen für Docstrings. Die Funktion print() unterstützt die Parameter sep und end, um die Ausgabeformatierung anzupassen. Docstrings dienen als Dokumentation, auf die über help() und __doc__ zugegriffen wird.

python
# This is a single-line comment
"""
This is a
multi-line comment (docstring)
"""
print("Hello, World!")        # print to stdout
print("A", "B", "C", sep="-") # A-B-C
print("No newline", end="")   # suppress newline

Einrückung & Codeblöcke

Im Gegensatz zu den meisten Sprachen verwendet Python Einrückung statt geschweifter Klammern, um Codeblöcke zu definieren. Konsistenz ist entscheidend – das Mischen von Tabs und Leerzeichen verursacht SyntaxError. PEP 8 empfiehlt 4 Leerzeichen pro Ebene.

python
# Python uses indentation (4 spaces) to define blocks
if True:
    print("inside if")
    if True:
        print("nested block")
print("outside block")

# No braces! Indentation IS the syntax
def func():
    x = 1
    return x + 1

Eingabe & Ausgabe

input() liest von stdin als String – konvertieren Sie immer, wenn Sie eine Zahl benötigen. Verwenden Sie int(), float() usw. für die Konvertierung. F-Strings (Python 3.6+) sind die bevorzugte Methode zum Formatieren von Strings.

python
# input() always returns a string
name = input("Enter your name: ")
age = int(input("Enter your age: "))  # convert to int

print(f"Hello {name}, you are {age} years old")

# formatted output
print("Pi is approximately {:.2f}".format(3.14159))
print(f"{1000000:,}")  # 1,000,000 with thousands separator

Mehrere Anweisungen & Zeilenfortsetzung

Verwenden Sie Semikolons, um Anweisungen in einer Zeile zu trennen (selten in idiomatischem Python). Lange Zeilen können mit Backslash oder automatisch innerhalb von (), [], {} fortgesetzt werden. Bevorzugen Sie implizite Fortsetzung für bessere Lesbarkeit.

python
# multiple statements on one line (discouraged)
a = 1; b = 2; c = 3

# explicit line continuation
total = 1 + 2 + 3 + \
        4 + 5 + 6

# implicit continuation inside brackets
nums = [
    1, 2, 3,
    4, 5, 6
]

result = (1 + 2
          + 3 + 4)

Python-Ausführung

Python-Skripte werden mit 'python script.py' ausgeführt. Die REPL ermöglicht interaktives Experimentieren. Verwenden Sie auf Systemen, auf denen python auf Python 2 verweist, immer python3. Die Shebang-Zeile macht Skripte unter Unix ausführbar.

python
# Run a script
# $ python script.py

# Run interactively (REPL)
# $ python
# >>> 2 + 2
# 4

# Shebang line for Unix scripts
#!/usr/bin/env python3

# Check Python version
import sys
print(sys.version)
print(sys.version_info.major)  # 3
02

Variablen & Datentypen

Variablen & dynamische Typisierung

Python verwendet dynamische Typisierung – Variablen können ihren Typ zur Laufzeit ändern. Verwenden Sie type() zum Prüfen, isinstance() zum Verifizieren. Python 3.6+ unterstützt Typhinweise (name: str = 'Alice') für IDE-Unterstützung ohne Laufzeit-Erzwingung.

python
# Python is dynamically typed - no declaration needed
name = "Alice"          # str
age = 30                # int
height = 5.7            # float
is_active = True        # bool
items = [1, 2, 3]       # list

# Type checking
print(type(name))       # <class 'str'>
print(isinstance(age, int))  # True

# Multiple assignment
x, y, z = 1, 2, 3
a = b = 0  # chain assignment

Typhinweise (Python 3.6+)

Typhinweise verbessern die Codelesbarkeit und ermöglichen IDE-Autovervollständigung sowie statische Analyse mit mypy. Sie werden zur Laufzeit NICHT erzwungen – Python bleibt dynamisch typisiert. Verwenden Sie Optional[X] für Werte, die None sein können.

python
# Variable annotations
name: str = "Alice"
age: int = 30
scores: list[float] = [90.5, 85.0]

# Function annotations
def greet(name: str, times: int = 1) -> str:
    return (f"Hi {name}! " * times).strip()

# Optional and Union
from typing import Optional, Union
def find(id: int) -> Optional[str]:
    return "Alice" if id == 1 else None

# mypy for static type checking
# $ mypy script.py

Typkonvertierung

Python hat eingebaute Konvertierungsfunktionen: int(), float(), str(), bool(), list(), tuple(), set(), dict(). Falsy-Werte umfassen 0, '', [], {}, None, False. int() schneidet gegen null ab, während round() Banker's Rounding verwendet.

python
# String to number
num_str = str(42)          # "42"
num = int("42")            # 42
float_num = float("3.14")  # 3.14

# Number conversions
print(int(3.99))           # 3 (truncates toward zero)
print(int(-3.99))          # -3
print(round(3.14159, 2))   # 3.14

# Boolean conversion
print(bool(0))             # False
print(bool(""))            # False
print(bool([]))            # False
print(bool("anything"))    # True

# Collection conversions
print(list("abc"))         # ['a', 'b', 'c']
print(tuple([1, 2, 3]))    # (1, 2, 3)
print(set([1, 1, 2]))      # {1, 2}

Numerische Typen

Python-Ints haben beliebige Genauigkeit (kein Überlauf). Floats sind IEEE 754 Doubles mit den üblichen Genauigkeitsproblemen. Komplexe Zahlen sind eingebaut. Booleans sind eine Unterklasse von int (True==1, False==0). Verwenden Sie Unterstriche in numerischen Literalen für bessere Lesbarkeit.

python
# Integers (arbitrary precision)
big = 10 ** 100  # no overflow
print(type(big))  # <class 'int'>

# Floats (IEEE 754 double)
pi = 3.14159
print(0.1 + 0.2)  # 0.30000000000000004

# Complex numbers
z = 3 + 4j
print(z.real, z.imag)  # 3.0 4.0
print(abs(z))          # 5.0

# Boolean is subclass of int
print(isinstance(True, int))  # True
print(True + True)            # 2

# Underscores in numbers (3.6+)
million = 1_000_000
binary = 0b_1010_1010

Konstanten & Namenskonventionen

Python hat kein const-Schlüsselwort – ALL_CAPS-Namen sind nur per Konvention Konstanten (nichts verhindert Neuzuweisung). PEP 8 definiert Namensgebung: snake_case für Variablen/Funktionen, PascalCase für Klassen, ALL_CAPS für Konstanten. Führender Unterstrich bedeutet per Konvention 'privat'; doppelter Unterstrich löst Name Mangling aus.

python
# Python has no true constants - convention only
MAX_SIZE = 100          # ALL_CAPS for constants
PI = 3.14159

# Naming conventions (PEP 8)
variable_name = "snake_case"      # variables, functions
ClassName = "PascalCase"          # classes
CONSTANT_VALUE = 100              # constants
_private_var = "underscore prefix" # private (convention)
__name_mangled = "double underscore" # name mangling

# dunder names (reserved)
__name__, __main__, __init__
03

Strings

String-Methoden

Strings sind unveränderlich – Methoden geben neue Strings zurück. find() gibt -1 zurück, wenn nicht gefunden, während index() ValueError auslöst. Verwenden Sie isalpha()/isdigit()/isalnum() zur Validierung. Die str-Klasse hat über 40 Methoden – erkunden Sie mit dir(str).

python
s = "Hello, World"

# Case operations
print(s.upper())          # HELLO, WORLD
print(s.lower())          # hello, world
print(s.title())          # Hello, World
print(s.capitalize())     # Hello, world
print(s.swapcase())       # hELLO, wORLD

# Search & replace
print(s.find("World"))    # 7 (index, -1 if not found)
print(s.index("World"))   # 7 (raises ValueError if not found)
print(s.replace("o", "0"))  # Hell0, W0rld
print(s.count("l"))       # 3

# Validation
print("abc".isalpha())    # True
print("123".isdigit())    # True
print("  ".isspace())     # True

String-Formatierung

F-Strings sind die moderne, schnellste und lesbarste Methode zum Formatieren von Strings. Sie unterstützen Format-Spezifikationen nach einem Doppelpunkt: :.2f für 2 Dezimalstellen, :>10 für rechtsbündig mit Breite 10, :, für Tausendertrennzeichen. Vermeiden Sie %-Formatierung in neuem Code.

python
name = "Alice"
age = 30

# f-strings (Python 3.6+) - PREFERRED
print(f"Hello, {name}! You are {age}.")
print(f"{name.upper()} is {age * 365} days old")
print(f"{3.14159:.2f}")      # 3.14
print(f"{42:>10}")           # right-align
print(f"{42:<10}")           # left-align
print(f"{42:^10}")           # center
print(f"{1000000:,}")        # 1,000,000

# str.format() method
print("Hello, {}!".format(name))
print("{name} is {age}".format(name="Bob", age=25))

# Old style (avoid in new code)
print("Hello, %s!" % name)

Slicing & Indizierung

Die Python-Slicing-Syntax [start:stop:step] ist mächtig – stop ist exklusiv. Negative Indizes zählen vom Ende. s[::-1] ist die idiomatische Methode, einen String umzukehren. Slicing ist sicher: Indizes außerhalb des Bereichs geben leere Strings zurück, statt Fehler auszulösen.

python
s = "Hello, World"

# Indexing (0-based, negative from end)
print(s[0])        # H
print(s[-1])       # d
print(s[7])        # W

# Slicing [start:stop:step]
print(s[0:5])      # Hello
print(s[7:])       # World
print(s[:5])       # Hello
print(s[::2])      # HloWrd (every 2nd char)
print(s[::-1])     # dlroW ,olleH (reverse!)

# Length
print(len(s))      # 12

# Slicing never raises IndexError
print(s[100:200])  # '' (empty string)

Teilen & Verbinden

split() teilt einen String in eine Liste, join() verbindet eine Liste zu einem String. Verwenden Sie immer join() für effiziente Verkettung vieler Strings – der +-Operator erstellt Zwischenstrings. partition() teilt in genau 3 Teile (vor, Trennzeichen, nach).

python
# Split
csv = "a,b,c,d"
print(csv.split(","))          # ['a', 'b', 'c', 'd']
print(csv.split(",", 2))       # ['a', 'b', 'c,d'] (max 2 splits)

# Splitlines
text = "line1\nline2\nline3"
print(text.splitlines())       # ['line1', 'line2', 'line3']

# Partition (splits on first occurrence)
print("[email protected]".partition("@"))
# ('user', '@', 'domain.com')

# Join
words = ["Hello", "World"]
print(" ".join(words))         # Hello World
print("-".join(["2024", "01", "15"]))  # 2024-01-15
print("".join(["a", "b", "c"])) # abc

# String concatenation
s = "Hello" + " " + "World"
parts = ["a"]
parts += "b"  # NOT string concat - adds chars to list!

Trimmen & Auffüllen

strip() entfernt standardmäßig führende/abschließende Leerzeichen oder angegebene Zeichen. zfill() füllt mit führenden Nullen auf (nützlich für IDs). rjust/ljust/center füllen auf eine angegebene Breite mit einem optionalen Füllzeichen auf.

python
# Strip whitespace (or specified chars)
s = "  hello  "
print(s.strip())    # "hello"
print(s.lstrip())   # "hello  "
print(s.rstrip())   # "  hello"

# Strip specific characters
print("xxxhelloxxx".strip("x"))  # hello

# Padding / centering
print("42".zfill(5))      # 00042
print("hi".rjust(10))     # "        hi"
print("hi".ljust(10, "-")) # "hi--------"
print("hi".center(10, "*")) # "****hi****"

# expandtabs
print("a\tb".expandtabs(4))  # "a   b"

Raw Strings & Escapes

Raw Strings (r'...') behandeln Backslashes wörtlich – unerlässlich für Regex-Muster und Windows-Dateipfade. Strings in dreifachen Anführungszeichen erhalten Zeilenumbrüche. Strings unterstützen den *-Operator für Wiederholung und + für Verkettung.

python
# Escape sequences
print("Line1\nLine2")     # newline
print("Tab\there")        # tab
print("Quote: \"hi\"")   # escaped quotes
print("Backslash: \\")   # literal backslash

# Raw strings (ignore escapes) - great for regex
path = r"C:\Users\name\file.txt"
regex = r"\d{3}-\d{4}"
print(path)  # C:\Users\name\file.txt

# Triple-quoted strings
multi = """
Multiple
lines
"""

# String multiplication
print("ab" * 3)  # ababab
04

Zahlen & Mathematik

Arithmetische Operatoren

Python hat 7 arithmetische Operatoren. / gibt immer float zurück, // ist Ganzzahldivision (rundet gegen minus unendlich). ** ist Potenzierung (kein ^ – das ist XOR). Das Ergebnis des %-Operators übernimmt das Vorzeichen des Divisors, anders als in C/Java.

python
# Basic operators
print(7 + 3)   # 10  addition
print(7 - 3)   # 4   subtraction
print(7 * 3)   # 21  multiplication
print(7 / 3)   # 2.333...  true division (always float)
print(7 // 3)  # 2   floor division
print(7 % 3)   # 1   modulo (remainder)
print(7 ** 3)  # 343 exponentiation

# Floor division with negatives
print(-7 // 3)  # -3 (rounds toward negative infinity)
print(-7 % 3)   # 2 (result has same sign as divisor)

# Augmented assignment
x = 10
x += 5   # x = x + 5
x **= 2  # x = x ** 2

Math-Modul

Das math-Modul bietet mathematische Funktionen und Konstanten. Alle trigonometrischen Funktionen verwenden Bogenmaß – konvertieren Sie mit math.radians()/degrees(). math.gcd() findet den größten gemeinsamen Teiler. Für komplexe Zahlen verwenden Sie das cmath-Modul.

python
import math

# Constants
print(math.pi)       # 3.141592653589793
print(math.e)        # 2.718281828459045
print(math.inf)      # inf
print(math.nan)      # nan

# Functions
print(math.sqrt(16))     # 4.0
print(math.pow(2, 10))   # 1024.0
print(math.log(100, 10)) # 2.0 (log base 10)
print(math.log(math.e))  # 1.0 (natural log)
print(math.factorial(5)) # 120
print(math.gcd(12, 8))   # 4

# Rounding
print(math.floor(3.7))   # 3
print(math.ceil(3.2))    # 4
print(math.trunc(-3.7))  # -3 (toward zero)

# Trigonometry (radians)
print(math.sin(math.pi / 2))  # 1.0
print(math.degrees(math.pi))  # 180.0

Random-Modul

Das random-Modul verwendet den Mersenne-Twister-PRNG – NICHT kryptografisch sicher. Verwenden Sie für Sicherheit das secrets-Modul. random.sample() wählt eindeutige Elemente, random.choices() erlaubt Duplikate. Setzen Sie einen Seed für reproduzierbare Ergebnisse beim Testen.

python
import random

# Random integers
print(random.randint(1, 100))    # 1 to 100 inclusive
print(random.randrange(0, 10, 2)) # even number 0,2,4,6,8

# Random floats
print(random.random())           # 0.0 to 1.0
print(random.uniform(1.0, 10.0)) # random float in range

# Choice & sampling
colors = ["red", "green", "blue"]
print(random.choice(colors))           # one random item
print(random.sample(colors, 2))        # 2 unique items
print(random.choices(colors, k=5))     # 5 items (with replacement)

# Shuffle (in-place)
nums = [1, 2, 3, 4, 5]
random.shuffle(nums)
print(nums)

# Reproducible randomness
random.seed(42)  # same seed = same sequence

Decimal & Fractions

Verwenden Sie Decimal für finanzielle Berechnungen, wo float-Genauigkeitsfehler inakzeptabel sind (z. B. Geld). Verwenden Sie Fraction für exakte rationale Arithmetik. Beide sind langsamer als float, vermeiden aber Rundungsfehler. Konstruieren Sie Decimal immer aus Strings, nicht aus floats.

python
from decimal import Decimal, getcontext
from fractions import Fraction

# Float precision issues
print(0.1 + 0.2)  # 0.30000000000000004

# Decimal for exact decimal arithmetic
a = Decimal("0.1")
b = Decimal("0.2")
print(a + b)  # 0.3 (exact!)

# Set precision
getcontext().prec = 6
print(Decimal(1) / Decimal(7))  # 0.142857

# Fractions for exact rational arithmetic
f1 = Fraction(1, 3)
f2 = Fraction(1, 6)
print(f1 + f2)  # 1/2
print(float(f1))  # 0.3333...

# Fraction from string
print(Fraction("3/4"))  # 3/4

Bitweise Operatoren

Bitweise Operatoren manipulieren einzelne Bits von Ganzzahlen. Python-Ganzzahlen haben beliebige Genauigkeit, daher funktionieren Schiebeoperationen anders als in Sprachen mit fester Breite. Häufige Verwendungen: Flags, Masken, Low-Level-Protokollverarbeitung. x & (x-1) == 0 prüft, ob x eine Zweierpotenz ist.

python
# Bitwise operators work on integers
a = 0b1010  # 10
b = 0b1100  # 12

print(a & b)   # 8  (0b1000) AND
print(a | b)   # 14 (0b1110) OR
print(a ^ b)   # 6  (0b0110) XOR
print(~a)      # -11 (NOT, two's complement)
print(a << 2)  # 40 (left shift, multiply by 4)
print(a >> 1)  # 5  (right shift, divide by 2)

# Binary representation
print(bin(10))   # 0b1010
print(hex(255))  # 0xff
print(oct(8))    # 0o10
print(int("1010", 2))  # 10 (parse binary)

# Common tricks
print(5 & 1)     # 1 (check odd: nonzero = odd)
print(8 & (8-1)) # 0 (check power of 2)
05

Datenstrukturen

Listen

Listen sind Pythons vielseitigste Datenstruktur – geordnet, veränderbar und heterogen. append() ist O(1), insert(0, x) ist O(n). Verwenden Sie collections.deque für schnelle Operationen an beiden Enden. sort() ist in-place, sorted() gibt eine neue Liste zurück.

python
# Lists are ordered, mutable sequences
nums = [1, 2, 3, 4, 5]
mixed = [1, "hello", True, 3.14]

# Adding elements
nums.append(6)           # [1,2,3,4,5,6]
nums.insert(0, 0)        # [0,1,2,3,4,5,6]
nums.extend([7, 8])      # extend with another list

# Removing elements
nums.remove(0)           # remove by value
popped = nums.pop()      # remove & return last
popped = nums.pop(0)     # remove & return by index
del nums[0]              # delete by index
nums.clear()             # remove all

# Slicing (same as strings)
nums = [1, 2, 3, 4, 5]
print(nums[1:3])    # [2, 3]
print(nums[::-1])   # [5, 4, 3, 2, 1] reverse

# Sorting
nums.sort()              # in-place sort
nums.sort(reverse=True)  # descending
sorted_nums = sorted(nums)  # returns new list

Tupel

Tupel sind unveränderlich und schneller als Listen. Verwenden Sie sie für feste Sammlungen, mehrere Rückgabewerte und Dictionary-Schlüssel (Listen können keine Schlüssel sein). Named Tuples bieten Feldnamen für bessere Lesbarkeit. Einzelement-Tupel benötigen ein abschließendes Komma.

python
# Tuples are ordered, IMMUTABLE sequences
point = (3, 4)
single = (42,)     # note the comma for single-element tuple
empty = ()

# Packing & unpacking
coordinates = 10, 20, 30  # packing
x, y, z = coordinates     # unpacking
x, y = y, x               # swap values!

# Multiple return values
def min_max(nums):
    return min(nums), max(nums)

lo, hi = min_max([3, 1, 4, 1, 5])

# Named tuples (readable)
from collections import namedtuple
Point = namedtuple("Point", ["x", "y"])
p = Point(3, 4)
print(p.x, p.y)       # 3 4
print(p[0], p[1])     # 3 4

# Tuples are immutable but can contain mutable objects
t = (1, [2, 3])
t[1].append(4)  # OK: (1, [2, 3, 4])

Dictionaries

Dictionaries sind Hash-Maps – O(1) im Durchschnitt für Lookup/Insert/Delete. Schlüssel müssen hashbar (unveränderlich) sein. Seit Python 3.7 behalten Dictionaries die Einfügereihenfolge. Verwenden Sie get(), um KeyError zu vermeiden. dict comprehension erstellt Dictionaries elegant.

python
# Dicts are key-value mappings (insertion-ordered since 3.7)
user = {"name": "Alice", "age": 30}

# Access
print(user["name"])          # Alice
print(user.get("email"))     # None (no KeyError)
print(user.get("email", "N/A"))  # N/A (default)

# Add/update
user["email"] = "[email protected]"    # add
user["age"] = 31             # update
user.setdefault("role", "user")  # set if missing

# Delete
del user["email"]
val = user.pop("age")        # remove & return
# user.clear()               # remove all

# Iteration
for key in user:             # keys
    print(key)
for k, v in user.items():    # key-value pairs
    print(k, v)
for v in user.values():      # values
    print(v)

# Dict comprehension
squares = {x: x**2 for x in range(5)}

# Merge dicts (3.9+)
merged = {"a": 1} | {"b": 2}

Sets

Sets sind ungeordnete Sammlungen eindeutiger, hashbarer Elemente. Sie glänzen beim Mitgliedschaftstest (O(1) vs. O(n) für Listen) und Mengenalgebra (Vereinigung, Schnittmenge, Differenz). frozenset ist unveränderlich und hashbar. Die Reihenfolge ist nicht garantiert.

python
# Sets are unordered collections of unique elements
a = {1, 2, 3, 4}
b = {3, 4, 5, 6}

# Set operations
print(a | b)    # union: {1, 2, 3, 4, 5, 6}
print(a & b)    # intersection: {3, 4}
print(a - b)    # difference: {1, 2}
print(a ^ b)    # symmetric difference: {1, 2, 5, 6}

# Methods
a.add(5)        # add element
a.discard(10)   # remove if present (no error)
a.remove(1)     # remove (KeyError if missing)
a.update([6, 7]) # add multiple

# Membership test (O(1) - faster than list)
print(3 in a)   # True

# Frozen set (immutable)
fs = frozenset([1, 2, 3])

# Common use: deduplicate
unique = list(set([1, 1, 2, 2, 3]))  # [1, 2, 3]

Comprehensions

Comprehensions sind eine pythonische Methode, um Sammlungen prägnant zu erstellen. Generator-Ausdrücke (Klammern statt Klammern) sind faul – sie produzieren Werte bei Bedarf und sparen Speicher. Bevorzugen Sie Comprehensions gegenüber map()/filter() für bessere Lesbarkeit.

python
# List comprehension
squares = [x**2 for x in range(10)]
evens = [x for x in range(20) if x % 2 == 0]
pairs = [(x, y) for x in range(3) for y in range(3)]

# Dict comprehension
square_map = {x: x**2 for x in range(5)}
# {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

# Set comprehension
unique_lens = {len(w) for w in ["a", "ab", "abc", "ab"]}

# Generator expression (lazy, memory-efficient)
gen = (x**2 for x in range(1000000))
print(next(gen))  # 0
print(next(gen))  # 1
total = sum(x**2 for x in range(100))  # no extra list

# Nested comprehension (matrix)
matrix = [[i * 3 + j for j in range(3)] for i in range(3)]
# [[0,1,2], [3,4,5], [6,7,8]]

Collections-Modul

Das collections-Modul bietet spezialisierte Container. Counter zählt hashbare Elemente. defaultdict erstellt fehlende Schlüssel automatisch. deque bietet O(1) append/pop an beiden Enden (vs. O(n) für Listen). Diese sind unerlässlich für sauberen, effizienten Code.

python
from collections import Counter, defaultdict, deque, OrderedDict

# Counter - counting
words = ["apple", "banana", "apple", "cherry", "banana", "apple"]
cnt = Counter(words)
print(cnt)  # Counter({'apple': 3, 'banana': 2, 'cherry': 1})
print(cnt.most_common(2))  # [('apple', 3), ('banana', 2)]

# defaultdict - no KeyError
dd = defaultdict(list)
dd["fruits"].append("apple")
dd["fruits"].append("banana")
# dd["vegs"] automatically creates empty list

# deque - fast double-ended queue
dq = deque([1, 2, 3])
dq.appendleft(0)   # [0, 1, 2, 3]
dq.append(4)       # [0, 1, 2, 3, 4]
dq.popleft()       # 0, deque is now [1, 2, 3, 4]
dq.rotate(1)       # rotate right

# OrderedDict (less needed since 3.7, dicts are ordered)
od = OrderedDict([("a", 1), ("b", 2)])
06

Kontrollfluss

If / Elif / Else

Python verwendet if/elif/else – beachten Sie 'elif' nicht 'elseif'. Einrückung definiert Blöcke. Der ternäre 'x if cond else y' ist ein Ausdruck. Python behandelt leere Sammlungen, 0, None und False als falsy – nützlich für prägnante Bedingungen.

python
score = 85

if score >= 90:
    grade = "A"
elif score >= 80:
    grade = "B"
elif score >= 70:
    grade = "C"
else:
    grade = "F"

print(f"Grade: {grade}")  # Grade: B

# Conditional expression (ternary)
status = "pass" if score >= 60 else "fail"

# Truthy/falsy values
# Falsy: False, 0, 0.0, "", [], {}, (), None
# Everything else is truthy
if []:    # False
    print("never")
if [0]:   # True (non-empty list)
    print("always")

For-Schleifen & Iteration

Pythons for-Schleife iteriert über jedes Iterable. range() generiert Zahlen (exklusiver Stop). enumerate() paart Elemente mit Indizes. zip() iteriert mehrere Sequenzen parallel. Verwenden Sie .items(), um Dict-Schlüssel-Wert-Paare zu iterieren.

python
# range(start, stop, step)
for i in range(5):        # 0, 1, 2, 3, 4
    print(i)
for i in range(2, 10, 2): # 2, 4, 6, 8
    print(i)
for i in range(10, 0, -1): # countdown
    print(i)

# Iterate over collections
fruits = ["apple", "banana", "cherry"]
for fruit in fruits:
    print(fruit)

# enumerate for index + value
for idx, fruit in enumerate(fruits):
    print(f"{idx}: {fruit}")

# zip to iterate multiple sequences
names = ["Alice", "Bob"]
ages = [30, 25]
for name, age in zip(names, ages):
    print(f"{name}: {age}")

# Iterate dict
user = {"name": "Alice", "age": 30}
for key, value in user.items():
    print(f"{key} = {value}")

While-Schleifen & Break/Continue

while-Schleifen wiederholen, solange eine Bedingung wahr ist. break beendet die Schleife sofort, continue springt zur nächsten Iteration. Das for/else-Konstrukt führt den else-Block nur aus, wenn die Schleife nicht durch break beendet wurde. pass ist ein No-Op-Platzhalter für leere Blöcke.

python
# Basic while
count = 0
while count < 5:
    print(count)
    count += 1

# break - exit loop
while True:
    cmd = input("> ")
    if cmd == "quit":
        break
    print(f"You said: {cmd}")

# continue - skip to next iteration
for i in range(10):
    if i % 2 == 0:
        continue  # skip even numbers
    print(i)  # prints 1, 3, 5, 7, 9

# else clause (runs if no break)
for i in range(5):
    if i == 10:
        break
else:
    print("Loop completed without break")

# pass - do nothing (placeholder)
for i in range(5):
    pass  # TODO: implement

Match-Anweisung (Python 3.10+)

Die match-Anweisung (Python 3.10+) ist mächtiges strukturelles Pattern Matching, weit über C's switch hinaus. Sie kann Sequenzen, Mappings, Klasseninstanzen matchen und Variablen binden. Das _-Pattern ist ein Wildcard (Standard). Guards mit 'if' fügen Bedingungen hinzu.

python
# Structural pattern matching (like switch)
def handle_command(cmd):
    match cmd.split():
        case ["quit"]:
            return "Goodbye"
        case ["hello", name]:
            return f"Hello, {name}!"
        case ["move", direction] if direction in "NSEW":
            return f"Moving {direction}"
        case ["add", x, y]:
            return int(x) + int(y)
        case _:
            return "Unknown command"

print(handle_command("hello Alice"))  # Hello, Alice!

# Matching data structures
match point:
    case (0, 0):
        print("origin")
    case (0, y):
        print(f"on y-axis at {y}")
    case (x, 0):
        print(f"on x-axis at {x}")
    case (x, y):
        print(f"at ({x}, {y})")

Iteratoren & Generatoren

Generatoren produzieren Werte träge mit yield – sie berechnen nicht alle Werte vorab, was Speicher spart. Sie implementieren das Iterator-Protokoll (iter() und next()). Einmal erschöpft, sind sie fertig. Verwenden Sie Generatoren für große/unendliche Sequenzen, Pipelines und Streaming-Daten.

python
# Iterator protocol
nums = [1, 2, 3]
it = iter(nums)
print(next(it))  # 1
print(next(it))  # 2
print(next(it))  # 3
# next(it)  # StopIteration

# Generator function (uses yield)
def count_up_to(n):
    count = 1
    while count <= n:
        yield count
        count += 1

for num in count_up_to(5):
    print(num)  # 1, 2, 3, 4, 5

# Infinite generator
def fibonacci():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

fib = fibonacci()
print(next(fib))  # 0
print(next(fib))  # 1
print(next(fib))  # 1
print(next(fib))  # 2

# Generator expression
squares = (x**2 for x in range(10))
07

Funktionen

Funktionen definieren & aufrufen

Funktionen werden mit def definiert. Standardargumente verwenden =. Python unterstützt Schlüsselwortargumente für Klarheit. Funktionen können mehrere Werte zurückgeben (als Tupel). Docstrings (dreifache Anführungszeichen) dokumentieren Funktionen und sind über help() zugänglich.

python
# Basic function
def greet(name):
    return f"Hello, {name}!"

print(greet("Alice"))  # Hello, Alice!

# Default arguments
def greet(name, greeting="Hello"):
    return f"{greeting}, {name}!"

print(greet("Bob"))              # Hello, Bob!
print(greet("Bob", "Hi"))        # Hi, Bob!

# Keyword arguments
print(greet(name="Carol", greeting="Hey"))

# Return multiple values (tuple)
def stats(nums):
    return min(nums), max(nums), sum(nums) / len(nums)

lo, hi, avg = stats([1, 2, 3, 4, 5])

# Docstrings
def add(a, b):
    """Add two numbers and return the result.
    
    Args:
        a: First number
        b: Second number
    Returns:
        Sum of a and b
    """
    return a + b

Argumente: *args & **kwargs

*args sammelt zusätzliche Positionsargumente in einem Tupel, **kwargs sammelt zusätzliche Schlüsselwortargumente in einem Dict. Die Namen args/kwargs sind Konvention. Sie können Sequenzen mit * und Dicts mit ** entpacken, wenn Sie Funktionen aufrufen. Reihenfolge: Positionsargumente, *args, Schlüsselwortargumente, **kwargs.

python
# *args - variable positional arguments (tuple)
def sum_all(*args):
    return sum(args)

print(sum_all(1, 2, 3))       # 6
print(sum_all(1, 2, 3, 4, 5)) # 15

# **kwargs - variable keyword arguments (dict)
def print_info(**kwargs):
    for key, value in kwargs.items():
        print(f"{key}: {value}")

print_info(name="Alice", age=30, role="admin")

# Combining all
def func(a, b, *args, **kwargs):
    print(f"a={a}, b={b}")
    print(f"args={args}")
    print(f"kwargs={kwargs}")

func(1, 2, 3, 4, x=5, y=6)
# a=1, b=2, args=(3, 4), kwargs={'x': 5, 'y': 6}

# Unpacking arguments
nums = [1, 2, 3]
print(sum_all(*nums))  # unpack list as args

opts = {"name": "Alice", "age": 30}
print_info(**opts)  # unpack dict as kwargs

Lambda & Funktionen höherer Ordnung

Lambdas sind auf einen einzelnen Ausdruck beschränkt – verwenden Sie def für komplexe Logik. Sie glänzen als Argumente für Funktionen höherer Ordnung wie sorted(), map(), filter(). Allerdings sind List-Comprehensions oft lesbarer als map/filter. reduce() lebt in functools.

python
# Lambda - anonymous function (single expression)
square = lambda x: x ** 2
print(square(5))  # 25

# Common with sorted, map, filter, reduce
students = [("Alice", 85), ("Bob", 92), ("Carol", 78)]

# Sort by score (key function)
sorted_by_score = sorted(students, key=lambda s: s[1])
# [('Carol', 78), ('Alice', 85), ('Bob', 92)]

# map - apply function to each item
nums = [1, 2, 3, 4, 5]
doubled = list(map(lambda x: x * 2, nums))
# [2, 4, 6, 8, 10]

# filter - keep items where function returns True
evens = list(filter(lambda x: x % 2 == 0, nums))
# [2, 4]

# reduce - accumulate to single value
from functools import reduce
product = reduce(lambda a, b: a * b, nums)
# 120 (1*2*3*4*5)

# Prefer comprehensions over map/filter
doubled = [x * 2 for x in nums]  # more Pythonic
evens = [x for x in nums if x % 2 == 0]

Dekoratoren

Dekoratoren umschließen Funktionen, um Verhalten hinzuzufügen, ohne den Originalcode zu modifizieren. Die @-Syntax ist syntaktischer Zucker. Dekoratoren mit Argumenten benötigen eine zusätzliche Verschachtelungsebene. Verwenden Sie immer functools.wraps, um die Metadaten der Originalfunktion (Name, Docstring) zu erhalten.

python
# A decorator modifies a function's behavior
def uppercase_result(func):
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        return result.upper()
    return wrapper

@uppercase_result
def greet(name):
    return f"hello, {name}"

print(greet("alice"))  # HELLO, ALICE

# Decorator with arguments
def repeat(times):
    def decorator(func):
        def wrapper(*args, **kwargs):
            for _ in range(times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@repeat(3)
def say_hi():
    print("Hi!")

say_hi()  # prints "Hi!" three times

# Practical: timing decorator
import time
def timer(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        print(f"{func.__name__} took {time.time() - start:.4f}s")
        return result
    return wrapper

# Use functools.wraps to preserve metadata
from functools import wraps
def my_decorator(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return func(*args, **kwargs)
    return wrapper

Gültigkeitsbereich & Closures

Python löst Namen mit der LEGB-Gültigkeitsreihenfolge: Local, Enclosing, Global, Built-in. Verwenden Sie 'global', um eine globale Variable in einer Funktion neu zu binden. Verwenden Sie 'nonlocal' (Python 3), um eine Variable in einem einschließenden Gültigkeitsbereich zu modifizieren. Closures erinnern sich an ihren einschließenden Gültigkeitsbereich.

python
# LEGB rule: Local, Enclosing, Global, Built-in
x = "global"

def outer():
    x = "enclosing"
    
    def inner():
        x = "local"
        print(x)  # local
    
    inner()
    print(x)  # enclosing

outer()
print(x)  # global

# global keyword - modify global variable
count = 0
def increment():
    global count
    count += 1

# nonlocal keyword - modify enclosing variable
def make_counter():
    count = 0
    def counter():
        nonlocal count
        count += 1
        return count
    return counter

c = make_counter()
print(c())  # 1
print(c())  # 2
print(c())  # 3
08

OOP & Klassen

Klassen & Objekte

Klassen bündeln Daten (Attribute) und Verhalten (Methoden). __init__ ist der Konstruktor. self verweist auf die Instanz (wie 'this' in anderen Sprachen). Klassenvariablen werden geteilt; Instanzvariablen sind pro Objekt. __str__ ist für Benutzer, __repr__ für Entwickler.

python
class Dog:
    # Class variable (shared by all instances)
    species = "Canis familiaris"
    
    # Constructor
    def __init__(self, name, age):
        # Instance variables
        self.name = name
        self.age = age
    
    # Instance method
    def bark(self):
        return f"{self.name} says Woof!"
    
    # String representation
    def __str__(self):
        return f"Dog({self.name}, {self.age})"
    
    # Official representation (for debugging)
    def __repr__(self):
        return f"Dog(name='{self.name}', age={self.age})"

# Create instances
buddy = Dog("Buddy", 3)
lucy = Dog("Lucy", 5)

print(buddy.bark())       # Buddy says Woof!
print(buddy.name)         # Buddy
print(buddy.species)      # Canis familiaris
print(str(buddy))         # Dog(Buddy, 3)

Vererbung & Polymorphismus

Vererbung ermöglicht Klassen, Verhalten wiederzuverwenden und zu erweitern. Python unterstützt Mehrfachvererbung mit MRO (Method Resolution Order), um Konflikte zu lösen. Polymorphismus erlaubt, verschiedene Typen einheitlich zu behandeln. Verwenden Sie isinstance() für Typprüfungen, nicht type().

python
class Animal:
    def __init__(self, name):
        self.name = name
    
    def speak(self):
        raise NotImplementedError("Subclass must implement")

class Dog(Animal):
    def speak(self):
        return f"{self.name} says Woof!"

class Cat(Animal):
    def speak(self):
        return f"{self.name} says Meow!"

# Polymorphism - same interface, different behavior
def animal_sound(animal):
    print(animal.speak())

animals = [Dog("Buddy"), Cat("Whiskers")]
for a in animals:
    animal_sound(a)
# Buddy says Woof!
# Whiskers says Meow!

# Multiple inheritance
class Swimmer:
    def swim(self):
        return "swimming"

class Flyer:
    def fly(self):
        return "flying"

class Duck(Animal, Swimmer, Flyer):
    pass

duck = Duck("Donald")
print(duck.swim())  # swimming
print(duck.fly())   # flying

# Check inheritance
print(isinstance(duck, Animal))  # True
print(issubclass(Dog, Animal))   # True

Properties & Kapselung

Python hat kein echtes private/protected – es verwendet Konventionen. Ein einzelner Unterstrich _ bedeutet 'intern'. Doppelter Unterstrich __ löst Name Mangling aus (keine echte Privatsphäre). @property wandelt Methoden in Attribute mit Gettern/Settern um und ermöglicht Validierung und berechnete Properties.

python
class Temperature:
    def __init__(self, celsius=0):
        self.celsius = celsius  # uses setter below
    
    # Getter
    @property
    def celsius(self):
        return self._celsius
    
    # Setter
    @celsius.setter
    def celsius(self, value):
        if value < -273.15:
            raise ValueError("Below absolute zero!")
        self._celsius = value
    
    # Computed property
    @property
    def fahrenheit(self):
        return self._celsius * 9/5 + 32
    
    @fahrenheit.setter
    def fahrenheit(self, value):
        self.celsius = (value - 32) * 5/9

temp = Temperature(25)
print(temp.fahrenheit)  # 77.0
temp.fahrenheit = 100
print(temp.celsius)     # 37.78...

# Name conventions:
# _name   - protected (convention, not enforced)
# __name  - private (name mangling: _ClassName__name)
# __name__ - dunder (reserved by Python)

Klassen- & statische Methoden

@staticmethod ist nur eine Funktion im Klassen-Namespace – kein implizites erstes Argument. @classmethod empfängt die Klasse (cls) als erstes Argument, nützlich für alternative Konstruktoren (Factory-Methoden) und vererbungsbewusstes Verhalten. Verwenden Sie classmethod für Konstruktoren, staticmethod für Hilfsfunktionen.

python
class MathUtils:
    pi = 3.14159
    
    # Static method - no self/cls, lives in class namespace
    @staticmethod
    def add(a, b):
        return a + b
    
    # Class method - receives the class as first argument
    @classmethod
    def circle_area(cls, radius):
        return cls.pi * radius ** 2
    
    # Alternative constructor (common classmethod use)
    @classmethod
    def from_diameter(cls, diameter):
        return cls()  # would configure instance

# Static: called on class or instance, no special first arg
print(MathUtils.add(2, 3))  # 5

# Class: often used for alternative constructors
print(MathUtils.circle_area(5))  # 78.54...

# Factory pattern with classmethod
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    @classmethod
    def origin(cls):
        return cls(0, 0)
    
    @classmethod
    def from_tuple(cls, coords):
        return cls(*coords)

p1 = Point.origin()
p2 = Point.from_tuple((3, 4))

Magische Methoden (Dunder)

Magische Methoden (Dunder-Methoden) implementieren Operatorüberladung und Protokollverhalten. __add__ für +, __eq__ für ==, __len__ für len(), __iter__ für Iteration/Entpacken. Sie lassen Ihre Objekte natürlich mit Pythons eingebauter Syntax und Funktionen arbeiten.

python
class Vector:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    
    # String representation
    def __str__(self):
        return f"Vector({self.x}, {self.y})"
    
    # Operator overloading
    def __add__(self, other):
        return Vector(self.x + other.x, self.y + other.y)
    
    def __mul__(self, scalar):
        return Vector(self.x * scalar, self.y * scalar)
    
    # Equality
    def __eq__(self, other):
        return self.x == other.x and self.y == other.y
    
    # Length
    def __len__(self):
        return int((self.x**2 + self.y**2) ** 0.5)
    
    # Make it iterable
    def __iter__(self):
        yield self.x
        yield self.y
    
    # Index access
    def __getitem__(self, index):
        return (self.x, self.y)[index]

v1 = Vector(2, 3)
v2 = Vector(4, 5)
print(v1 + v2)        # Vector(6, 8)
print(v1 * 3)         # Vector(6, 9)
print(v1 == Vector(2, 3))  # True
print(len(v1))        # 3
x, y = v1             # unpacking via __iter__
09

Fehlerbehandlung

Try / Except / Finally

try/except/else/finally: try führt riskanten Code aus, except fängt Fehler ab, else läuft, wenn keine Ausnahme auftritt, finally läuft immer (Aufräumen). Fangen Sie spezifische Ausnahmen ab, nicht bloßes 'except:'. Der else-Block ist nützlich, wenn Aufräumen nur bei Erfolg erfolgen soll. Exception ist die Basis für die meisten abfangbaren Fehler.

python
# Basic exception handling
try:
    result = 10 / 0
except ZeroDivisionError as e:
    print(f"Error: {e}")  # division by zero
finally:
    print("This always runs")

# Multiple exception types
try:
    value = int("abc")
except (ValueError, TypeError) as e:
    print(f"Conversion error: {e}")

# Different handlers for different exceptions
try:
    f = open("nonexistent.txt")
    data = f.read()
except FileNotFoundError:
    print("File not found")
except PermissionError:
    print("No permission")
except Exception as e:
    print(f"Unexpected: {e}")
else:
    print("No exception occurred")
    f.close()
finally:
    print("Cleanup (always runs)")

# Exception hierarchy
# BaseException
#  ├── SystemExit
#  ├── KeyboardInterrupt
#  └── Exception
#       ├── ValueError
#       ├── TypeError
#       ├── KeyError
#       └── ...

Ausnahmen auslösen

Verwenden Sie raise, um Ausnahmen auszulösen. 'raise' allein löst die aktuelle Ausnahme erneut aus (in einem except-Block). 'raise X from Y' verkettet Ausnahmen und erhält die ursächliche Ursache. Lösen Sie immer spezifische Ausnahmetypen aus. Vermeiden Sie Ausnahmen für normalen Kontrollfluss.

python
# Raise an exception
def divide(a, b):
    if b == 0:
        raise ZeroDivisionError("Cannot divide by zero!")
    return a / b

# Re-raise the current exception
def process(data):
    try:
        return parse(data)
    except ValueError:
        print("Logging parse error...")
        raise  # re-raises the same exception

# Raise with context (from)
try:
    int("abc")
except ValueError as e:
    raise RuntimeError("Failed to process input") from e

# Common built-in exceptions
raise ValueError("invalid value")
raise TypeError("wrong type")
raise KeyError("missing key")
raise IndexError("out of range")
raise RuntimeError("something went wrong")
raise NotImplementedError("override this")
raise FileNotFoundError("no such file")

# Exception with custom args
class ValidationError(Exception):
    pass

raise ValidationError("field is required", "email")

Benutzerdefinierte Ausnahmen

Erstellen Sie benutzerdefinierte Ausnahmen durch Ableiten von Exception (oder einer spezifischeren eingebauten). Entwerfen Sie eine Hierarchie, damit Aufrufer auf der richtigen Ebene abfangen können. Fügen Sie benutzerdefinierte Attribute hinzu, um Kontext zu transportieren. Erben Sie von Exception, nicht von BaseException (welche SystemExit/KeyboardInterrupt umfasst).

python
# Custom exception hierarchy
class AppError(Exception):
    """Base exception for the application."""
    pass

class DatabaseError(AppError):
    def __init__(self, message, query=None):
        super().__init__(message)
        self.query = query

class ValidationError(AppError):
    def __init__(self, field, message):
        super().__init__(f"{field}: {message}")
        self.field = field
        self.message = message

class AuthenticationError(AppError):
    pass

# Usage
def login(username, password):
    if not username:
        raise ValidationError("username", "is required")
    if password != "secret":
        raise AuthenticationError("Invalid credentials")

# Catching by hierarchy
try:
    login("", "x")
except ValidationError as e:
    print(f"Validation failed: {e.field}")
except AppError as e:
    print(f"App error: {e}")

# Access exception info
import traceback
try:
    1 / 0
except:
    traceback.print_exc()
    print(repr(sys.exc_info()[1]))

Kontextmanager (with-Anweisung)

Kontextmanager (die 'with'-Anweisung) garantieren Aufräumen via __enter__ und __exit__. Sie sind unerlässlich für Ressourcen wie Dateien, Locks und Datenbankverbindungen. contextlib.contextmanager vereinfacht die Erstellung mit einem Generator. __exit__ kann Ausnahmen unterdrücken, indem es True zurückgibt.

python
# Context managers handle setup and cleanup
with open("file.txt") as f:
    content = f.read()
# file is automatically closed, even if an error occurs

# Multiple context managers
with open("input.txt") as fin, open("output.txt", "w") as fout:
    fout.write(fin.read())

# Creating a context manager (class-based)
class Timer:
    def __enter__(self):
        import time
        self.start = time.time()
        return self
    
    def __exit__(self, exc_type, exc_val, exc_tb):
        import time
        self.elapsed = time.time() - self.start
        print(f"Elapsed: {self.elapsed:.4f}s")
        return False  # don't suppress exceptions

with Timer() as t:
    # code to time
    sum(range(1000000))

# contextlib for simpler context managers
from contextlib import contextmanager

@contextmanager
def open_db(url):
    db = connect(url)
    try:
        yield db
    finally:
        db.close()

with open_db("localhost") as db:
    db.query("SELECT 1")

Assertions & Logging

assert-Anweisungen dienen dem Debuggen von Invarianten – sie werden entfernt, wenn Python mit -O (Optimierung) läuft. Verwenden Sie niemals Assertions für Eingabevalidierung. Verwenden Sie das logging-Modul statt print() für Produktionscode – es unterstützt Level, Formatierung und Ausgabeziele.

python
# Assertions - for debugging (removed with -O flag)
def divide(a, b):
    assert b != 0, "Divisor cannot be zero"
    return a / b

# Never use assertions for data validation (they can be disabled)
# Use them for invariant checks during development

# Logging (better than print for production)
import logging

# Configure logging
logging.basicConfig(
    level=logging.DEBUG,
    format="%(asctime)s [%(levelname)s] %(message)s"
)

logger = logging.getLogger(__name__)

logger.debug("Detailed info for debugging")
logger.info("Confirmation things are working")
logger.warning("Something unexpected happened")
logger.error("A serious problem")
logger.critical("A fatal error")

# Log exceptions with traceback
try:
    1 / 0
except:
    logger.exception("Failed to divide")  # includes traceback
10

Datei-I/O

Dateien lesen

Verwenden Sie immer 'with', um Dateien zu öffnen – es schließt sie automatisch, selbst wenn ein Fehler auftritt. Geben Sie encoding='utf-8' an, um plattformabhängige Encoding-Probleme zu vermeiden. Für große Dateien iterieren Sie zeilenweise statt read(), um Speicher zu sparen. readlines() lädt die gesamte Datei in den Speicher.

python
# Read entire file
with open("file.txt", "r", encoding="utf-8") as f:
    content = f.read()
    print(content)

# Read line by line (memory-efficient for large files)
with open("file.txt", "r") as f:
    for line in f:
        print(line.strip())  # strip removes trailing newline

# Read all lines into a list
with open("file.txt") as f:
    lines = f.readlines()  # ['line1\n', 'line2\n', ...]

# Read specific number of characters
with open("file.txt") as f:
    chunk = f.read(100)  # first 100 chars

# File modes:
# "r"  read (default)
# "w"  write (truncate)
# "a"  append
# "x"  exclusive create (fails if exists)
# "b"  binary mode (e.g., "rb", "wb")
# "+"  read and write (e.g., "r+")

Dateien schreiben

Modus 'w' schneidet die Datei ab (löscht Inhalt); verwenden Sie 'a' zum Anhängen. writelines() fügt keine Zeilenumbrüche hinzu – fügen Sie diese manuell hinzu. Verwenden Sie 'rb'/'wb' für Binärdateien (Bilder usw.). seek() bewegt den Cursor; tell() gibt seine Position zurück. Geben Sie immer encoding für Textdateien an.

python
# Write text (overwrites existing)
with open("output.txt", "w") as f:
    f.write("First line\n")
    f.write("Second line\n")
    
    # writelines doesn't add newlines
    f.writelines(["line3\n", "line4\n"])

# Append to a file
with open("log.txt", "a") as f:
    f.write("New log entry\n")

# Write binary data
with open("data.bin", "wb") as f:
    f.write(b"\x00\x01\x02\x03")

# Read and write simultaneously
with open("file.txt", "r+") as f:
    content = f.read()
    f.seek(0)           # move to beginning
    f.write("Updated")  # overwrite
    f.truncate()        # cut off remaining

# Check if file exists
import os
if os.path.exists("file.txt"):
    print("File exists")

Pfadverarbeitung (pathlib)

pathlib (Python 3.4+) ist die moderne, objektorientierte Methode zur Pfadverarbeitung – bevorzugen Sie es gegenüber os.path. Der /-Operator verbindet Pfade plattformunabhängig. Path-Objekte haben read_text()/write_text()-Methoden, die open/close für Sie übernehmen. rglob() sucht rekursiv.

python
from pathlib import Path

# Create Path objects (preferred over os.path)
p = Path("src/main.py")
home = Path.home()           # /home/user or C:\Users\user
cwd = Path.cwd()             # current working directory

# Path components
print(p.name)        # main.py
print(p.stem)        # main
print(p.suffix)      # .py
print(p.parent)      # src
print(p.parts)       # ('src', 'main.py')

# Joining paths (use / operator)
config = home / ".config" / "app" / "config.json"

# Existence and type
print(p.exists())    # True/False
print(p.is_file())
print(p.is_dir())

# Listing directories
for f in Path(".").iterdir():
    print(f)

# Glob patterns
for py_file in Path(".").rglob("*.py"):
    print(py_file)

# Create directories
Path("new/dir").mkdir(parents=True, exist_ok=True)

# Read/write (Path methods)
content = Path("file.txt").read_text()
Path("output.txt").write_text("Hello!")

JSON

json.dumps() serialisiert zu String, json.loads() deserialisiert. dump()/load() arbeiten mit Dateien. Verwenden Sie indent für Pretty-Printing. Benutzerdefinierte Objekte benötigen einen Standard-Serializer. JSON unterstützt nur grundlegende Typen – verwenden Sie default= für datetime und andere komplexe Objekte.

python
import json

# Python dict to JSON string
data = {"name": "Alice", "age": 30, "skills": ["Python", "SQL"]}
json_str = json.dumps(data, indent=2)
print(json_str)

# JSON string to Python dict
parsed = json.loads('{"name": "Bob", "active": true}')
print(parsed["name"])  # Bob
print(parsed["active"])  # True (Python bool)

# Write JSON to file
with open("data.json", "w") as f:
    json.dump(data, f, indent=2, ensure_ascii=False)

# Read JSON from file
with open("data.json") as f:
    loaded = json.load(f)

# Custom serialization (e.g., datetime)
from datetime import datetime
def json_default(obj):
    if isinstance(obj, datetime):
        return obj.isoformat()
    raise TypeError

json.dumps({"time": datetime.now()}, default=json_default)

# Type mapping:
# JSON object  <-> Python dict
# JSON array   <-> Python list
# JSON string  <-> Python str
# JSON number  <-> Python int/float
# JSON boolean <-> Python bool
# JSON null    <-> Python None

CSV & andere Formate

Das csv-Modul verarbeitet CSV mit korrektem Quoting. Verwenden Sie newline='', wenn Sie CSV-Dateien unter Windows öffnen. DictReader/DictWriter arbeiten mit Spaltennamen. pickle kann jedes Python-Objekt serialisieren, ist aber Python-spezifisch und unsicher – unpickle niemals Daten aus nicht vertrauenswürdigen Quellen.

python
import csv

# Write CSV
with open("data.csv", "w", newline="") as f:
    writer = csv.writer(f)
    writer.writerow(["name", "age", "city"])
    writer.writerows([
        ["Alice", 30, "NYC"],
        ["Bob", 25, "LA"],
    ])

# Read CSV
with open("data.csv") as f:
    reader = csv.reader(f)
    header = next(reader)  # first row
    for row in reader:
        print(row)  # ['Alice', '30', 'NYC']

# DictReader/DictWriter (column access by name)
with open("data.csv") as f:
    reader = csv.DictReader(f)
    for row in reader:
        print(row["name"], row["age"])

# Pickle (Python-specific, can store any object)
import pickle
with open("data.pkl", "wb") as f:
    pickle.dump({"complex": [1, 2, {"a": 3}]}, f)

with open("data.pkl", "rb") as f:
    obj = pickle.load(f)

# WARNING: pickle is insecure - never unpickle untrusted data!
11

Module & Pakete

Module importieren

Imports bringen Module herein. 'import X' hält den Namespace sauber. 'from X import Y' ist bequem, kann aber Namenskollisionen verursachen. Aliasse (import X as Y) sind üblich für Bibliotheken mit Konventionen (np, pd). Vermeiden Sie 'from X import *' – es verschmutzt den Namespace.

python
# Import entire module
import math
print(math.sqrt(16))

# Import specific names
from datetime import datetime, timedelta
now = datetime.now()

# Import with alias
import numpy as np
import pandas as pd

# Import all names (discouraged - pollutes namespace)
# from os import *

# Conditional import (try/except)
try:
    import cjson as json
except ImportError:
    import json

# Check what's in a module
import os
print(dir(os))         # list all attributes
print(os.__file__)     # module location
print(os.__name__)     # module name

# Reload a module (during development)
import importlib
importlib.reload(my_module)

Module & Pakete erstellen

Ein Modul ist eine .py-Datei; ein Paket ist ein Verzeichnis mit __init__.py. Die __init__.py-Datei kann leer sein oder das Paket einrichten. __all__ in __init__.py steuert, was 'from package import *' exportiert. Modernes Python (3.3+) unterstützt Namespace-Pakete ohne __init__.py.

python
# A module is just a .py file
# mymath.py
def add(a, b):
    return a + b

PI = 3.14159

# A package is a directory with __init__.py
# mypackage/
#   __init__.py
#   module1.py
#   module2.py
#   subpackage/
#     __init__.py
#     module3.py

# __init__.py can be empty or contain package initialization
# mypackage/__init__.py
from .module1 import ClassA
from .module2 import func_b
__version__ = "1.0.0"
__all__ = ["ClassA", "func_b"]

# Using the package
from mypackage import ClassA
from mypackage.subpackage import module3

# __all__ controls 'from package import *'
# Without __all__, * imports only what's in __init__.py

__name__ == '__main__'

Das Idiom if __name__ == '__main__' ermöglicht einer Datei, sowohl als Skript als auch als Modul zu dienen. Bei direkter Ausführung ist __name__ '__main__'; beim Importieren ist es der Modulname. Dieses Pattern ist unerlässlich, um wiederverwendbare Module zu erstellen, die auch eigenständig ausgeführt werden können.

python
# script.py
def main():
    print("Running main")

def helper():
    print("Helper function")

if __name__ == "__main__":
    # This code only runs when the file is executed directly
    # NOT when imported as a module
    main()

# When you run: python script.py
# __name__ is "__main__" -> main() runs

# When you: import script
# __name__ is "script" -> main() does NOT run
# This lets the module be both a script and an importable library

# Common pattern for CLI tools
def main():
    import argparse
    parser = argparse.ArgumentParser()
    parser.add_argument("--name", required=True)
    args = parser.parse_args()
    print(f"Hello, {args.name}!")

if __name__ == "__main__":
    main()

Standardbibliothek-Highlights

Pythons Standardbibliothek ist riesig und inkludiert alles. os/sys für Systeminteraktion, datetime für Daten, collections für spezialisierte Container, itertools für Iterator-Tools, functools für funktionale Programmierung. Erkunden Sie die Dokumentation unter docs.python.org/3/library/.

python
# os - operating system interface
import os
os.getcwd()           # current directory
os.listdir(".")       # list files
os.environ.get("HOME")  # environment variables

# sys - system-specific
import sys
sys.argv              # command-line arguments
sys.exit(0)           # exit with status code
sys.path              # module search path

# datetime - date and time
from datetime import datetime, timedelta
now = datetime.now()

# collections - specialized containers
from collections import Counter, defaultdict, deque

# itertools - iterator tools
from itertools import chain, cycle, repeat, product

# functools - higher-order functions
from functools import lru_cache, reduce, partial

# typing - type hints
from typing import List, Dict, Optional, Union, Any

# pathlib - path handling
from pathlib import Path

# subprocess - run external commands
import subprocess
result = subprocess.run(["ls", "-l"], capture_output=True, text=True)

Pip & virtuelle Umgebungen

Verwenden Sie immer virtuelle Umgebungen, um Projektabhängigkeiten zu isolieren. venv ist eingebaut; Alternativen umfassen virtualenv, conda und uv. Fixieren Sie Versionen in requirements.txt für Reproduzierbarkeit. Installieren Sie niemals Pakete global mit --user oder als root – verwenden Sie eine venv.

python
# Create a virtual environment
# $ python -m venv venv

# Activate it
# Windows: venv\Scripts\activate
# Unix:    source venv/bin/activate

# Install packages
# $ pip install requests
# $ pip install requests==2.28.0
# $ pip install "requests>=2.25,<3.0"

# Install from requirements file
# $ pip install -r requirements.txt

# requirements.txt example:
# requests==2.31.0
# numpy>=1.21.0
# pandas~=2.0.0  # compatible release

# List installed packages
# $ pip list
# $ pip freeze > requirements.txt

# Uninstall
# $ pip uninstall requests

# Show package info
# $ pip show requests

# Modern alternative: uv (faster)
# $ uv pip install requests
12

Datum & Uhrzeit

datetime-Modul

Das datetime-Modul bietet die Klassen date, time, datetime und timedelta. datetime.now() gibt lokale Zeit zurück; verwenden Sie datetime.now(timezone.utc) für UTC. weekday() gibt 0-6 (Mo-So) zurück. Verwenden Sie in der Produktion immer zeitzonenbewusste Datetimes, um Mehrdeutigkeit zu vermeiden.

python
from datetime import datetime, date, time, timedelta

# Current date and time
now = datetime.now()          # local time
utc_now = datetime.utcnow()   # UTC (deprecated in 3.12)
utc = datetime.now(timezone.utc)  # preferred

# Current date
today = date.today()

# Create specific date/time
dt = datetime(2024, 1, 15, 10, 30, 0)
d = date(2024, 1, 15)
t = time(10, 30, 0)

# Access components
print(now.year, now.month, now.day)
print(now.hour, now.minute, now.second)
print(now.weekday())  # 0=Monday, 6=Sunday

# From timestamp
ts = 1705315200
dt = datetime.fromtimestamp(ts)

# To timestamp
print(datetime.now().timestamp())

Formatierung & Parsen

strftime (string format time) konvertiert datetime zu String; strptime (string parse time) konvertiert String zu datetime. ISO 8601-Format (isoformat/fromisoformat) ist die beste Wahl zum Speichern von Daten – es ist eindeutig und sortierbar. Merken Sie sich häufige Codes: %Y %m %d %H %M %S.

python
from datetime import datetime

# Format datetime to string (strftime)
dt = datetime(2024, 1, 15, 10, 30)
print(dt.strftime("%Y-%m-%d"))         # 2024-01-15
print(dt.strftime("%Y/%m/%d %H:%M"))   # 2024/01/15 10:30
print(dt.strftime("%B %d, %Y"))        # January 15, 2024
print(dt.strftime("%A"))               # Monday

# Parse string to datetime (strptime)
dt = datetime.strptime("2024-01-15", "%Y-%m-%d")
dt = datetime.strptime("15/01/2024 10:30", "%d/%m/%Y %H:%M")

# Common format codes:
# %Y  year (2024)     %m  month (01)
# %d  day (15)        %H  hour (14)
# %M  minute (30)     %S  second (00)
# %B  month name      %b  month abbrev
# %A  weekday name    %a  weekday abbrev
# %I  12-hour         %p  AM/PM
# %j  day of year     %U  week number

# ISO format (recommended for storage)
iso = dt.isoformat()  # "2024-01-15T10:30:00"
dt = datetime.fromisoformat("2024-01-15T10:30:00")

timedelta & Arithmetik

timedelta repräsentiert eine Dauer. Sie können timedeltas zu datetimes addieren/subtrahieren und zwei datetimes subtrahieren, um ein timedelta zu erhalten. timedelta normalisiert: days=1, hours=25 wird zu days=2, hours=1. total_seconds() gibt die gesamte Dauer in Sekunden zurück.

python
from datetime import datetime, timedelta

now = datetime.now()

# Add/subtract time
tomorrow = now + timedelta(days=1)
last_week = now - timedelta(weeks=1)
in_2_hours = now + timedelta(hours=2)
in_90_days = now + timedelta(days=90)

# Difference between dates
date1 = datetime(2024, 1, 15)
date2 = datetime(2024, 6, 18)
diff = date2 - date1
print(diff.days)         # 155
print(diff.total_seconds())

# timedelta components
td = timedelta(days=5, hours=3, minutes=30)
print(td.days)           # 5
print(td.seconds)        # 12600 (3h 30m in seconds)
print(td.total_seconds())

# Comparisons
if now > date1:
    print("now is later")

# Business day calculation (using numpy)
# import numpy as np
# business_days = np.busday_count(date1.date(), date2.date())

Zeitzonen

Verwenden Sie immer zeitzonenbewusste datetimes (Python 3.9+ ZoneInfo wird gegenüber pytz bevorzugt). Speichern Sie Daten in UTC und konvertieren Sie nur für die Anzeige in lokale Zeit. Naive datetimes (ohne tzinfo) verursachen subtile Bugs. ZoneInfo verwendet die IANA-Zeitzonendatenbank und behandelt DST automatisch.

python
from datetime import datetime, timezone, timedelta

# Timezone-aware datetime
utc_time = datetime.now(timezone.utc)
print(utc_time)  # 2024-01-15 10:30:00+00:00

# Create a timezone (offset-based)
tz_ny = timezone(timedelta(hours=-5), "EST")
ny_time = datetime.now(tz_ny)

# Convert between timezones
utc_time = datetime.now(timezone.utc)
ny_time = utc_time.astimezone(timezone(timedelta(hours=-5)))
tokyo_time = utc_time.astimezone(timezone(timedelta(hours=9)))

# Use zoneinfo (Python 3.9+) for IANA timezones
from zoneinfo import ZoneInfo
tz = ZoneInfo("America/New_York")
dt = datetime.now(tz)
print(dt.tzname())  # EST or EDT

# Common IANA timezones:
# "UTC"
# "America/New_York", "America/Los_Angeles"
# "Europe/London", "Europe/Paris"
# "Asia/Tokyo", "Asia/Shanghai"
# "Australia/Sydney"

# Best practice: store UTC, convert for display
utc_stored = datetime.now(timezone.utc)
local_display = utc_stored.astimezone(ZoneInfo("Asia/Shanghai"))
13

Reguläre Ausdrücke

re-Modul-Grundlagen

re.search() findet die erste Übereinstimmung überall; re.match() nur am Anfang; re.fullmatch() erfordert, dass der gesamte String übereinstimmt. Verwenden Sie Raw Strings (r'...') für Muster, um Backslash-Escape-Probleme zu vermeiden. Match-Objekte bieten group(), start(), end() und span().

python
import re

# re.search - find first match anywhere in string
m = re.search(r"\d{4}", "Order #2024 was placed")
if m:
    print(m.group())  # 2024
    print(m.start(), m.end())  # 9 13

# re.match - match at beginning of string
m = re.match(r"Hello", "Hello, World")
print(m.group())  # Hello

# re.fullmatch - entire string must match
m = re.fullmatch(r"\d+", "12345")
print(bool(m))  # True

# re.findall - all matches as list
emails = re.findall(r"\S+@\S+", text)
numbers = re.findall(r"\d+", "a1b22c333")  # ['1', '22', '333']

# re.finditer - all matches as iterator (with positions)
for m in re.finditer(r"\w+", "Hello World"):
    print(m.group(), m.span())

# Match object methods
m = re.search(r"(\w+)@(\w+)", "[email protected]")
print(m.group())   # user@domain (whole match)
print(m.group(1))  # user (first group)
print(m.group(2))  # domain (second group)
print(m.groups())  # ('user', 'domain')

Muster-Syntax

Regex-Syntax: [] für Zeichenklassen, \d \w \s für häufige Mengen, Quantoren (* + ? {}) für Wiederholung, ^ $ \b für Anker, () für Gruppen, | für Alternation. Verwenden Sie Raw Strings (r'...'), damit Backslashes wörtlich sind. Gierige Quantoren matchen so viel wie möglich; fügen Sie ? für faul hinzu (z. B. *?).

python
import re

# Character classes
re.findall(r"[aeiou]", "hello")      # vowels
re.findall(r"[^aeiou]", "hello")     # non-vowels
re.findall(r"[a-z]", "Hello123")     # lowercase
re.findall(r"[A-Za-z0-9]", "Hi-1!")  # alphanumeric

# Predefined classes
# .   any char except newline
# \d  digit [0-9]      \D  non-digit
# \w  word char [a-zA-Z0-9_]  \W  non-word
# \s  whitespace       \S  non-whitespace

# Quantifiers
# *     0 or more
# +     1 or more
# ?     0 or 1
# {n}   exactly n
# {n,}  n or more
# {n,m} between n and m
re.findall(r"\d{3}", "1234567")      # ['123', '456']
re.findall(r"\d{2,4}", "12345678")   # ['1234', '5678']

# Anchors
# ^  start of string    $  end of string
# \b word boundary
re.findall(r"^\w+", "Hello World")   # ['Hello']
re.findall(r"\b\w+\b", "hi there") # ['hi', 'there']

# Groups & alternation
re.findall(r"(cat|dog)", "cat and dog")  # ['cat', 'dog']
re.findall(r"(\w+)@(\w+\.\w+)", "[email protected]")

Ersetzung & Aufteilung

re.sub() ersetzt Übereinstimmungen – verwenden Sie Backreferences (\1, \2), um auf Gruppen zu verweisen, oder eine Funktion für dynamische Ersetzung. re.split() ist mächtiger als str.split() – es akzeptiert Regex-Muster. Erfassungsgruppen im Muster sind im Ergebnis enthalten. Verwenden Sie re.subn(), um (result, count) zu erhalten.

python
import re

# re.sub - replace matches
result = re.sub(r"\d+", "#", "a1b22c333")
# 'a#b#c#'

# Replace with count limit
result = re.sub(r"\d", "X", "a1b2c3", count=2)
# 'aXbXc3'

# Use backreferences in replacement
result = re.sub(r"(\w+)@(\w+)", r"\2.\1", "user@domain")
# 'domain.user'

# Use function as replacement
def upper(m):
    return m.group().upper()

result = re.sub(r"\b[a-z]", upper, "hello world")
# 'Hello World' (capitalize first letter of each word)

# re.split - split by pattern
parts = re.split(r"[,;\s]+", "a, b; c d")
# ['a', 'b', 'c', 'd']

# Split with capture groups (keeps delimiters)
parts = re.split(r"([,;])", "a,b;c")
# ['a', ',', 'b', ';', 'c']

# Split with maxsplit
parts = re.split(r",", "a,b,c,d", maxsplit=2)
# ['a', 'b', 'c,d']

Kompilierung & Flags

Kompilieren Sie Muster mit re.compile(), wenn Sie sie wiederholt verwenden – es ist schneller. Flags modifizieren Verhalten: IGNORECASE, MULTILINE, DOTALL, VERBOSE (erlaubt Kommentare/Leerzeichen in Mustern). Benannte Gruppen (?P<name>...) verbessern die Lesbarkeit. Lookahead (?=) und Lookbehind (?<=) matchen ohne zu konsumieren.

python
import re

# Compile pattern for reuse (faster when used many times)
email_re = re.compile(r"^[\w.+-]+@([\w-]+\.)+[\w-]+$")
print(email_re.match("[email protected]"))  # match object
print(email_re.match("invalid"))           # None

# Common flags
re.IGNORECASE  # case-insensitive
re.MULTILINE   # ^ and $ match line boundaries
re.DOTALL      # . matches newline too
re.VERBOSE     # allow whitespace & comments in pattern

# Combine flags with |
pattern = re.compile(r"""
    ^           # start of line
    (\w+)      # capture word
    \s+        # whitespace
    (\d+)      # capture number
""", re.VERBOSE | re.MULTILINE)

# Named groups (more readable)
m = re.match(r"(?P<year>\d{4})-(?P<month>\d{2})", "2024-01")
print(m.group("year"))   # 2024
print(m.group("month"))  # 01
print(m.groupdict())     # {'year': '2024', 'month': '01'}

# Lookahead/lookbehind
re.findall(r"\d+(?= dollars)", "100 dollars, 200 euros")
# ['100']  (positive lookahead)
re.findall(r"(?<=\$)\d+", "$100 and $200")
# ['100', '200']  (positive lookbehind)
14

Async & Nebenläufigkeit

asyncio-Grundlagen

asyncio ist Pythons Async-I/O-Framework. 'async def' definiert eine Koroutine; 'await' pausiert, bis ein Ergebnis bereit ist. asyncio.run() startet die Event-Schleife. asyncio.gather() führt Koroutinen nebeneinander aus. Koroutinen ermöglichen hohe Nebenläufigkeit für I/O ohne Threads.

python
import asyncio

# Define a coroutine
async def greet(name, delay):
    await asyncio.sleep(delay)  # non-blocking sleep
    return f"Hello, {name}!"

# Run a coroutine
async def main():
    result = await greet("Alice", 1)
    print(result)

# Run the event loop
asyncio.run(main())

# Concurrent execution with gather
async def main():
    # Run coroutines concurrently
    results = await asyncio.gather(
        greet("Alice", 2),
        greet("Bob", 1),
        greet("Carol", 3)
    )
    print(results)  # all complete after 3 seconds (max delay)

asyncio.run(main())

# asyncio.create_task - schedule without awaiting immediately
async def main():
    task = asyncio.create_task(greet("Alice", 1))
    # do other work here
    result = await task  # await when needed
    print(result)

Async HTTP & Timeouts

asyncio.timeout() (Python 3.11+) bricht Operationen ab, die zu lange dauern. Für HTTP verwenden Sie aiohttp (async) statt requests (sync). asyncio.Queue ermöglicht Producer-Consumer-Pattern. Async ist ideal für I/O-gebundene Arbeit (Netzwerk, Festplatte) – nicht für CPU-gebundene Arbeit.

python
import asyncio

# Async timeout
async def fetch_with_timeout(url, timeout=5):
    try:
        async with asyncio.timeout(timeout):
            # simulate async operation
            await asyncio.sleep(2)
            return f"Data from {url}"
    except asyncio.TimeoutError:
        return "Request timed out"

# Using aiohttp (third-party) for HTTP
# import aiohttp
# 
# async def fetch(url):
#     async with aiohttp.ClientSession() as session:
#         async with session.get(url) as response:
#             return await response.text()

# Producer-consumer pattern
async def producer(queue):
    for i in range(5):
        await asyncio.sleep(0.1)
        await queue.put(f"item-{i}")
    await queue.put(None)  # sentinel

async def consumer(queue):
    while True:
        item = await queue.get()
        if item is None:
            break
        print(f"Processed: {item}")
        queue.task_done()

async def main():
    queue = asyncio.Queue()
    await asyncio.gather(producer(queue), consumer(queue))

asyncio.run(main())

Threading

Threading ist für I/O-gebundene Nebenläufigkeit (Netzwerk, Datei-I/O). Pythons GIL verhindert echte parallele CPU-Ausführung in Threads. Verwenden Sie Lock, um gemeinsamen Zustand vor Race Conditions zu schützen. Für CPU-gebundene Arbeit verwenden Sie stattdessen multiprocessing. Threads teilen Speicher; Prozesse nicht.

python
import threading
import time

# Basic threading
def worker(name, delay):
    print(f"Worker {name} starting")
    time.sleep(delay)
    print(f"Worker {name} done")

# Create and start threads
t1 = threading.Thread(target=worker, args=("A", 2))
t2 = threading.Thread(target=worker, args=("B", 1))
t1.start()
t2.start()

# Wait for threads to complete
t1.join()
t2.join()
print("All done")

# Thread with Lock (for shared state)
counter = 0
lock = threading.Lock()

def increment():
    global counter
    for _ in range(100000):
        with lock:  # acquire/release lock
            counter += 1

threads = [threading.Thread(target=increment) for _ in range(5)]
for t in threads: t.start()
for t in threads: t.join()
print(f"Counter: {counter}")  # 500000 (correct with lock)

Multiprocessing

Multiprocessing umgeht den GIL für echtes CPU-Parallellismus – jeder Prozess hat seinen eigenen Python-Interpreter. Verwenden Sie Pool für parallele map-Operationen. concurrent.futures bietet eine einheitliche API für Threads und Prozesse. Schützen Sie unter Windows immer mit if __name__ == '__main__'.

python
from multiprocessing import Process, Pool, Queue
import os

# Basic process
def worker(name):
    print(f"Process {name} PID: {os.getpid()}")

if __name__ == "__main__":
    p = Process(target=worker, args=("A",))
    p.start()
    p.join()

# Process pool for parallel work
def square(x):
    return x * x

if __name__ == "__main__":
    with Pool(4) as pool:  # 4 worker processes
        results = pool.map(square, range(10))
        print(results)  # [0, 1, 4, 9, ..., 81]
    
    # Asynchronous map
    with Pool(4) as pool:
        result = pool.map_async(square, range(10))
        print(result.get())  # blocks until done

# concurrent.futures (higher-level API)
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor

with ProcessPoolExecutor() as executor:
    results = list(executor.map(square, range(10)))

with ThreadPoolExecutor() as executor:
    futures = [executor.submit(square, i) for i in range(10)]
    results = [f.result() for f in futures]
15

Dekoratoren

Basis-Dekorator

Dekoratoren umschließen eine Funktion, um ihr Verhalten zu erweitern oder zu modifizieren, ohne den Originalcode zu ändern. Die @-Syntax ist syntaktischer Zucker für die Zuweisung des Ergebnisses des Dekoratoraufrufs zurück an den Funktionsnamen. Verwenden Sie *args, **kwargs im Wrapper, damit er mit jeder Signatur funktioniert.

python
# A decorator is a function that takes a function and returns a new function
def uppercase_result(func):
    def wrapper(*args, **kwargs):
        result = func(*args, **kwargs)
        return result.upper()
    return wrapper

@uppercase_result
def greet(name):
    return f"hello, {name}"

print(greet("world"))  # HELLO, WORLD
# @uppercase_result is sugar for: greet = uppercase_result(greet)

functools.wraps (Metadaten erhalten)

Ohne @wraps verliert die umschlossene Funktion ihren ursprünglichen __name__, __doc__ und ihre Signatur – Debugging-Tools und help() zeigen 'wrapper' an. Verwenden Sie immer @functools.wraps(func) innerhalb von Dekoratoren, um Metadaten zu erhalten. Dies ist eine nahezu universelle Best Practice.

python
from functools import wraps

def log_calls(func):
    @wraps(func)  # copies __name__, __doc__, __module__
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__}({args}, {kwargs})")
        return func(*args, **kwargs)
    return wrapper

@log_calls
def add(a, b):
    """Add two numbers."""
    return a + b

print(add.__name__)  # 'add' (not 'wrapper')
print(add.__doc__)   # 'Add two numbers.'
help(add)            # shows original docstring

Dekorator mit Argumenten

Wenn ein Dekorator Argumente annimmt, benötigen Sie drei Verschachtelungsebenen: die Factory (nimmt Argumente), den Dekorator (nimmt die Funktion) und den Wrapper (nimmt Aufrufargumente). @repeat(3) ruft zuerst repeat(3) auf, was den Dekorator zurückgibt, der dann auf die Funktion angewendet wird.

python
# A decorator factory: returns the actual decorator
def repeat(times):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            result = None
            for _ in range(times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@repeat(times=3)
def say_hi(name):
    print(f"Hi, {name}!")

say_hi("Alice")
# Hi, Alice! (printed 3 times)

Klassenbasierter Dekorator

Klassen-Dekoratoren verwenden __init__, um die Funktion zu speichern, und __call__, um Aufrufe abzufangen. Sie sind ideal, wenn der Dekorator Zustand verwalten muss (wie ein Aufrufzähler oder Cache). Die Klasseninstanz ersetzt die Funktion, sodass der Aufruf __call__ auslöst.

python
class CountCalls:
    def __init__(self, func):
        self.func = func
        self.count = 0
        wraps(func)(self)  # preserve metadata

    def __call__(self, *args, **kwargs):
        self.count += 1
        print(f"{self.func.__name__} called {self.count} times")
        return self.func(*args, **kwargs)

@CountCalls
def say_hello():
    print("Hello!")

say_hello()  # count=1
say_hello()  # count=2
say_hello()  # count=3
print(say_hello.count)  # 3

Eingebaute Dekoratoren (@property, @staticmethod, @classmethod)

@property wandelt eine Methode in ein berechnetes Attribut um (ohne Klammern zugänglich). @classmethod empfängt die Klasse als erstes Argument – perfekt für alternative Konstruktoren. @staticmethod empfängt kein implizites erstes Argument – nur eine Funktion, die zufällig im Klassen-Namespace lebt. Zusammen bilden sie das Rückgrat der pythonischen OOP.

python
class Circle:
    pi = 3.14159

    def __init__(self, radius):
        self._radius = radius

    @property
    def area(self):
        return Circle.pi * self._radius ** 2

    @property
    def radius(self):
        return self._radius

    @radius.setter
    def radius(self, value):
        if value < 0:
            raise ValueError("Radius cannot be negative")
        self._radius = value

    @classmethod
    def from_diameter(cls, diameter):
        return cls(diameter / 2)

    @staticmethod
    def is_valid_radius(r):
        return r >= 0

c = Circle(5)
print(c.area)              # 78.54 (no parentheses!)
c.radius = 10             # uses the setter
c2 = Circle.from_diameter(20)  # alternative constructor

Gestapelte Dekoratoren

Beim Stapeln von Dekoratoren werden sie von unten nach oben angewendet (der der Funktion am nächsten liegende läuft zuerst), aber zur Aufrufzeit von oben nach unten ausgeführt. Also umschließt @bold @italic, welches greet umschließt. Das Ergebnis ist wie Zwiebelschichten verschachtelt. Die Reihenfolge ist wichtig – Umkehren ändert die Verschachtelung der Ausgabe.

python
from functools import wraps

def bold(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return f"<b>{func(*args, **kwargs)}</b>"
    return wrapper

def italic(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        return f"<i>{func(*args, **kwargs)}</i>"
    return wrapper

@bold
@italic
def greet(name):
    return f"Hello, {name}"

print(greet("World"))
# <b><i>Hello, World</i></b>
# Applied bottom-up: italic first, then bold
16

Generatoren & Iteratoren

Generator-Funktionen (yield)

Generatoren produzieren Werte träge mit yield – sie pausieren die Ausführung nach jedem yield und setzen fort, wenn next() aufgerufen wird. Das macht sie speichereffizient für große oder unendliche Sequenzen, da nur ein Wert gleichzeitig im Speicher existiert. Einmal erschöpft, kann ein Generator nicht wiederverwendet werden.

python
# A generator uses 'yield' to produce values lazily, one at a time
def count_up_to(n):
    count = 1
    while count <= n:
        yield count
        count += 1

gen = count_up_to(5)
print(next(gen))  # 1
print(next(gen))  # 2
print(list(gen))  # [3, 4, 5] (exhausts the rest)

# Generators are memory-efficient: they don't build the whole list
def fibonacci():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

fib = fibonacci()
print([next(fib) for _ in range(10)])
# [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

Generator-Ausdrücke

Generator-Ausdrücke sind das faule Äquivalent von List-Comprehensions – verwenden Sie Klammern statt eckige Klammern. Sie verwenden konstanten Speicher unabhängig von der Größe, was sie ideal für sum(), max(), any() oder das Einspeisen in andere Iteratoren macht. Bevorzugen Sie sie gegenüber List-Comprehensions, wenn Sie keinen wahlfreien Zugriff benötigen.

python
# Like list comprehensions, but lazy (uses parentheses)
squares_list = [x ** 2 for x in range(10)]      # builds full list
squares_gen  = (x ** 2 for x in range(10))      # lazy generator

print(squares_gen)        # <generator object>
print(next(squares_gen))  # 0
print(next(squares_gen))  # 1

# Memory comparison
import sys
print(sys.getsizeof([x for x in range(10000)]))  # ~87616 bytes
print(sys.getsizeof((x for x in range(10000))))  # ~200 bytes (constant!)

# Use in sum(), list(), any() etc.
total = sum(x ** 2 for x in range(100))  # no extra list created
print(total)  # 328350

Iterator-Protokoll (__iter__, __next__)

Das Iterator-Protokoll erfordert __iter__ (gibt einen Iterator zurück) und __next__ (gibt den nächsten Wert oder löst StopIteration aus). Iterables können durchlaufen werden; Iteratoren produzieren Werte einzeln. Für wiederverwendbare Iterables trennen Sie das Iterable (gibt einen neuen Iterator zurück) vom Iterator (hält den Zustand).

python
class Range2:
    """A custom iterator that yields even numbers."""
    def __init__(self, start, end):
        self.current = start
        self.end = end

    def __iter__(self):
        return self  # the object is its own iterator

    def __next__(self):
        if self.current >= self.end:
            raise StopIteration
        value = self.current
        self.current += 2
        return value

r = Range2(0, 10)
for num in r:
    print(num)  # 0, 2, 4, 6, 8

# An iterable returns a fresh iterator each time __iter__ is called.
# An iterator returns itself and raises StopIteration when exhausted.

send(), throw(), close()

Erweiterte Generator-Methoden ermöglichen bidirektionale Kommunikation: send() übergibt einen Wert an den Generator (wird das Ergebnis von yield), throw() injiziert eine Ausnahme an der yield-Stelle, und close() beendet den Generator. Sie müssen den Generator mit next() 'primen', bevor Sie senden. Diese treiben Koroutinen und Async-Frameworks an.

python
def echo():
    while True:
        received = yield  # yield without a value, receives via send()
        print(f"Echo: {received}")

gen = echo()
next(gen)          # prime the generator (advance to first yield)
gen.send("hello")  # Echo: hello
gen.send("world")  # Echo: world

# throw() injects an exception at the yield point
def safe_gen():
    try:
        while True:
            yield
    except ValueError:
        print("Caught ValueError inside generator")

g = safe_gen()
next(g)
g.throw(ValueError, "boom")  # Caught ValueError inside generator

# close() stops the generator (raises GeneratorExit)
gen.close()

Generator-Pipelines

Generator-Pipelines verketten faule Produzenten, sodass Daten ein Element nach dem anderen durch Stufen fließen – jedes Element wird vollständig verarbeitet, bevor das nächste gelesen wird. Dies vermeidet den Aufbau von Zwischenlisten und ist die Grundlage der Streaming-Datenverarbeitung. Unix-Pipes funktionieren konzeptionell gleich.

python
# Chain generators to build data-processing pipelines
def numbers():
    for i in range(1, 11):
        yield i

def squared(seq):
    for n in seq:
        yield n ** 2

def evens(seq):
    for n in seq:
        if n % 2 == 0:
            yield n

# Each stage processes one item at a time — no intermediate lists
pipeline = evens(squared(numbers()))
print(list(pipeline))  # [4, 16, 36, 64, 100]

# Equivalent with generator expressions:
result = (n for n in (x ** 2 for x in range(1, 11)) if n % 2 == 0)
print(list(result))  # [4, 16, 36, 64, 100]

yield from (Delegation)

yield from delegiert alle yields (und send/throw/close) an einen Sub-Iterator, flattet verschachtelte Strukturen und komponiert Koroutinen. Es ist besonders mächtig für rekursive Generatoren – der klassische Anwendungsfall ist das Flatten beliebig verschachtelter Listen. In Async-Code basiert 'await' auf demselben Konzept.

python
# yield from delegates to a sub-iterator (Python 3.3+)
def flatten(nested):
    for item in nested:
        if isinstance(item, (list, tuple)):
            yield from flatten(item)  # recursive delegation
        else:
            yield item

data = [1, [2, 3, [4, 5]], 6, [7, [8, [9]]]]
print(list(flatten(data)))
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

# yield from also forwards send()/throw() to the sub-generator,
# making it essential for coroutine composition.
17

Kontextmanager

with-Anweisung-Grundlagen

Die with-Anweisung stellt sicher, dass Ressourcen freigegeben werden (Dateien geschlossen, Locks freigegeben, Verbindungen zurückgegeben), selbst wenn Ausnahmen auftreten. Sie ruft __enter__ am Anfang und __exit__ am Ende auf. Bevorzugen Sie immer 'with' gegenüber manuellem try/finally für Ressourcenverwaltung – es ist sicherer und lesbarer.

python
# 'with' guarantees cleanup even if an exception occurs
with open("data.txt", "r") as f:
    content = f.read()
# f.close() is called automatically here, even if read() raised

# Without 'with' you must manually close:
f = open("data.txt", "r")
try:
    content = f.read()
finally:
    f.close()  # easy to forget!

# Common built-in context managers:
with open("out.txt", "w") as f, open("in.txt") as g:
    f.write(g.read())  # both files close automatically

Benutzerdefinierter Kontextmanager (Klasse)

Ein klassenbasierter Kontextmanager implementiert __enter__ (Setup, gibt das Kontextobjekt zurück) und __exit__(exc_type, exc_val, exc_tb) (Aufräumen). Die __exit__-Argumente empfangen Ausnahmeinformationen, falls eine auftrat; True zurückgeben unterdrückt sie. Dieses Pattern ist ideal für komplexes Setup/Teardown wie Datenbanktransaktionen.

python
class Timer:
    def __init__(self, label="Timer"):
        self.label = label

    def __enter__(self):
        import time
        self.start = time.perf_counter()
        return self  # value bound to 'as' variable

    def __exit__(self, exc_type, exc_val, exc_tb):
        import time
        elapsed = time.perf_counter() - self.start
        print(f"{self.label}: {elapsed:.4f}s")
        # Return False (or None) to propagate exceptions
        # Return True to suppress the exception
        return False

with Timer("Processing"):
    total = sum(i ** 2 for i in range(1_000_000))
# Processing: 0.1234s

contextlib.contextmanager

contextlib.contextmanager wandelt eine Generatorfunktion in einen Kontextmanager um – Code vor yield ist __enter__, Code nach yield (in finally) ist __exit__. Dies ist prägnanter als eine Klasse für einfache Fälle. Yielden Sie einen Wert, um ihn der 'as'-Variable bereitzustellen. Verwenden Sie try/finally, um Aufräumen zu garantieren.

python
from contextlib import contextmanager
import time

@contextmanager
def timer(label="Timer"):
    start = time.perf_counter()
    try:
        yield  # code inside the 'with' block runs here
    finally:
        elapsed = time.perf_counter() - start
        print(f"{label}: {elapsed:.4f}s")

with timer("My task"):
    sum(i ** 2 for i in range(1_000_000))
# My task: 0.1234s

# You can also yield a value to bind with 'as'
@contextmanager
def open_db(path):
    db = connect(path)
    try:
        yield db
    finally:
        db.close()

with open_db("app.db") as db:
    db.query("SELECT 1")

Mehrere Kontextmanager

Python 3.10+ erlaubt eingeklammerte mehrzeilige 'with'-Anweisungen für sauberere Syntax. Für dynamische Anzahlen von Kontextmanagern verwaltet contextlib.ExitStack sie als Gruppe und wickelt alle in umgekehrter Reihenfolge ab. ExitStack ist unerlässlich, wenn die Anzahl der Ressourcen bis zur Laufzeit nicht bekannt ist.

python
# Python 3.10+ supports parenthesized context managers
with (
    open("input.txt") as fin,
    open("output.txt", "w") as fout,
):
    fout.write(fin.read())

# Pre-3.10: nest them or use contextlib.ExitStack
from contextlib import ExitStack

files = ["a.txt", "b.txt", "c.txt"]
with ExitStack() as stack:
    handles = [stack.enter_context(open(f)) for f in files]
    for h in handles:
        print(h.read())

contextlib-Dienstprogramme (suppress, redirect)

contextlib.suppress ersetzt try/except/pass für erwartete Ausnahmen – viel lesbarer. redirect_stdout/redirect_stderr erfassen Ausgaben, die sonst an die Konsole ginge, nützlich für Tests oder Logging. Diese Dienstprogramme vermeiden Boilerplate und machen die Absicht explizit.

python
from contextlib import suppress, redirect_stdout, redirect_stderr
import io, warnings

# suppress: ignore specific exceptions (cleaner than try/except/pass)
with suppress(FileNotFoundError):
    os.remove("temp.txt")  # no error if file doesn't exist

# redirect_stdout: capture print output
buffer = io.StringIO()
with redirect_stdout(buffer):
    print("This goes to the buffer, not console")
captured = buffer.getvalue()

# redirect_stderr: capture error/warning output
err_buf = io.StringIO()
with redirect_stderr(err_buf):
    warnings.warn("a warning")
print(err_buf.getvalue())  # the warning text

# Also: contextlib.chdir (3.11+) to temporarily change directory
# from contextlib import chdir
# with chdir("/tmp"): ...

Async-Kontextmanager

Async-Kontextmanager verwenden __aenter__/__aexit__ (beachten Sie das 'a'-Präfix) und die 'async with'-Anweisung. Sie sind unerlässlich für die Verwaltung von Async-Ressourcen wie Datenbankverbindungen oder HTTP-Sessions (z. B. aiohttp.ClientSession). Das Aufräumen läuft auch, wenn ein await oder eine Ausnahme innerhalb des Blocks auftritt.

python
import asyncio

class AsyncDB:
    async def __aenter__(self):
        print("connecting...")
        await asyncio.sleep(0.1)  # simulate async connect
        return self

    async def __aexit__(self, exc_type, exc_val, exc_tb):
        print("closing...")
        await asyncio.sleep(0.1)  # simulate async close
        return False

    async def query(self, sql):
        return f"result of: {sql}"

async def main():
    async with AsyncDB() as db:
        result = await db.query("SELECT 1")
        print(result)

asyncio.run(main())
# connecting...
# result of: SELECT 1
# closing...
18

Typhinweise

Basis-Variablen- & Funktionsannotationen

Typhinweise dokumentieren erwartete Typen, werden aber zur Laufzeit NICHT erzwungen – Python bleibt dynamisch typisiert. Verwenden Sie einen statischen Prüfer wie mypy oder pyright, um Typfehler vor der Ausführung zu erkennen. Die eingebauten Generics (list[str], dict[str, int]) erfordern Python 3.9+; ältere Versionen benötigen typing.List, typing.Dict.

python
# Variable annotations (Python 3.6+)
name: str = "Alice"
age: int = 30
scores: list[float] = [95.5, 88.0, 92.3]
config: dict[str, int] = {"timeout": 30}

# Function annotations
def greet(name: str, excited: bool = False) -> str:
    punctuation = "!" if excited else "."
    return f"Hello, {name}{punctuation}"

print(greet("World", excited=True))  # Hello, World!

# Annotations are optional and NOT enforced at runtime
def add(a: int, b: int) -> int:
    return a + b
add("2", "3")  # runs fine, returns "23" (no error!)

typing-Modul (List, Dict, Tuple, Optional)

Das typing-Modul bietet generische Aliase für älteres Python. Seit 3.9 können Sie eingebaute Typen direkt verwenden (list[str] statt List[str]). Optional[X] ist eine Abkürzung für Union[X, None] – verwenden Sie es, um zu signalisieren, dass eine Funktion None zurückgeben kann, was Aufrufer zwingt, den None-Fall zu behandeln.

python
from typing import List, Dict, Tuple, Set, FrozenSet

# Pre-3.9 style (still works, needed for older Python)
names: List[str] = ["Alice", "Bob"]
scores: Dict[str, int] = {"Alice": 95}
point: Tuple[int, int] = (10, 20)
mixed: Tuple[str, int, float] = ("a", 1, 2.0)
variadic: Tuple[int, ...] = (1, 2, 3)  # variable-length

# Python 3.9+ built-in generics (preferred)
names: list[str] = ["Alice", "Bob"]
scores: dict[str, int] = {"Alice": 95}
point: tuple[int, int] = (10, 20)

# Optional means "could be None"
from typing import Optional
def find_user(uid: int) -> Optional[str]:
    if uid == 1:
        return "Alice"
    return None  # could also just 'return'

Union & Literal-Typen

Union[X, Y] (oder X | Y in 3.10+) bedeutet, dass ein Wert entweder Typ sein kann. Literal beschränkt einen Wert auf spezifische Konstanten – großartig für String-Enums ohne den Enum-Overhead und für überladene Funktionsdispatch. mypy verwendet Literal, um Typen einzuengen und ungültige Argumente zur Prüfzeit zu erkennen.

python
from typing import Union, Literal, overload

# Union: value can be one of several types
def process(data: Union[str, bytes]) -> str:
    if isinstance(data, bytes):
        return data.decode("utf-8")
    return data

# Python 3.10+ union syntax with | (preferred)
def process2(data: str | bytes) -> str:
    if isinstance(data, bytes):
        return data.decode("utf-8")
    return data

# Literal: restrict to specific constant values
def set_mode(mode: Literal["r", "w", "a"]) -> None:
    print(f"Mode set to {mode}")

set_mode("r")   # OK
# set_mode("x")  # mypy error: not a valid literal

# Literal for boolean-like flags
Direction = Literal["up", "down", "left", "right"]

TypeVar & Generics

TypeVar erstellt generische Typvariablen, sodass Funktionen und Klassen Typbeziehungen erhalten können (z. B. 'gibt denselben Typ wie die Eingabe zurück'). Verwenden Sie bound=, um auf einen Subtyp zu beschränken, oder geben Sie Constraints wie TypeVar('T', int, float) an. Generische Klassen verwenden Generic[T] als Basis, um parametrisierte Container zu werden.

python
from typing import TypeVar, Generic, List

T = TypeVar("T")  # a generic type variable

def first(items: List[T]) -> T:
    return items[0]

# Type inference: T is bound to the argument's type
x: int = first([1, 2, 3])        # T = int
y: str = first(["a", "b", "c"])  # T = str

# Bounded TypeVar: T must be a subtype of Number
from typing import TypeVar
from numbers import Number
N = TypeVar("N", bound=Number)

def sum_all(values: list[N]) -> N:
    total = values[0]
    for v in values[1:]:
        total = total + v
    return total

# Generic class
class Stack(Generic[T]):
    def __init__(self) -> None:
        self._items: list[T] = []
    def push(self, item: T) -> None:
        self._items.append(item)
    def pop(self) -> T:
        return self._items.pop()

s: Stack[int] = Stack()
s.push(1)
# s.push("x")  # mypy error

Callable, Typ-Aliasse & Protocols

Callable[[int, str], bool] beschreibt eine Funktion, die int und str annimmt und bool zurückgibt. Typ-Aliasse geben beschreibende Namen für komplexe Typen. Protocol ermöglicht strukturelle (Duck-)Typisierung – jedes Objekt mit den richtigen Methoden erfüllt das Protocol, keine Vererbung erforderlich. Dies ist Pythons Antwort auf Interfaces.

python
from typing import Callable, Protocol, TypeAlias

# Callable signature: Callable[[ArgTypes], ReturnType]
def apply(func: Callable[[int, int], int], a: int, b: int) -> int:
    return func(a, b)

apply(lambda x, y: x + y, 3, 4)  # 7

# Type aliases (3.12+ uses 'type' statement; older uses assignment)
type Vector = list[float]          # 3.12+
Vector2: TypeAlias = list[float]   # 3.10+

def magnitude(v: Vector) -> float:
    return sum(x ** 2 for x in v) ** 0.5

# Protocol: structural typing (duck typing with static checks)
class Drawable(Protocol):
    def draw(self) -> None: ...

def render(obj: Drawable) -> None:
    obj.draw()  # any object with a draw() method works

class Circle:
    def draw(self) -> None:
        print("drawing circle")

render(Circle())  # OK — Circle has draw()

Typprüfung mit mypy

mypy ist der beliebteste statische Typprüfer für Python – er analysiert Typhinweise, ohne den Code auszuführen. Er erkennt None-Handhabungs-Bugs, falsche Argumenttypen und fehlende Rückgaben. Beginnen Sie mit gradueller Typisierung: Fügen Sie Hinweise zu neuem Code hinzu und führen Sie mypy in CI aus. Verwenden Sie --strict für neue Projekte, um umfassende Annotationen zu erzwingen.

python
# Save as example.py, then run:  mypy example.py
from typing import Optional

def divide(a: float, b: float) -> Optional[float]:
    if b == 0:
        return None
    return a / b

result = divide(10, 0)
# Without checking, this would crash at runtime:
# print(result + 1)  # TypeError: NoneType + int

# mypy catches it:
# error: Unsupported operand types for + ("None" and "int")
# fix: check for None first
if result is not None:
    print(result + 1)

# Run strict mode for maximum safety:
#   mypy --strict example.py
# Common strict flags: --disallow-untyped-defs, --no-implicit-optional
19

Datenklassen

Basis-@dataclass

@dataclass auto-generiert __init__, __repr__ und __eq__ basierend auf annotierten Feldern – eliminiert Boilerplate für datenhaltende Klassen. Es ist ideal für Wertobjekte, Konfigurationen, DTOs und Datensätze. Verfügbar seit Python 3.7. Felder müssen Typannotationen haben; die Annotation definiert das Feld.

python
from dataclasses import dataclass

@dataclass
class Point:
    x: float
    y: float

p1 = Point(1.0, 2.0)
p2 = Point(1.0, 2.0)

print(p1)         # Point(x=1.0, y=2.0)  — auto __repr__
print(p1 == p2)   # True  — auto __eq__ (compares fields)
print(p1.x)       # 1.0

# Without @dataclass you'd write all this boilerplate:
# class Point:
#     def __init__(self, x, y): self.x = x; self.y = y
#     def __repr__(self): ...
#     def __eq__(self, other): ...

Standardwerte & default_factory

Veränderbare Standards (Listen, Dicts, Sets) müssen field(default_factory=list) verwenden – [] direkt zu verwenden würde eine Liste über alle Instanzen teilen, ein klassischer Bug. default_factory wird einmal pro Instanz aufgerufen, um ein frisches Objekt zu erstellen. Einfache unveränderliche Standards (int, str, bool, None) können direkt zugewiesen werden.

python
from dataclasses import dataclass, field

@dataclass
class Student:
    name: str
    grade: str = "A"          # simple default
    tags: list[str] = field(default_factory=list)  # mutable default!
    scores: dict[str, int] = field(default_factory=dict)

s = Student("Alice")
print(s)  # Student(name='Alice', grade='A', tags=[], scores={})

s.tags.append("honors")
s2 = Student("Bob")
print(s2.tags)  # [] — each instance gets its own list

# NEVER use [] or {} as a direct default — all instances would share
# the same mutable object (classic Python pitfall).

frozen, eq & order

frozen=True macht eine Datenklasse unveränderlich – Felder können nicht neu zugewiesen werden, und die Instanz wird hashbar (als Dict-Schlüssel oder Set-Mitglied verwendbar). order=True fügt Vergleichsmethoden für Sortierung hinzu. Kombinieren Sie frozen=True mit order=True für unveränderliche, sortierbare Werttypen wie Koordinaten, Farben oder Versionen.

python
from dataclasses import dataclass

# frozen=True makes instances immutable (hashable, usable as dict keys)
@dataclass(frozen=True)
class Color:
    r: int
    g: int
    b: int

c = Color(255, 0, 0)
# c.r = 128  # FrozenInstanceError!
print(hash(c))  # works — frozen dataclasses are hashable

# order=True generates __lt__, __le__, __gt__, __ge__ for sorting
@dataclass(order=True)
class Priority:
    level: int

tasks = [Priority(3), Priority(1), Priority(2)]
tasks.sort()
print(tasks)  # [Priority(level=1), Priority(level=2), Priority(level=3)]

# Common combo: frozen + order for immutable comparable values
@dataclass(frozen=True, order=True)
class Version:
    major: int
    minor: int

__post_init__ & Feldanpassung

__post_init__ läuft automatisch nach dem generierten __init__ – verwenden Sie es, um abgeleitete Felder zu berechnen, Werte zu validieren oder Setup durchzuführen. field(init=False) erstellt ein Feld nicht im Konstruktor (gut für berechnete/zwischengespeicherte Werte). field(repr=False, compare=False) verbirgt Felder vor repr und Gleichheitsprüfungen.

python
from dataclasses import dataclass, field

@dataclass
class User:
    email: str
    _email_normalized: str = field(init=False, repr=False)
    id: int = field(default=0)

    def __post_init__(self):
        # runs after __init__; compute derived fields here
        self._email_normalized = self.email.strip().lower()

u = User("  [email protected] ")
print(u.email)             # '  [email protected] '
print(u._email_normalized) # '[email protected]'

# field(init=False) excludes a field from __init__
# field(repr=False) hides it from the repr
# field(compare=False) excludes from __eq__/__hash__
# field(metadata={...}) attaches custom metadata

Vererbung & slots

Datenklassen unterstützen Vererbung – Kind-Felder werden nach Eltern-Feldern angehängt, und Sie können Eltern-Standards überschreiben. Hinweis: Ein Feld mit einem Standard im Eltern kann nicht von einem Nicht-Standard-Feld im Kind gefolgt werden. slots=True (3.10+) verhindert das Hinzufügen beliebiger Attribute und reduziert den Speicher pro Instanz erheblich – ideal für Millionen kleiner Objekte.

python
from dataclasses import dataclass

@dataclass
class Animal:
    name: str
    sound: str = "..."

@dataclass
class Dog(Animal):
    breed: str = "unknown"
    sound: str = "Woof"  # override parent default

d = Dog("Rex", breed="Labrador")
print(d)  # Dog(name='Rex', sound='Woof', breed='Labrador')

# Python 3.10+: slots=True saves memory (no __dict__)
@dataclass(slots=True)
class Pixel:
    r: int
    g: int
    b: int

p = Pixel(0, 128, 255)
# p.new_field = 1  # AttributeError — slots prevent arbitrary attrs
# Saves ~40-50% memory vs regular dataclass for many instances
20

Collections & Itertools

namedtuple

namedtuple erstellt Tupel-Subklassen mit benannten Feldern – so speichereffizient wie Tupel, aber viel lesbarer. Sie sind unveränderlich, verwenden Sie also _replace(), um modifizierte Kopien zu erstellen. Bevorzugen Sie typing.NamedTuple für neuen Code, da es Typannotationen und Standardwerte unterstützt. Großartig für mehrere Rückgabewerte aus Funktionen.

python
from collections import namedtuple

# Lightweight immutable class with named fields
Point = namedtuple("Point", ["x", "y"])
p = Point(3, 4)
print(p.x, p.y)       # 3 4  — access by name
print(p[0], p[1])     # 3 4  — also by index
print(p._asdict())    # {'x': 3, 'y': 4}

# More memory-efficient than a full class
# Use _replace to create a modified copy (immutable!)
p2 = p._replace(x=10)
print(p2)  # Point(x=10, y=4)

# Python 3.6+ typing.NamedTuple for type hints:
from typing import NamedTuple
class Point3D(NamedTuple):
    x: float
    y: float
    z: float = 0.0

Counter

Counter ist eine Dict-Subklasse zum Zählen hashbarer Objekte – perfekt für Häufigkeitsanalyse, Histogramme und Abstimmungen. most_common(n) gibt die top n Elemente zurück. Fehlende Schlüssel geben 0 statt KeyError zurück. Counter unterstützt +, -, &, | für mengenartige Arithmetik auf Zählungen.

python
from collections import Counter

# Count hashable items
words = "the cat sat on the mat the cat".split()
c = Counter(words)
print(c)  # Counter({'the': 3, 'cat': 2, 'sat': 1, 'on': 1, 'mat': 1})

# Most common elements
print(c.most_common(2))  # [('the', 3), ('cat', 2)]

# Arithmetic on counters
c1 = Counter(a=3, b=1)
c2 = Counter(a=1, b=2)
print(c1 + c2)  # Counter({'a': 4, 'b': 3})
print(c1 - c2)  # Counter({'a': 2})  (drops zero/negatives)

# Missing keys return 0 (not KeyError)
print(c["dog"])  # 0

# Update and elements
c.update(["cat", "cat"])
print(sorted(c.elements()))  # ['cat','cat','cat','cat','mat','on','sat','the','the','the']

defaultdict

defaultdict erstellt automatisch fehlende Schlüssel mit einem Standardwert aus einer Factory-Funktion – list zum Gruppieren, int zum Zählen, set zum Deduplizieren. Dies eliminiert das 'if key not in dict'-Boilerplate. Die Factory wird nur aufgerufen, wenn ein Schlüssel fehlt, nicht bei jedem Zugriff.

python
from collections import defaultdict

# Group items by key without checking if key exists
words = ["apple", "banana", "avocado", "blueberry", "cherry"]
by_first = defaultdict(list)
for w in words:
    by_first[w[0]].append(w)
print(dict(by_first))
# {'a': ['apple', 'avocado'], 'b': ['banana', 'blueberry'], 'c': ['cherry']}

# Counting with int (default 0)
counts = defaultdict(int)
for w in words:
    counts[w[0]] += 1
print(dict(counts))  # {'a': 2, 'b': 2, 'c': 1}

# Nested defaultdicts
tree = defaultdict(lambda: defaultdict(list))
tree["2024"]["Jan"].append("event1")

# vs regular dict: avoids the key-check boilerplate
# d = {}
# for w in words:
#     if w[0] not in d:
#         d[w[0]] = []
#     d[w[0]].append(w)

OrderedDict & deque

deque bietet O(1) append/pop an beiden Enden – verwenden Sie es für Warteschlangen, BFS und Sliding Windows statt Listen (list.pop(0) ist O(n)). Mit maxlen verwirft deque automatisch alte Elemente, perfekt für begrenzte Puffer. OrderedDict wird seit 3.7 weniger benötigt (Dicts sind geordnet), aber move_to_end und popitem sind weiterhin einzigartig nützlich für LRU-Caches.

python
from collections import OrderedDict, deque

# deque: double-ended queue, O(1) append/pop at both ends
dq = deque([1, 2, 3], maxlen=5)
dq.appendleft(0)   # deque([0, 1, 2, 3])
dq.append(4)       # deque([0, 1, 2, 3, 4])
dq.append(5)       # deque([1, 2, 3, 4, 5]) — oldest dropped (maxlen!)
print(dq.popleft())  # 1
print(dq)            # deque([2, 3, 4, 5])

# deque is ideal for queues, BFS, sliding windows
from collections import deque
queue = deque(["task1", "task2"])
queue.append("task3")
next_task = queue.popleft()  # FIFO — O(1) vs list.pop(0) which is O(n)

# OrderedDict: remembers insertion order (regular dicts do too in 3.7+,
# but OrderedDict has move_to_end and equality is order-sensitive)
od = OrderedDict([("a", 1), ("b", 2)])
od.move_to_end("a")  # move to last
print(list(od))  # ['b', 'a']
od.popitem(last=False)  # pop first item (FIFO)

itertools: chain, product, combinations, permutations

itertools bietet schnelle, speichereffiziente Tools für Kombinatorik. chain flattet Iterables träge. product gibt kartesische Produkte (ersetzt verschachtelte for-Schleifen). combinations/permutations generieren Auswahlen, ohne die vollständige Liste zu erstellen – unerlässlich für große oder unendliche Eingaben. Alle geben Iteratoren zurück, also in list() einpacken, um zu betrachten.

python
from itertools import chain, product, combinations, permutations

# chain: flatten multiple iterables
list(chain([1, 2], [3, 4], [5]))  # [1, 2, 3, 4, 5]
list(chain.from_iterable([[1, 2], [3, 4]]))  # [1, 2, 3, 4]

# product: Cartesian product (nested loops)
list(product([1, 2], ["a", "b"]))
# [(1,'a'), (1,'b'), (2,'a'), (2,'b')]
list(product("AB", repeat=2))  # [('A','A'),('A','B'),('B','A'),('B','B')]

# combinations: unordered selections (no repeats)
list(combinations("ABC", 2))  # [('A','B'),('A','C'),('B','C')]
list(combinations("AAA", 2))  # [('A','A'),('A','A'),('A','A')]

# permutations: ordered arrangements
list(permutations("ABC", 2))  # [('A','B'),('A','C'),('B','A'),('B','C'),('C','A'),('C','B')]

# combinations_with_replacement: allow picking same element
from itertools import combinations_with_replacement
list(combinations_with_replacement("AB", 2))  # [('A','A'),('A','B'),('B','B')]

itertools: groupby, accumulate, starmap

groupby gruppiert aufeinanderfolgende Elemente, die einen Schlüssel teilen – sortieren Sie zuerst nach dem Schlüssel, sonst erhalten Sie mehrere Gruppen für denselben Schlüssel. accumulate produziert laufende Summen/Produkte. islice, takewhile und dropwhile sind fale Alternativen zu Slicing und Filterung, die auf jedem Iterator arbeiten, einschließlich unendlichen.

python
from itertools import groupby, accumulate, starmap, islice, takewhile, dropwhile

# groupby: group consecutive items by a key (sort first!)
data = [("A", 1), ("A", 2), ("B", 3), ("B", 4), ("A", 5)]
data.sort(key=lambda x: x[0])  # MUST sort by key first
for key, group in groupby(data, key=lambda x: x[0]):
    print(key, list(group))
# A [('A',1),('A',2),('A',5)]
# B [('B',3),('B',4)]

# accumulate: running aggregate (sum by default)
list(accumulate([1, 2, 3, 4]))  # [1, 3, 6, 10]
import operator
list(accumulate([1, 2, 3, 4], operator.mul))  # [1, 2, 6, 24]

# starmap: unpack args from tuples before calling
list(starmap(pow, [(2, 3), (3, 2), (10, 3)]))  # [8, 9, 1000]

# islice: slice an iterator (doesn't support negative indices)
list(islice(range(100), 5, 10))  # [5, 6, 7, 8, 9]

# takewhile / dropwhile: filter by predicate
list(takewhile(lambda x: x < 5, [1, 4, 6, 3, 8]))  # [1, 4]
list(dropwhile(lambda x: x < 5, [1, 4, 6, 3, 8]))  # [6, 3, 8]

functools: lru_cache, partial, reduce

lru_cache memoisiert Ergebnisse – dramatische Beschleunigungen für rekursive oder teure reine Funktionen; cache_info() zeigt Treffer/Fehl-Statistiken. partial füllt Argumente vor, um spezialisierte Callables zu erstellen. reduce wendet eine Funktion kumulativ an (obwohl sum(), any(), all() es oft ersetzen). cached_property berechnet einmal und cachet dann auf der Instanz.

python
from functools import lru_cache, partial, reduce
import operator

# lru_cache: memoize function results (Least Recently Used)
@lru_cache(maxsize=128)
def fib(n):
    if n < 2:
        return n
    return fib(n - 1) + fib(n - 2)

print(fib(100))  # instant (without cache: impossibly slow)
print(fib.cache_info())  # CacheInfo(hits=98, misses=101, ...)

# partial: fix some arguments, create a new callable
def power(base, exponent):
    return base ** exponent

square = partial(power, exponent=2)
cube = partial(power, exponent=3)
print(square(5))  # 25
print(cube(3))    # 27

# reduce: cumulatively apply a function, reducing to one value
product = reduce(operator.mul, [1, 2, 3, 4])  # 24
# Equivalent: ((1*2)*3)*4

# Python 3.8+: cached_property for lazy computed attributes
from functools import cached_property
class Data:
    @cached_property
    def expensive(self):
        print("computing...")
        return [i ** 2 for i in range(1000000)]
21

JSON & CSV-Verarbeitung

json.dumps & json.loads

json.dumps() (dump string) serialisiert ein Python-Objekt zu einem JSON-String; json.loads() (load string) parst JSON zurück. Verwenden Sie indent für Lesbarkeit, ensure_ascii=False, um Unicode-Zeichen lesbar zu halten, und sort_keys für deterministische Ausgabe. JSON-Schlüssel müssen Strings sein – int-Schlüssel werden zu Strings.

python
import json

# Serialize Python object to JSON string
data = {"name": "Alice", "age": 30, "scores": [95, 88, 92]}
json_str = json.dumps(data)
print(json_str)  # {"name": "Alice", "age": 30, "scores": [95, 88, 92]}

# Pretty-print with indent
print(json.dumps(data, indent=2))
# {
#   "name": "Alice",
#   "age": 30,
#   "scores": [95, 88, 92]
# }

# Parse JSON string to Python object
parsed = json.loads(json_str)
print(parsed["name"])  # Alice
print(type(parsed["scores"]))  # <class 'list'>

# Sort keys, handle non-ASCII
print(json.dumps({"name": "Zoë"}, ensure_ascii=False, sort_keys=True))

JSON-Dateien lesen & schreiben

json.dump() schreibt direkt in ein Dateiobjekt; json.load() liest von einem. Geben Sie immer encoding='utf-8' für Portabilität an. Merken Sie sich das Type-Mapping: JSON-Objekte werden zu Dicts, Arrays zu Listen und Zahlen zu int oder float. Datetimes, Sets und benutzerdefinierte Objekte sind standardmäßig NICHT JSON-serialisierbar.

python
import json

data = {"users": [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}]}

# Write to file
with open("data.json", "w", encoding="utf-8") as f:
    json.dump(data, f, indent=2, ensure_ascii=False)

# Read from file
with open("data.json", "r", encoding="utf-8") as f:
    loaded = json.load(f)

print(loaded["users"][0]["name"])  # Alice

# Type conversions to remember:
# JSON object  <-> Python dict
# JSON array   <-> Python list
# JSON string  <-> Python str
# JSON number  <-> Python int/float
# JSON true/false <-> Python True/False
# JSON null    <-> Python None

Benutzerdefinierte JSON-Kodierung (datetime, benutzerdefinierte Objekte)

Das json-Modul kann datetime, set oder benutzerdefinierte Klassen standardmäßig nicht serialisieren. Stellen Sie eine default-Funktion bereit (aufgerufen für nicht-serialisierbare Objekte) oder eine JSONEncoder-Subklasse. Für Round-Tripping paaren Sie einen benutzerdefinierten Encoder mit einem object_hook in loads(), um die ursprünglichen Typen zu rekonstruieren. So serialisieren ORMs Modellobjekte.

python
import json
from datetime import datetime

# Default behavior: TypeError on non-serializable types
# json.dumps({"now": datetime.now()})  # TypeError!

# Solution 1: default function for unknown types
def default_encoder(obj):
    if isinstance(obj, datetime):
        return obj.isoformat()
    if isinstance(obj, set):
        return sorted(obj)
    raise TypeError(f"Cannot serialize {type(obj)}")

data = {"now": datetime.now(), "tags": {"a", "b"}}
print(json.dumps(data, default=default_encoder))

# Solution 2: custom JSONEncoder subclass
class MyEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, datetime):
            return {"__datetime__": obj.isoformat()}
        return super().default(obj)

print(json.dumps(data, cls=MyEncoder))

# Decoding with object_hook
def decoder(dct):
    if "__datetime__" in dct:
        return datetime.fromisoformat(dct["__datetime__"])
    return dct
json.loads(json.dumps(data, cls=MyEncoder), object_hook=decoder)

CSV-Dateien lesen

Öffnen Sie CSV-Dateien immer mit newline='', um Leerzeilen-Probleme unter Windows zu vermeiden. csv.reader gibt Listen zurück; csv.DictReader gibt Dicts zurück, schlüsselorientiert nach der Kopfzeile. Das csv-Modul verarbeitet Quoting, eingebettete Kommas und Zeilenumbrüche korrekt – teilen Sie CSV-Zeilen niemals manuell mit line.split(','). Verwenden Sie Sniffer, um Trennzeichen automatisch zu erkennen.

python
import csv

# Basic reader: each row is a list of strings
with open("data.csv", newline="", encoding="utf-8") as f:
    reader = csv.reader(f)
    for row in reader:
        print(row)  # ['name', 'age', 'city']

# DictReader: each row is a dict keyed by header
with open("data.csv", newline="") as f:
    reader = csv.DictReader(f)
    for row in reader:
        print(row["name"], row["age"])  # access by column name

# Handle different delimiters and quoting
with open("data.tsv", newline="") as f:
    reader = csv.reader(f, delimiter="\t", quotechar='"')
    for row in reader:
        print(row)

# Sniffer to auto-detect format
with open("unknown.csv", newline="") as f:
    sample = f.read(1024)
    dialect = csv.Sniffer().sniff(sample)
    f.seek(0)
    reader = csv.reader(f, dialect)

CSV-Dateien schreiben

csv.writer schreibt Listen; csv.DictWriter schreibt Dicts mit einer festen Menge von Feldnamen. Verwenden Sie immer newline='', wenn Sie die Datei öffnen. Der quoting-Parameter steuert, wann Felder in Anführungszeichen gesetzt werden – QUOTE_MINIMAL (Standard) nur bei Bedarf, QUOTE_ALL alles, nützlich für strenge Parser.

python
import csv

rows = [
    ["name", "age", "city"],
    ["Alice", 30, "NYC"],
    ["Bob", 25, "LA"],
]

# Basic writer
with open("out.csv", "w", newline="", encoding="utf-8") as f:
    writer = csv.writer(f)
    writer.writerows(rows)  # write multiple rows

# DictWriter: write from dicts
with open("out.csv", "w", newline="") as f:
    fieldnames = ["name", "age", "city"]
    writer = csv.DictWriter(f, fieldnames=fieldnames)
    writer.writeheader()
    writer.writerow({"name": "Alice", "age": 30, "city": "NYC"})
    writer.writerow({"name": "Bob", "age": 25, "city": "LA"})

# Control quoting: QUOTE_MINIMAL (default), QUOTE_ALL, QUOTE_NONNUMERIC
writer = csv.writer(f, quoting=csv.QUOTE_ALL)
# QUOTE_ALL wraps every field in quotes: "Alice","30","NYC"

JSON Lines (NDJSON) & Streaming

JSON Lines (NDJSON) legt ein JSON-Objekt pro Zeile – ideal für Logs, Event-Streams und Append-Only-Dateien, da Sie jede Zeile unabhängig verarbeiten können. Für riesige einzelne JSON-Dokumente verwenden Sie die ijson-Bibliothek zum Stream-Parsen, ohne die gesamte Datei in den Speicher zu laden. NDJSON ist der Standard für viele Datenpipelines.

python
import json

# JSON Lines: one JSON object per line (great for logs, big data)
records = [{"id": 1, "msg": "first"}, {"id": 2, "msg": "second"}]

# Write NDJSON
with open("logs.jsonl", "w") as f:
    for rec in records:
        f.write(json.dumps(rec) + "\n")

# Read NDJSON line by line (memory-efficient for huge files)
with open("logs.jsonl", "r") as f:
    for line in f:
        rec = json.loads(line)
        print(rec["id"], rec["msg"])

# Stream large JSON arrays without loading everything into memory
# Use ijson library for streaming parsing of huge JSON files:
# import ijson
# with open("huge.json", "rb") as f:
#     for item in ijson.items(f, "items.item"):
#         process(item)  # one item at a time
22

Logging & Testing

logging-Grundlagen

Das logging-Modul ist die Standardmethode zum Ausgeben von Diagnoseausgaben – viel besser als print(), da Sie Level, Formate und Ziele steuern. Verwenden Sie logging.getLogger(__name__) pro Modul, damit Sie die Ausführlichkeit pro Modul einstellen können. logging.exception() schließt automatisch den Traceback ein. Konfigurieren Sie basicConfig einmal beim Start.

python
import logging

# Basic configuration (call once at program start)
logging.basicConfig(
    level=logging.DEBUG,
    format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
    datefmt="%Y-%m-%d %H:%M:%S",
)

# Log levels (severity ascending)
logging.debug("Detailed debug info")     # DEBUG (10)
logging.info("General information")       # INFO (20)
logging.warning("Something unexpected")   # WARNING (30)
logging.error("A real error occurred")    # ERROR (40)
logging.critical("System is down")        # CRITICAL (50)

# Logging exceptions with traceback
try:
    1 / 0
except ZeroDivisionError:
    logging.exception("Division failed")  # includes full traceback

# Get a named logger (best practice per module)
logger = logging.getLogger(__name__)
logger.info("Module-specific log")

Logging in Datei & mehrere Handler

Handler leiten Log-Einträge an Ziele – Konsole, Dateien, Netzwerk, E-Mail. RotatingFileHandler begrenzt die Dateigröße und behält Backups, was ungebremstes Log-Wachstum verhindert. Jeder Handler kann sein eigenes Level und Format haben (z. B. detaillierte Logs in Datei, knappe Logs an Konsole). TimedRotatingFileHandler rotiert nach Zeit statt nach Größe.

python
import logging
from logging.handlers import RotatingFileHandler

logger = logging.getLogger("myapp")
logger.setLevel(logging.DEBUG)

# Console handler (INFO and above)
console = logging.StreamHandler()
console.setLevel(logging.INFO)
console.setFormatter(logging.Formatter("%(levelname)s: %(message)s"))

# Rotating file handler (DEBUG and above, max 5MB x 3 backups)
file_handler = RotatingFileHandler(
    "app.log", maxBytes=5_000_000, backupCount=3, encoding="utf-8"
)
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(
    logging.Formatter("%(asctime)s [%(levelname)s] %(name)s: %(message)s")
)

logger.addHandler(console)
logger.addHandler(file_handler)

logger.debug("debug to file only")
logger.info("info to both console and file")
logger.error("error everywhere")

unittest-Grundlagen

unittest ist Pythons eingebautes Test-Framework (xUnit-Stil). Tests leben in Klassen, die von TestCase erben. setUp/tearDown laufen vor/nach jedem Test zur Isolation. Häufige Assertions: assertEqual, assertTrue, assertRaises, assertIn. Ausführen mit python -m unittest zur automatischen Entdeckung von test_*.py-Dateien.

python
import unittest

def add(a, b):
    return a + b

def divide(a, b):
    if b == 0:
        raise ValueError("Cannot divide by zero")
    return a / b

class TestMath(unittest.TestCase):
    def setUp(self):
        # runs before each test method
        self.data = [1, 2, 3]

    def tearDown(self):
        # runs after each test method
        pass

    def test_add(self):
        self.assertEqual(add(1, 2), 3)
        self.assertEqual(add(-1, 1), 0)

    def test_add_types(self):
        self.assertEqual(add("a", "b"), "ab")

    def test_divide_by_zero(self):
        with self.assertRaises(ValueError):
            divide(1, 0)

    def test_membership(self):
        self.assertIn(2, self.data)
        self.assertTrue(3 in self.data)

if __name__ == "__main__":
    unittest.main()
# Run: python -m unittest test_file.py -v

pytest-Grundlagen

pytest ist das beliebteste Python-Test-Tool – einfache assert-Anweisungen geben aussagekräftige Fehlerberichte, kein Boilerplate-Klassen nötig. pytest.raises prüft Ausnahmen mit optionaler Regex-Übereinstimmung. pytest.approx behandelt Float-Vergleich-Ungenauigkeiten. Installieren mit pip install pytest und ausführen mit pytest -v für ausführliche Ausgabe.

python
# test_math.py — pytest is simpler and more powerful than unittest
# Install: pip install pytest
# Run: pytest -v

def add(a, b):
    return a + b

def divide(a, b):
    if b == 0:
        raise ValueError("Cannot divide by zero")
    return a / b

# Plain functions, no classes required
def test_add():
    assert add(1, 2) == 3
    assert add(-1, 1) == 0

def test_add_strings():
    assert add("hello", " world") == "hello world"

# Testing exceptions with pytest.raises
import pytest

def test_divide_by_zero():
    with pytest.raises(ValueError, match="Cannot divide by zero"):
        divide(1, 0)

# Approximate float comparison
def test_float():
    assert 0.1 + 0.2 == pytest.approx(0.3)

pytest-Fixtures

Fixtures sind pytests Dependency Injection – sie stellen Setup-Daten, Mock-Objekte oder Ressourcen für Tests über Parameternamen bereit. yield-basierte Fixtures handhaben sowohl Setup (vor yield) als auch Teardown (nach yield). Scopes steuern die Wiederverwendung: 'session' erstellt die Fixture einmal für den gesamten Lauf, 'module' einmal pro Datei, 'function' (Standard) einmal pro Test.

python
import pytest

# A fixture provides setup data/resources to tests
@pytest.fixture
def sample_list():
    return [1, 2, 3, 4, 5]

# Use fixtures by passing their name as a parameter
def test_length(sample_list):
    assert len(sample_list) == 5

def test_sum(sample_list):
    assert sum(sample_list) == 15

# Fixture with setup AND teardown (yield)
@pytest.fixture
def db_connection():
    print("\n[setup] connecting to DB")
    conn = {"connected": True}
    yield conn  # test runs here; value passed to test
    print("\n[teardown] closing DB")
    conn["connected"] = False

def test_db(db_connection):
    assert db_connection["connected"] is True

# Fixture scopes: function (default), class, module, session
@pytest.fixture(scope="session")
def expensive_resource():
    return load_large_dataset()  # created once per test session

pytest parametrize & Mocking

parametrize führt eine einzelne Testfunktion über mehrere Eingabesätze aus – eliminiert Copy-Paste-Testcode und gibt klare pro-Fall-Ausgabe. unittest.mock.patch ersetzt Funktionen/Objekte durch Mocks für isoliertes Testen. assert_called_once_with verifiziert, dass der Mock korrekt verwendet wurde. @pytest.mark.skip und xfail behandeln unvollständige Tests elegant.

python
import pytest
from unittest.mock import patch, MagicMock

# parametrize: run one test with multiple inputs
@pytest.mark.parametrize("a, b, expected", [
    (1, 2, 3),
    (-1, 1, 0),
    (0, 0, 0),
    (100, 200, 300),
])
def test_add_many(a, b, expected):
    assert add(a, b) == expected

# parametrize with IDs for readable output
@pytest.mark.parametrize("x", [1, 2, 3], ids=["one", "two", "three"])
def test_ids(x):
    assert x > 0

# Mocking: replace external dependencies
def fetch_user(uid):
    # imagine this calls a real API
    return {"id": uid, "name": "real_user"}

@patch("__main__.fetch_user")
def test_with_mock(mock_fetch):
    mock_fetch.return_value = {"id": 1, "name": "mocked"}
    result = fetch_user(1)
    assert result["name"] == "mocked"
    mock_fetch.assert_called_once_with(1)

# Skip and expected failure
@pytest.mark.skip(reason="not implemented yet")
def test_future():
    pass

@pytest.mark.xfail(reason="known bug #42")
def test_known_bug():
    assert 1 == 2
23

Netzwerkprogrammierung

TCP-Server

Erstellt einen TCP-Server mit dem socket-Modul. bind verknüpft den Socket mit einer Adresse, listen setzt die Backlog-Warteschlange, accept blockiert, bis ein Client sich verbindet. Schließen Sie immer Verbindungen, um File-Deskriptoren freizugeben.

python
import socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind(('localhost', 8080))
server.listen(5)
conn, addr = server.accept()
data = conn.recv(1024)
conn.sendall(b'Hello')
conn.close()

TCP-Client

Erstellt einen TCP-Client, der sich mit einem Server verbindet. connect stellt die Verbindung her, sendall sendet alle Bytes, recv liest bis zu den angegebenen Bytes. Verwenden Sie encode/decode für String-zu-Bytes-Konvertierung.

python
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('localhost', 8080))
client.sendall(b'Hello Server')
response = client.recv(1024)
print(response.decode())
client.close()

UDP-Socket

UDP ist verbindungslos: kein Handshake, keine garantierte Zustellung. recvfrom gibt sowohl Daten als auch Absenderadresse zurück. Verwenden Sie SOCK_DGRAM für UDP. Ideal für DNS, Gaming und Echtzeit-Streaming.

python
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(('localhost', 9090))
data, addr = sock.recvfrom(1024)
print(f"From {addr}: {data.decode()}")
sock.sendto(b'Reply', addr)

HTTP-Server

Das http.server-Modul bietet einen einfachen HTTP-Server. Leiten Sie BaseHTTPRequestHandler ab und überschreiben Sie do_GET, do_POST. Nur für Entwicklung verwenden; verwenden Sie gunicorn für Produktion.

python
from http.server import HTTPServer, BaseHTTPRequestHandler
class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header('Content-Type', 'text/html')
        self.end_headers()
        self.wfile.write(b'<h1>Hello</h1>')
HTTPServer(('localhost', 8000), Handler).serve_forever()

Socket-Timeout

settimeout setzt ein Timeout für alle Socket-Operationen. Wenn eine Operation das Timeout überschreitet, wird eine socket.timeout-Ausnahme ausgelöst. Verwenden Sie try/finally, um Aufräumen zu garantieren.

python
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(5.0)
try:
    sock.connect(('example.com', 80))
    data = sock.recv(1024)
except socket.timeout:
    print('Connection timed out')
finally:
    sock.close()
24

Datenbank (SQLite)

Tabelle erstellen

sqlite3 ist in Python eingebaut. connect erstellt oder öffnet eine Datenbankdatei. CREATE TABLE IF NOT EXISTS verhindert Fehler, wenn die Tabelle existiert. Rufen Sie immer commit auf, um Änderungen zu speichern.

python
import sqlite3
conn = sqlite3.connect('example.db')
cursor = conn.cursor()
cursor.execute('''CREATE TABLE IF NOT EXISTS users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL, email TEXT UNIQUE, age INTEGER)''')
conn.commit()

Daten einfügen

Verwenden Sie immer parametrisierte Abfragen (?-Platzhalter), um SQL-Injection zu verhindern. lastrowid gibt die auto-inkrementierte ID zurück. Verwenden Sie niemals String-Formatierung für SQL-Werte.

python
cursor.execute(
    'INSERT INTO users (name, email, age) VALUES (?, ?, ?)',
    ('Alice', '[email protected]', 30))
conn.commit()
print(f"ID: {cursor.lastrowid}")

Daten abfragen

fetchall gibt alle übereinstimmenden Zeilen als Liste von Tupeln zurück. fetchone gibt eine einzelne Zeile oder None zurück. Für große Ergebnismengen iterieren Sie direkt über den Cursor.

python
cursor.execute('SELECT * FROM users WHERE age > ?', (25,))
rows = cursor.fetchall()
for row in rows:
    print(row)
cursor.execute('SELECT * FROM users WHERE id = ?', (1,))
user = cursor.fetchone()

Update & Delete

UPDATE modifiziert vorhandene Zeilen, DELETE entfernt sie. rowcount gibt betroffene Zeilen an. Verwenden Sie immer WHERE mit DELETE. commit persistiert Änderungen.

python
cursor.execute('UPDATE users SET age = ? WHERE name = ?', (31, 'Alice'))
cursor.execute('DELETE FROM users WHERE age < ?', (18,))
conn.commit()
print(f"Affected: {cursor.rowcount} rows")

Row Factory

Die Verwendung von conn als Kontextmanager committet bei Erfolg und rollt bei Ausnahme zurück. row_factory = sqlite3.Row ermöglicht den Zugriff auf Spalten nach Namen.

python
conn = sqlite3.connect('example.db')
conn.row_factory = sqlite3.Row
with conn:
    conn.execute('INSERT INTO users (name, email) VALUES (?, ?)',
                 ('Bob', '[email protected]'))
for row in conn.execute('SELECT * FROM users'):
    print(row['name'], row['email'])
25

Web Scraping

BeautifulSoup-Grundlagen

requests holt HTML-Inhalt, BeautifulSoup parst ihn. html.parser ist eingebaut; lxml ist schneller. Prüfen Sie immer response.status_code vor dem Parsen.

python
import requests
from bs4 import BeautifulSoup
resp = requests.get('https://example.com')
soup = BeautifulSoup(resp.text, 'html.parser')
print(soup.title.string)
print(soup.find('h1').text)

Elemente finden

find_all gibt alle übereinstimmenden Elemente zurück, find das erste. Verwenden Sie class_ (mit Unterstrich). select verwendet CSS-Selektoren für komplexe Abfragen.

python
links = soup.find_all('a')
for link in links:
    print(link.get('href'), link.text)
article = soup.find('div', class_='article')
items = soup.select('ul.list > li.item')

Tabellen extrahieren

Tabellen sind als tr (Zeilen) strukturiert, die td (Daten) oder th (Kopf) Zellen enthalten. strip entfernt Leerzeichen. find_all akzeptiert eine Liste von Tag-Namen.

python
table = soup.find('table')
for row in table.find_all('tr'):
    cols = row.find_all(['td', 'th'])
    data = [col.text.strip() for col in cols]
    print(data)

Paginierung behandeln

Paginierung wird durch das Folgen von Next-Page-Links behandelt. select_one gibt die erste Übereinstimmung oder None zurück. Fügen Sie time.sleep zwischen Anfragen hinzu.

python
all_items = []
url = 'https://example.com/page/1'
while url:
    resp = requests.get(url)
    soup = BeautifulSoup(resp.text, 'html.parser')
    all_items.extend([i.text for i in soup.select('.item')])
    next_link = soup.select_one('a.next')
    url = next_link.get('href') if next_link else None

Als CSV speichern

csv.DictWriter schreibt Dictionaries in CSV. newline verhindert zusätzliche Leerzeilen unter Windows. encoding=utf-8 verarbeitet Sonderzeichen.

python
import csv
with open('data.csv', 'w', newline='', encoding='utf-8') as f:
    writer = csv.DictWriter(f, fieldnames=['name', 'price'])
    writer.writeheader()
    for item in scraped_data:
        writer.writerow(item)
26

Async Web (aiohttp)

HTTP-Client

aiohttp bietet async HTTP. ClientSession verwaltet Connection-Pooling. async with stellt Aufräumen sicher. asyncio.run führt die Koroutine aus.

python
import aiohttp, asyncio
async def fetch(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as resp:
            return await resp.text()
data = asyncio.run(fetch('https://api.example.com'))

Gleichzeitige Anfragen

asyncio.gather führt Koroutinen gleichzeitig aus und reduziert die Gesamtzeit. Alle Anfragen teilen sich dieselbe Session. Verwenden Sie ein Semaphor, um die Nebenläufigkeit zu begrenzen.

python
async def fetch_all(urls):
    async with aiohttp.ClientSession() as session:
        tasks = [session.get(url) for url in urls]
        responses = await asyncio.gather(*tasks)
        return [await r.text() for r in responses]

Web-Server

aiohttp.web erstellt async Web-Server. Routen werden mit HTTP-Methode und Pfad-Muster definiert. match_info extrahiert Pfad-Parameter.

python
from aiohttp import web
async def handle(request):
    name = request.match_info.get('name', 'World')
    return web.json_response({'message': f'Hello, {name}!'})
app = web.Application()
app.add_routes([web.get('/', handle), web.get('/{name}', handle)])
web.run_app(app, port=8080)

WebSocket-Server

WebSockets ermöglichen bidirektionale Echtzeit-Kommunikation. WebSocketResponse behandelt den Upgrade-Handshake. async for iteriert über Nachrichten.

python
async def ws_handler(request):
    ws = web.WebSocketResponse()
    await ws.prepare(request)
    async for msg in ws:
        if msg.type == aiohttp.WSMsgType.TEXT:
            await ws.send_str(f'Echo: {msg.data}')
    return ws

Session mit Cookies

ClientSession persistiert Cookies automatisch über Anfragen hinweg. Unerlässlich für authentifiziertes Scraping. Verwenden Sie eine einzelne Session für alle Anfragen.

python
async def login_and_fetch():
    async with aiohttp.ClientSession() as session:
        await session.post('https://example.com/login',
                          data={'user': 'admin', 'pass': '123'})
        resp = await session.get('https://example.com/dashboard')
        return await resp.text()
27

Multiprocessing Deep Dive

Process Pool

Pool verwaltet Worker-Prozesse. map verteilt Arbeit parallel. apply_async führt eine einzelne Funktion asynchron aus. Verwenden Sie unter Windows immer die if __name__ == main-Wächter.

python
from multiprocessing import Pool
def square(x): return x * x
if __name__ == '__main__':
    with Pool(4) as pool:
        results = pool.map(square, range(10))
        result = pool.apply_async(square, (100,))
        print(result.get(timeout=5))

Shared Memory

Value und Array erstellen gemeinsamen Speicher zwischen Prozessen. Verwenden Sie get_lock, um den Zugriff zu synchronisieren und Race Conditions zu verhindern.

python
from multiprocessing import Value, Array
counter = Value('i', 0)
arr = Array('d', [0.0, 1.0, 2.0])
with counter.get_lock():
    counter.value += 1

Queue-Kommunikation

Queue ermöglicht sichere Kommunikation zwischen Prozessen. put fügt Elemente hinzu, get ruft sie ab. Queue ist prozesssicher und handhabt Locking intern.

python
from multiprocessing import Process, Queue
def worker(q):
    q.put('Data from worker')
if __name__ == '__main__':
    q = Queue()
    p = Process(target=worker, args=(q,))
    p.start()
    print(q.get())
    p.join()

Pipe

Pipe erstellt einen bidirektionalen Kommunikationskanal. send und recv übertragen Python-Objekte via Pickling. Pipe ist schneller als Queue für Punkt-zu-Punkt-Kommunikation.

python
from multiprocessing import Process, Pipe
def worker(conn):
    conn.send(['hello', 'world'])
    msg = conn.recv()
    conn.close()
if __name__ == '__main__':
    parent, child = Pipe()
    p = Process(target=worker, args=(child,))
    p.start()
    print(parent.recv())
    parent.send('acknowledged')
    p.join()

Synchronisation

Lock stellt sicher, dass nur ein Prozess gleichzeitig auf eine gemeinsame Ressource zugreift. with lock akquiriert und gibt automatisch frei. Weitere Primitive: RLock, Semaphore, Event.

python
from multiprocessing import Process, Lock
def safe_print(lock, msg):
    with lock:
        print(msg)
if __name__ == '__main__':
    lock = Lock()
    procs = [Process(target=safe_print, args=(lock, f'Task {i}'))
             for i in range(5)]
    for p in procs: p.start()
    for p in procs: p.join()
28

Virtuelle Umgebungen Deep Dive

venv-Modul

venv erstellt isolierte Python-Umgebungen mit eigenen Paketverzeichnissen. Aktivierung modifiziert PATH. Aktivieren Sie immer vor der Installation von Abhängigkeiten.

python
# Create
python -m venv myenv
# Activate (Linux/Mac)
source myenv/bin/activate
# Activate (Windows)
myenv\Scripts\activate
# Deactivate
deactivate

requirements.txt

requirements.txt listet Projektabhängigkeiten auf. == fixiert exakte Versionen, >= erlaubt Upgrades innerhalb eines Bereichs. Committen Sie immer in die Versionskontrolle.

python
# Generate
pip freeze > requirements.txt
# Install
pip install -r requirements.txt
# Pin versions
flask==2.3.3
requests>=2.28.0,<3.0.0

Poetry

Poetry ist ein moderner Abhängigkeitsmanager. pyproject.toml ersetzt requirements.txt. Virtuelle Umgebungen werden automatisch verwaltet.

python
# Initialize
poetry init
# Add dependency
poetry add flask
poetry add pytest --group dev
# Install all
poetry install
# Run command
poetry run python app.py

pipenv

pipenv kombiniert pip und virtualenv. Pipfile deklariert Abhängigkeiten, Pipfile.lock fixiert exakte Versionen. --dev trennt Entwicklungsabhängigkeiten.

python
# Create environment
pipenv install
# Add package
pipenv install requests
pipenv install pytest --dev
# Activate shell
pipenv shell
# Run command
pipenv run python app.py

Conda-Umgebungen

Conda verwaltet sowohl Python- als auch Nicht-Python-Abhängigkeiten. environment.yml erfasst die vollständige Umgebung. Ideal für Data Science mit binären Abhängigkeiten.

python
# Create
conda create -n myenv python=3.11
# Activate
conda activate myenv
# Export
conda env export > environment.yml
# Recreate
conda env create -f environment.yml
29

pip Erweiterte Nutzung

Installieren von Git

Installieren Sie Pakete direkt von Git-Repositories. Nützlich für unveröffentlichte Versionen, Forks oder private Pakete. @branch oder @commit fixiert auf eine spezifische Version.

python
# From GitHub
pip install git+https://github.com/user/repo.git
# Specific branch
pip install git+https://github.com/user/repo.git@branch-name
# Specific commit
pip install git+https://github.com/user/repo.git@abc123

Editable Install

Editable Install (-e) verlinkt das Paket statt zu kopieren. Änderungen sind sofort verfügbar ohne Neuinstallation. Unerlässlich für Paketentwicklung.

python
# Install in development mode
pip install -e .
# From a specific path
pip install -e /path/to/package
# With extras
pip install -e ".[dev,test]"

Constraints & Hashes

Constraints begrenzen, welche Versionen installiert werden können. Hash-Prüfung verifiziert die Paketintegrität und verhindert Supply-Chain-Angriffe.

python
# constraints.txt
flask==2.3.3
pip install -c constraints.txt flask
# Hash checking
pip install --require-hashes -r requirements.txt

Cache-Verwaltung

pip cachet heruntergeladene Wheels. --no-cache-dir erzwingt frische Downloads. Purge gibt Speicherplatz frei, wenn der Cache zu groß wird.

python
# Show cache info
pip cache info
# List cached packages
pip cache list
# Purge entire cache
pip cache purge
# Install with no cache
pip install --no-cache-dir flask

Custom Index

--index-url spezifiziert ein benutzerdefiniertes Paket-Repository. --extra-index-url fügt einen Fallback hinzu. --trusted-host umgeht SSL für interne Registries.

python
# Use custom index
pip install --index-url https://pypi.custom.com/simple/ flask
# Extra index (fallback)
pip install --extra-index-url https://pypi.custom.com/simple/ flask
# Trusted host (no SSL)
pip install --trusted-host pypi.custom.com flask
30

Typprüfung (mypy)

Basis-Typhinweise

Typhinweise annotieren Funktionsparameter und Rückgabetypen. Python 3.9+ erlaubt eingebaute Typen direkt. Hinweise ermöglichen statische Analyse mit mypy.

python
def greet(name: str, times: int = 1) -> str:
    return (f"Hello, {name}! " * times).strip()
def process(data: list[int]) -> dict[str, int]:
    return {str(x): x for x in data}

Optional und Union

Optional[X] ist äquivalent zu X | None (Python 3.10+). Union-Typen erlauben mehrere mögliche Typen. mypy prüft, dass alle Codepfade alle Typen behandeln.

python
from typing import Optional
def find(items: list[int], target: int) -> int | None:
    for i, v in enumerate(items):
        if v == target: return i
    return None

Generische Typen

Generics erstellen wiederverwendbare typsichere Container. TypeVar definiert eine Typvariable, Generic macht die Klasse generisch. mypy stellt Typkonsistenz sicher.

python
from typing import TypeVar, Generic
T = TypeVar('T')
class Stack(Generic[T]):
    def __init__(self) -> None:
        self._items: list[T] = []
    def push(self, item: T) -> None:
        self._items.append(item)
    def pop(self) -> T:
        return self._items.pop()

Protocol

Protocol definiert strukturelle Subtypisierung (Duck-Typing mit Typprüfung). Jede Klasse mit den erforderlichen Methoden erfüllt das Protocol, keine Vererbung nötig.

python
from typing import Protocol
class Closeable(Protocol):
    def close(self) -> None: ...
def cleanup(resource: Closeable) -> None:
    resource.close()
class File:
    def close(self) -> None: print("Closed")
cleanup(File())  # OK - File has close()

mypy-Konfiguration

mypy.ini konfiguriert die Strenge der Typprüfung. strict aktiviert alle Prüfungen. Pro-Modul-Overrides lockern Regeln für Tests oder Legacy-Code.

python
# mypy.ini
[mypy]
python_version = 3.11
strict = True
warn_return_any = True
disallow_untyped_defs = True
[mypy-tests.*]
ignore_errors = True
31

Performance-Tipps

Liste vs. Generator

Listen speichern alle Elemente im Speicher; Generatoren produzieren Werte bei Bedarf. Verwenden Sie Generatoren für große Sequenzen, die einmal iteriert werden.

python
# List: all in memory
squares = [x**2 for x in range(1000000)]
# Generator: lazy evaluation
squares_gen = (x**2 for x in range(1000000))
import sys
print(sys.getsizeof(squares))      # ~8MB
print(sys.getsizeof(squares_gen))  # ~200 bytes

String-Verkettung

String-Verkettung mit += ist O(n^2). join ist O(n). f-strings sind die schnellste Interpolationsmethode.

python
# Slow: creates intermediate strings
result = ""
for s in parts:
    result += s
# Fast: join in one operation
result = "".join(parts)
# Fast: f-strings
msg = f"Hello, {name}!"

Lokale Variablen

Lokale Variablen-Lookups sind schneller als globale oder Attribut-Lookups. Das Zuweisen häufig verwendeter Funktionen zu lokalen Variablen beschleunigt Schleifen.

python
import math
# Slow: global lookup
def compute_slow(values):
    return [math.sqrt(v) for v in values]
# Fast: local reference
def compute_fast(values):
    sqrt = math.sqrt
    return [sqrt(v) for v in values]

__slots__

__slots__ verhindert die Erstellung von __dict__ und spart 40-50% Speicher pro Instanz. Bedeutend beim Erstellen von Millionen Objekten. Kann nicht gelistete Attribute hinzufügen.

python
class Point:
    __slots__ = ('x', 'y')
    def __init__(self, x, y):
        self.x = x
        self.y = y
p = Point(1, 2)
# p.z = 3  # AttributeError

timeit & cProfile

timeit misst die Ausführungszeit kleiner Snippets. cProfile zeigt, wo Zeit verbracht wird. Verwenden Sie Profiling vor dem Optimieren, um tatsächliche Engpässe zu finden.

python
import timeit
t = timeit.timeit('sum(range(100))', number=10000)
print(f"{t:.4f}s")
import cProfile
cProfile.run('sum(x**2 for x in range(10000))')
32

Häufige Fallstricke

Veränderbare Standardargumente

Standardargumentwerte werden einmal zur Definitionszeit ausgewertet. Veränderbare Standards werden über alle Aufrufe geteilt. Verwenden Sie immer None als Standard.

python
# BUG: default list is shared
def add_item(item, lst=[]):
    lst.append(item)
    return lst
print(add_item(1))  # [1]
print(add_item(2))  # [1, 2]!
# FIX: use None
def add_item(item, lst=None):
    if lst is None: lst = []
    lst.append(item)
    return lst

Späte Bindung von Closures

Closures erfassen Variablen per Referenz. Wenn Lambdas aufgerufen werden, hat die Schleifenvariable ihren Endwert. Standardargumente erfassen den aktuellen Wert.

python
# BUG: all print 2
funcs = [lambda: i for i in range(3)]
print([f() for f in funcs])  # [2, 2, 2]
# FIX: default argument
funcs = [lambda i=i: i for i in range(3)]
print([f() for f in funcs])  # [0, 1, 2]

Integer-Caching

Python cachet kleine Integer. is prüft Identität, == prüft Gleichheit. Verwenden Sie niemals is für Wertevergleiche; verwenden Sie is nur für None, True, False.

python
# Small integers cached (-5 to 256)
a = 256; b = 256
print(a is b)  # True (cached)
c = 257; d = 257
print(c is d)  # False (not cached)
print(c == d)  # True

is vs ==

is prüft, ob zwei Referenzen auf dasselbe Objekt verweisen. == prüft, ob zwei Objekte denselben Wert haben. Verwenden Sie is nur für None, True, False.

python
a = [1, 2, 3]; b = [1, 2, 3]
print(a == b)  # True (same values)
print(a is b)  # False (different objects)
# Correct usage of is
if x is None: ...
if x is not None: ...

GIL

Der GIL erlaubt nur einem Thread gleichzeitig, Python-Bytecode auszuführen. Threading ist effektiv für I/O-gebundene Aufgaben. Verwenden Sie multiprocessing für CPU-gebundenen Parallellismus.

python
# GIL prevents true parallelism for CPU-bound tasks
import threading
def cpu_work():
    total = sum(i**2 for i in range(10**7))
# Use multiprocessing for CPU work
from multiprocessing import Pool
with Pool(4) as p:
    p.map(cpu_work, range(4))

Verwandte Python-Snippets

Copy-paste ready code for common tasks.

Dictionary nach Wert sortieren

Ein Python-Dictionary nach seinen Werten in absteigender Reihenfolge sortieren.

List Comprehension

Schnell Listen mit List Comprehensions generieren.

Dictionary-Zusammenführung

Mehrere Möglichkeiten, Dictionaries zusammenzuführen.

Datei Lesen/Schreiben

Verschiedene Möglichkeiten, Dateien zu lesen und zu schreiben.

CSV-Verarbeitung

CSV-Dateien mit dem csv-Modul lesen und schreiben.

JSON-Verarbeitung

JSON-Serialisierung und -Deserialisierung.

Regex-Matching

Regex-Matching mit dem re-Modul durchführen.

Datumsbehandlung

Daten und Zeiten mit datetime behandeln.

Decorators

Decorators definieren und verwenden.

Generators

Speicher sparen mit Generators.

Context Manager

Benutzerdefinierte Context Manager.

Exception Handling

Vollständiger Exception-Handling-Mechanismus.

Klassen-Vererbung

Klassen-Vererbung und Methoden-Überschreibung.

Multithreading

Multithreading mit dem threading-Modul implementieren.

Multiprocessing

Echte Parallelität mit Multiprocessing erreichen.

asyncio Asynchrone Programmierung

Asynchrone Nebenläufigkeit mit asyncio implementieren.

Socket-Programmierung

TCP-Socket-Server und -Client.

HTTP-Anfragen

HTTP-Anfragen mit der requests-Bibliothek senden.

Datenbank-Operationen

Datenbanken mit sqlite3 bedienen.

Virtuelle Umgebung

Python-virtuelle Umgebungen erstellen und verwalten.

pip Install

Gängige pip-Paketverwaltungsbefehle.

Umgebungsvariablen

Umgebungsvariablen lesen und setzen.

Logging

Das logging-Modul konfigurieren und verwenden.

Unit-Tests

Unit-Tests mit unittest schreiben.

Type Hints

Code-Lesbarkeit mit Typ-Annotationen verbessern.

Dataclass

Klassendefinitionen mit dataclass vereinfachen.

Enum

Enum-Typen mit Enum definieren.

Property-Decorator

Attributzugriff mit property steuern.

Magic Methods

Häufige Magic-Method-Beispiele.

Iterator

Benutzerdefinierte Iterator-Implementierung.

Coroutine

Grundlegende Verwendung von Coroutinen.

Was this helpful?

Learning path

Learn from scratch

Learn this language from the ground up with structured lessons.