1Welche eingebauten Datentypen hat Python und welche sind veränderbar?
Python verfügt über skalare Typen wie NoneType, bool, int, float und complex; Text- und Binärtypen wie str, bytes und bytearray; Sequenztypen wie list, tuple und range; sowie Sammlertypen wie dict, set und frozenset. Der wichtige Unterscheidungspunkt für Interviews ist die Veränderbarkeit (Mutability): list, dict, set und bytearray können an Ort und Stelle geändert werden, während Zahlen, bool, str, bytes, tuple, range, frozenset und None unveränderlich sind. Ein nützlicher Nuance ist, dass die Elementreferenzen eines Tupels selbst fixiert sind, sich aber der interne Zustand eines referenzierten veränderbaren Objekts dennoch ändern kann.
2Wie unterscheiden sich Listen, Tupel, Sets und Dicts in echtem Code?
Eine Liste ist eine geordnete, veränderbare Sequenz, daher eignet sie sich gut für Arbeitswarteschlangen, gesammelte Ergebnisse und geordnete Daten, die sich ändern. Ein Tupel ist geordnet, aber unveränderlich, weshalb es nützlich für feste Datensätze, Funktionsrückgaben und Werte ist, die nicht neu zugewiesen werden sollten. Ein Set speichert eindeutige hashbare Objekte und ist ideal für Mitgliedschaftsprüfungen oder Deduplizierung. Ein dict ordnet hashbare Schlüssel Werten zu und ist die normale Struktur für Indizes, Suchen und JSON-ähnliche Daten. In modernem Python behält das dict die Einfügereihenfolge bei, aber sein Hauptzweck bleibt der schlüsselbasierte Zugriff.
items = ["a", "b", "a"]
unique = set(items)
index = {"user_id": 42}
point = (10, 20)
== fragt, ob zwei Objekte gleich sind (nach Wert), normalerweise über __eq__. is fragt, ob sich zwei Referenzen auf dasselbe Objekt im Speicher beziehen. In Produktionscode sollte is normalerweise für Singleton-Prüfungen reserviert sein, wie z. B. value is None oder value is True, nur wenn die Identität wirklich wichtig ist, oder bei Sentinel-Objekten. Verlassen Sie sich nicht auf Implementierungsdetails wie das Caching kleiner Ganzzahlen oder String-Interning; diese können dazu führen, dass is in Beispielen zu funktionieren scheint, obwohl es die falsche semantische Wahl ist.
4Was ist ein hashbarer Objekt und warum ist das wichtig?
Ein hashbares Objekt hat einen Hashwert, der während seiner Lebensdauer stabil bleibt, und ein Gleichheitsverhalten, das mit diesem Hash konsistent ist. Die Hashbarkeit ist wichtig, weil Diktier-Schlüssel (dict keys) und Set-Elemente davon abhängen. Die meisten unveränderlichen Built-ins sind hashbar, aber nicht alle Objekte innerhalb eines unveränderlichen Containers werden automatisch hashbar: Ein Tupel, das eine Liste enthält, ist nicht hashbar. In Interviews sollte man dies mit dem Design verknüpfen: Wenn ein Objekt als Schlüssel verwendet wird, dürfen Felder, die an __eq__ oder __hash__ teilnehmen, nach der Einfügung nicht mehr geändert werden, da sonst das Objekt in einem Dict oder Set unzugänglich werden kann.
5Wie funktioniert Slicing, einschließlich negativer Indizes und Schritten?
Eine Slice `sequence[start:stop:step]` beinhaltet `start`, schließt `stop` aus und bewegt sich mit `step`. Fehlende Grenzen werden aus der Richtung des Schritts abgeleitet. Negative Indizes zählen vom Ende, daher ist `s[-1]` das letzte Element. Ein negativer Schritt geht von rechts nach links; `s[::-1]` erstellt für viele Sequenztypen eine umgekehrte Sequenz. Der Kompromiss besteht darin, dass Slicing oft ein neues Objekt erzeugt, es ist also ausdrucksstark, aber nicht immer kostenlos bei großen Datenmengen.
text = "interview"
print(text[1:5])
print(text[::-1])
Python übergibt Objektreferenzen durch Zuweisung, was oft als Call by Sharing bezeichnet wird. Die Funktion erhält lokale Parameternamen, die auf dieselben Objekte verweisen wie die vom Aufrufer bereitgestellten. Wenn das Objekt mutierbar ist, kann die Funktion es mutieren und der Aufrufer wird die Änderung feststellen. Wenn die Funktion den Parameternamen neu zuweist, ändert sich nur die lokale Bindung. Eine gute Antwort in einem Interview unterscheidet zwischen In-Place-Mutation und lokaler Neuzuweisung: items.append(...) oder items.clear() ändern die Liste des Aufrufers, während items = [] innerhalb der Funktion nur den lokalen Namen auf eine neue Liste zeigt.
7Warum sind veränderliche Standardargumente gefährlich?
Standardargumentwerte werden nur einmal beim Definieren der Funktion ausgewertet, nicht jedes Mal, wenn die Funktion aufgerufen wird. Wenn ein Standardwert eine Liste oder ein Dictionary ist, können alle Aufrufe dasselbe Objekt teilen und versehentlich den Zustand preisgeben (state leak). Das sichere Muster ist def f(items=None): und dann innerhalb von items = [] erstellen, wenn items None ist. Es gibt seltene Fälle, in denen ein geteilter Zustand beabsichtigt ist, wie z. B. ein kleiner Cache, aber dann sollte dies explizit und dokumentiert sein, da es Leser überrascht.
def add_tag(tag, tags=[]):
tags.append(tag)
return tags
print(add_tag("python"))
print(add_tag("backend")) # Same list reused
def safe_add_tag(tag, tags=None):
tags = [] if tags is None else tags
tags.append(tag)
return tags
*args sammelt zusätzliche positionelle Argumente in ein Tupel, während **kwargs zusätzliche Schlüsselwortargumente in ein Wörterbuch sammelt. Sie sind nützlich für Wrapper, Decoratoren, flexible APIs und das Weiterleiten von Aufrufen. Am Aufrufort erweitert *iterable die positionellen Werte und **mapping die Schlüsselwerte. Der Kompromiss ist die Klarheit: explizite Parameter sind besser, wenn eine Funktion einen stabilen öffentlichen Vertrag hat, während args und kwargs an Grenzen besser geeignet sind, bei denen Weiterleitung oder Kompatibilität wichtig ist.
Python sucht nach Namen in den lokalen (Local), umschließenden (Enclosing), globalen (Global) und eingebauten (Builtins) Gültigkeitsbereichen. Das Lesen eines Namens folgt dieser Reihenfolge. Die Zuweisung eines Namens innerhalb einer Funktion erstellt normalerweise eine lokale Bindung, weshalb der Code einen UnboundLocalError auslösen kann, wenn er einen Namen liest, bevor er lokal zugewiesen wurde. global leitet die Zuweisung an den Modulbereich weiter, und nonlocal leitet die Zuweisung an den umschließenden Funktionsbereich weiter. In gutem Produktionscode sollten beide selten verwendet werden, da sie den Zustandfluss weniger offensichtlich machen.
total = 0
def add(value):
global total
total += value
def counter():
count = 0
def increment():
nonlocal count
count += 1
return count
return increment
Ein Closure ist eine Funktion, die weiterhin auf Variablen aus einem umschließenden Scope zugreifen kann, nachdem diese äußere Funktion zurückgekehrt ist. Dies ist nützlich für Decoratoren, Callback-Fabriken und konfigurierte Funktionen. Late Binding bedeutet, dass die freie Variable aufgelöst wird, wenn die innere Funktion aufgerufen wird, nicht wenn sie erstellt wird. Lambdas, die in einer Schleife erstellt werden, können alle den endgültigen Schleifenwert sehen. Fangen Sie den aktuellen Wert mit einem Standardargument ein, zum Beispiel `lambda i=i: i`, oder verwenden Sie eine kleine Fabrikfunktion.
callbacks = []
for i in range(3):
callbacks.append(lambda: i)
print([callback() for callback in callbacks]) # [2, 2, 2]
callbacks = []
for i in range(3):
callbacks.append(lambda i=i: i)
print([callback() for callback in callbacks]) # [0, 1, 2]
11Wie unterscheiden sich Klassenvariablen von Instanzvariablen?
Eine Klassenvariable wird auf dem Klassenobjekt gespeichert und von den Instanzen geteilt, es sei denn, eine Instanz überschreibt sie mit ihrem eigenen Attribut. Eine Instanzvariable wird auf einem bestimmten Objekt gespeichert, normalerweise als self.name in __init__ zugewiesen. Unveränderliche Klassenkonstanten sind meist unbedenklich, aber veränderbare Klassenattribute sind ein häufiger Fehler, wenn sie versehentlich als instanzspezifischer Zustand verwendet werden. In einem Vorstellungsgespräch erwähnen Sie die Nachschlagereihenfolge: Python prüft zuerst die Instanz, dann die Klasse und deren Vorfahren.
class Cart:
items = []
first = Cart()
second = Cart()
first.items.append("book")
print(second.items) # ["book"]
class SafeCart:
def __init__(self):
self.items = []
Python löst Attribute über die Method Resolution Order (MRO) auf, welche mit ClassName.__mro__ sichtbar ist. Bei einfacher Vererbung ist dies unkompliziert, aber bei multipler Vererbung verwendet Python C3-Linearisierung, um eine konsistente Reihenfolge zu erstellen. super() bedeutet nicht genau 'rufe meinen Elternteil auf'; es bedeutet 'rufe die nächste Implementierung in der MRO auf'. Das macht kooperative multiple Vererbung möglich, wenn Methoden kompatible Argumente akzeptieren und jede Klasse super() korrekt aufruft.
class A:
def save(self):
print("A")
class B(A):
def save(self):
print("B")
super().save()
class C(A):
def save(self):
print("C")
super().save()
class D(B, C):
pass
D().save() # B, C, A
13Was ist der Unterschied zwischen iterierbar (iterable), Iterator und Generator?
Ein iterierbares Objekt (iterable) ist jedes Objekt, das einen Iterator mittels iter() zurückgeben kann, wie zum Beispiel eine Liste oder ein Datei-Objekt. Ein Iterator ist das zustandsbehaftete Objekt, das __next__ implementiert und bei Erschöpfung StopIteration auslöst. Ein Generator ist eine bequeme Art, einen Iterator mit yield oder einem Generatorausdruck zu erstellen. Generatoren sind speichereffizient, da sie Werte träge (lazy) erzeugen, aber sie sind normalerweise einmalig und können nicht wie eine Liste indiziert werden.
def read_lines(path):
with open(path, encoding="utf-8") as file:
for line in file:
yield line.strip()
14Was ist ein Kontextmanager und warum ist with nützlich?
Ein Kontextmanager definiert Verhaltensweisen beim Betreten und Verlassen mit __enter__ und __exit__ oder kann mit contextlib.contextmanager erstellt werden. Die Anweisung with macht die Ressourcenverwaltung explizit und zuverlässig: Dateien werden geschlossen, Sperren werden freigegeben und Transaktionen können committed oder zurückgerollt werden, selbst wenn Ausnahmen auftreten. __exit__ erhält Informationen über Ausnahmen und kann die Ausnahme durch das Zurückgeben von true unterdrücken, obwohl die Unterdrückung von Fehlern mit Vorsicht zu genießen ist.
from contextlib import contextmanager
with open("data.txt", encoding="utf-8") as file:
data = file.read()
@contextmanager
def temporary_setting():
print("enter")
try:
yield
finally:
print("exit")
15Wie unterscheiden sich Shallow Copy und Deep Copy?
Ein Shallow Copy erstellt einen neuen äußeren Container, behält aber Verweise auf dieselben verschachtelten Objekte. Ein Deep Copy kopiert verschachtelte Objekte rekursiv, wann immer dies möglich ist. Für einfache flache Listen reicht oft ein Shallow Copy aus. Bei verschachtelten veränderlichen Strukturen kann der Shallow Copy dennoch den Zustand teilen und Sie überraschen. Der Deep Copy ist teurer und kann bei Ressourcen wie geöffneten Dateien, Sockets, Datenbank-Sitzungen oder Objekten mit identitätsempfindlichem Verhalten einen TypeError oder PicklingError verursachen.
from copy import copy, deepcopy
original = [[1], [2]]
shallow = copy(original)
deep = deepcopy(original)
original[0].append(99)
print(shallow) # [[1, 99], [2]]
print(deep) # [[1], [2]]
16Was ist der GIL und wie wirkt er sich auf Backend-Code aus?
In standard CPython erlaubt das Global Interpreter Lock (GIL) nur einem Thread, Python-Bytecode gleichzeitig auszuführen. Das bedeutet, dass Threads CPU-gebundenen Python-Code normalerweise nicht beschleunigen. Threads sind immer noch nützlich für I/O-gebundene Aufgaben, da der GIL während des Wartens auf viele blockierende Operationen freigegeben wird. Für sehr rechenintensive Aufgaben sollten Sie multiprocessing, native Erweiterungen, vektorisierte native Bibliotheken oder separate Dienste verwenden. Für Interviews im Jahr 2026 erwähnen Sie auch die free-threaded CPython Builds ab Python 3.13+: Diese machen den GIL für kompatiblen Code optional, erfordern aber dennoch Abhängigkeits- und Leistungstests.
17Wann sollten Sie Threads, Prozesse oder asyncio wählen?
Verwenden Sie Threads, wenn Sie blockierende I/O oder Bibliotheken haben, die nur synchrone APIs bereitstellen. Verwenden Sie Prozesse für CPU-gebundene Aufgaben, Isolation oder Absturzbehandlung, wobei Sie eine Serialisierungs- und Speicherüberlastung in Kauf nehmen. Verwenden Sie asyncio, wenn Sie viele gleichzeitige I/O-Operationen und async-kompatible Bibliotheken haben, wie z. B. asynchrone Datenbanktreiber oder HTTP-Clients. Die praktische Wahl hängt auch von Ihrem Framework und Ihren Abhängigkeiten ab: Das Mischen von Async-Code mit blockierenden Aufrufen kann die Leistung verschlechtern.
18Wofür ist async/await in Python Webdiensten gut?
async und await ermöglichen kooperative Nebenläufigkeit. Eine Coroutine gibt die Kontrolle an die Event Loop zurück, wenn sie auf I/O wartet, wodurch andere Aufgaben ausgeführt werden können. Dies ist wertvoll für APIs, die auf Datenbanken, HTTP-Aufrufe, Warteschlangen oder Sockets warten. Es macht CPU-gebundenen Code nicht magisch schneller; arbeitsintensive Arbeit blockiert die Event Loop, es sei denn, sie wird in einen Worker, Prozess oder nativen Code verschoben. In FastAPI wird ein normaler `def` Endpoint in einem Thread Pool ausgeführt, während blockierende Aufrufe innerhalb von `async def` die Event Loop blockieren können; async hilft nur, wenn der Aufrufpfad nicht-blockierenden async I/O verwendet.
19Wie sollten Ausnahmen in Python für die Produktion behandelt werden?
Fangen Sie die spezifischste Ausnahme ab, die Sie behandeln können, und fangen Sie sie auf einer Ebene ab, von der aus wiederhergestellt, Kontext hinzugefügt oder sie in einen Domänenfehler übersetzt werden kann. Vermeiden Sie `bare except` und vermeiden Sie es, Fehler stillschweigend zu ignorieren, da dies Datenverlust und betriebliche Probleme verschleiert. Verwenden Sie `raise`, um dieselbe Ausnahme erneut auszuwerfen, und verwenden Sie `raise NewError(...)` von `exc`, um den kausalen Kontext zu bewahren. Guter Produktionscode protokolliert auch an Grenzen, gibt angemessene HTTP-Fehler zurück und hält Ausnahmemeldungen nützlich, ohne Geheimnisse preiszugeben.
try:
result = parse_payload(payload)
except ValueError as exc:
raise InvalidRequest("Invalid payload") from exc
20Wie würden Sie einen produktiven Python-Dienst strukturieren und testen?
Trennen Sie die Domänenlogik von Framework- und Infrastrukturcode, damit das Kernverhalten ohne Server oder Datenbank getestet werden kann. Verwenden Sie Konfiguration aus der Umgebung oder vordefinierte Einstellungen (typed settings), Protokollierung anstelle von print, Dependency Injection an den Grenzen und explizite Timeouts für Netzwerkaufrufe. Für Tests kombinieren Sie Unit-Tests für reine Logik, Integrationstests für Datenbanken oder externe Grenzen und einige End-to-End-Prüfungen für kritische Abläufe. Mocken Sie externe Systeme an klaren Grenzen, aber testen Sie das beobachtbare Verhalten und nicht die privaten Implementierungsdetails.