Datentypen und Collections (Referenzen, Liste, Dict, Set, Zahlen)
Track Python · PCAP-Fundament · ca. 55 Min.
Worum es geht
In JS/TS denkst du bei const a = [1, 2]; const b = a; automatisch richtig: Arrays und Objekte sind Referenzen. In Python gilt das für alles: Eine Variable ist nur ein Name, der auf ein Objekt zeigt. Dazu kommt die strenge Trennung in veränderbare (mutable) und unveränderbare (immutable) Typen. Daraus entstehen die häufigsten Python-Fehler (geteilte Listen, flache Kopien, Float-Rundung). Nach der Lektion kannst du Aliasing und Kopien vorhersagen, Dicts und Sets gezielt einsetzen und Float-Fallen umgehen.
Ablauf: erst die Übersetzung JS/TS nach Python, dann fünf kleine Schritte mit lauffähigem Code, dann fünf Übungen.
Von JS/TS her gedacht
| Thema | JS/TS | Python |
|---|---|---|
| Zahl | number (immer Float), BigInt extra |
int (beliebig groß) und float getrennt |
| Array | Array |
list (veränderbar), tuple (unveränderbar) |
| Objekt/Map | Object / Map |
dict (Einfügereihenfolge, Schlüssel müssen hashbar sein) |
| Set | Set |
set, dazu Operatoren \|, &, -, ^ |
| Kopie | [...a], structuredClone(a) |
a.copy(), a[:], list(a) (flach), copy.deepcopy(a) (tief) |
| Fehlender Schlüssel | obj.k ergibt undefined |
d["k"] wirft KeyError, d.get("k") ergibt None |
| Leeres Objekt/Set | {} Objekt, new Set() |
{} ist ein leeres dict, leeres Set nur mit set() |
true + true |
2 |
2 (bool ist ein int) |
Dieselbe Idee in beiden Sprachen, der Spread als flache Kopie:
// TypeScript: Spread kopiert nur die oberste Ebene
const a = { name: "x", tags: ["a"] };
const b = { ...a };
b.tags.push("b");
console.log(a.tags); // ["a", "b"]Auch hier steht in a jetzt ['a', 'b']. Genau wie in JS.
Konzept in kleinen Schritten
Schritt 1: Namen zeigen auf Objekte (Referenz, Aliasing)
b = a kopiert nichts. Beide Namen zeigen auf dasselbe Objekt (Referenz, reference). Mit is prüfst du Identität, mit == Gleichheit des Inhalts.
Die bekannte Alias-Falle (alias trap): [[0] * 3] * 3 baut drei Verweise auf dieselbe innere Liste.
Schritt 2: veränderbar oder nicht (mutable vs immutable)
Unveränderbar sind int, float, bool, str, tuple, bytes, frozenset, None. Veränderbar sind list, dict, set, bytearray. Aber Achtung: Ein Tupel ist nur flach unveränderbar. Es enthält Verweise, und diese Verweise können auf veränderbare Objekte zeigen.
Daraus folgt auch hashbar (hashable): Dict-Schlüssel und Set-Elemente müssen unveränderbar sein. int, str, tuple gehen, list, dict nicht.
Beachte: (1) ist nur die Zahl 1. Ein Tupel mit einem Element braucht das Komma: (1,).
Schritt 3: Kopien (flach und tief)
- Flache Kopie (shallow copy):
a.copy(),a[:],list(a),copy.copy(a). Die äußere Liste ist neu, die inneren Objekte werden weiter geteilt. - Tiefe Kopie (deep copy):
copy.deepcopy(a)kopiert verschachtelte Objekte mit.
Schritt 4: Liste, Dict und Set im Einsatz
Liste
l.sort()sortiert direkt und liefertNone.sorted(l)liefert eine neue Liste.- Slicing mit zu großem Ende wirft keinen Fehler, ein einzelner Index außerhalb wirft
IndexError.
Dict
d["k"]wirftKeyErrorbei fehlendem Schlüssel,d.get("k", default)nicht.keys(),values(),items()sind Ansichten (views), die sich mit dem Dict ändern.- Das Dict während des Durchlaufens zu verändern wirft
RuntimeError.
Set: eindeutig, ungeordnet, Mengenoperationen | (Vereinigung), & (Schnitt), - (Differenz), ^ (symmetrische Differenz).
Eine typische Kombination: Ein dict zählt, ein set vergleicht.
Schritt 5: Zahlen und Float-Fallen
intist beliebig groß,floathat die übliche Fließkommagenauigkeit (IEEE 754, wienumberin JS).boolist einint:True + Trueist2./liefert immer einenfloat,//rundet nach unten (auch bei negativen Zahlen).- Schreibweisen:
1_000_000,0b101,0o17,0xFF,1e3.
Die Float-Falle bei Geld: 0.29 * 100 ist nicht 29.
int() schneidet ab, round() rundet. Wer Geldbeträge als Float speichert, sollte in ganzen Cent rechnen oder ein Rundungswerkzeug wie decimal nutzen (steht nicht in der Quelle, bitte prüfen, ob es in der PCAP drankommt).
Falle: die Prüfungs- und Praxisklassiker
b = akopiert nicht. Änderungen überbsiehta.[[0] * 3] * 3: drei Verweise auf dieselbe innere Liste. Richtig:[[0] * 3 for _ in range(3)].- Flache Kopie bei verschachtelten Daten:
copy(),[:],list(),dict()teilen die inneren Objekte. Danncopy.deepcopy. l.sort()liefertNone:l = l.sort()zerstört die Variable.{}ist ein Dict, kein Set. Leeres Set:set().(1)ist kein Tupel,(1,)schon.- Float-Vergleich mit
==undint()stattround()bei berechneten Beträgen. - Dict beim Durchlaufen ändern:
RuntimeError.
Übungen
Übung 1: Aliasing und Kopie vorhersagen
Der Code steht fest, du musst nur das Ergebnis vorhersagen:
a = [1, 2]
b = a
b.append(3)
c = a[:]
c.append(4)
gitter = [[0] * 2] * 2
gitter[0][0] = 7Trage das Tupel (a, c, gitter) ein.
Frage dich bei jeder Zeile: Entsteht hier ein neues Objekt, oder kommt nur ein zweiter Name dazu? Und bei gitter: Wie viele innere Listen gibt es wirklich?
antwort = ([1, 2, 3], [1, 2, 3, 4], [[7, 0], [7, 0]])
antwortÜbung 2: Tupel mit Liste (Multiple Choice)
t = ([1], 2)
t[0] += [5]Was passiert? Gib den Buchstaben als String zurück.
- A:
TypeError, undtbleibt unverändert([1], 2). - B: Kein Fehler,
tist danach([1, 5], 2). - C: Kein Fehler,
tbleibt([1], 2). - D:
TypeError, undtist trotzdem([1, 5], 2).
+= auf einer Liste macht zwei Dinge hintereinander: erst wird die Liste erweitert, dann wird das Ergebnis wieder an die Stelle t[0] zugewiesen. Welcher der beiden Teile ist bei einem Tupel erlaubt, und welcher nicht?
antwort = "D"
antwortÜbung 3: Kopieren statt verändern
Die Funktion mit_port soll eine neue Konfiguration liefern, in der server["port"] auf den neuen Wert gesetzt ist. Das Original darf sich nicht ändern, und die neue Konfiguration darf auch keine inneren Objekte (zum Beispiel die tags-Liste) mit dem Original teilen. Fülle die Lücken.
Die Konfiguration ist verschachtelt: ein Dict mit einem Dict und einer Liste darin. Welche Kopierart erzeugt auch für die inneren Objekte neue Kopien? Schau auf Schritt 3.
import copy
def mit_port(konfig, port):
neu = copy.deepcopy(konfig)
neu["server"]["port"] = port
return neu
mit_portÜbung 4: Dict und Set kombinieren
Schreibe abgleich(bestellt, geliefert). Beide Parameter sind Listen von Artikelnamen (Strings, mit möglichen Doppelten). Die Funktion liefert ein Dict mit drei Einträgen:
"fehlt": sortierte Liste der Artikel, die bestellt, aber nicht geliefert wurden (ohne Doppelte)"zuviel": sortierte Liste der Artikel, die geliefert, aber nicht bestellt wurden (ohne Doppelte)"doppelt": sortierte Liste der Artikel, die ingeliefertmehr als einmal vorkommen (ohne Doppelte)
Für “fehlt” und “zuviel” sind Mengen praktisch, weil sie Doppelte entfernen und eine Differenz kennen. Für “doppelt” brauchst du eine Zählung, wie sie in Schritt 4 ein Dict macht. Achte auf die Richtung der Differenz und darauf, dass Mengen keine Reihenfolge haben.
def abgleich(bestellt, geliefert):
b, g = set(bestellt), set(geliefert)
zaehler = {}
for x in geliefert:
zaehler[x] = zaehler.get(x, 0) + 1
return {
"fehlt": sorted(b - g),
"zuviel": sorted(g - b),
"doppelt": sorted(k for k, n in zaehler.items() if n > 1),
}
abgleichÜbung 5: Geld ohne Float-Fehler
Die Preise kommen als Floats mit zwei Nachkommastellen, zum Beispiel [0.29, 0.57]. Schreibe summe_in_cent(preise), die die Summe als ganze Zahl in Cent (int) liefert. Eine leere Liste ergibt 0.
Schau auf die Ausgabe von 0.29 * 100 in Schritt 5. Welche der beiden Umwandlungen (int oder round) schneidet ab, welche rundet? Und an welcher Stelle der Rechnung musst du sie anwenden: pro Preis oder erst am Ende?
def summe_in_cent(preise):
return sum(round(p * 100) for p in preise)
summe_in_centMerksatz und Prüfstein
Merksatz: Ein Name ist nur ein Verweis, b = a kopiert nichts, eine flache Kopie teilt das Innere, und Float-Werte rechnest du nicht mit == und int(), sondern mit round() oder in ganzen Cent.
Prüfstein (offene Frage): Erkläre in eigenen Worten, warum ein Tupel als Dict-Schlüssel erlaubt ist, ([1], 2) aber nicht, obwohl beides ein Tupel ist. Was hat das mit “flach unveränderbar” und “hashbar” zu tun?
Quelle: quellen/python-glossar-pcap-pcpp1.md, Abschnitte “Datentypen und Collections” mit “Begriffe rund um Referenzen”, “Liste”, “Dict”, “Set” und “Zahlen”. Hinweis: is für Identität, decimal, das Verhalten von t[0] += [5] im Tupel und die Aussage, dass round() rundet, stehen nicht in der Quelle, wurden aber mit Python 3.13 ausgeführt und geprüft (bitte prüfen, ob sie in der PCAP geprüft werden).