Zum Inhalt springen
aviral gupta

// B4.6 · ca. 25 Min. · Einstieg

Referenzen, Kopien und die Falle veränderlicher Standardwerte

Nach dieser Lektion sagen Sie vorher, wann eine Änderung an einer Variablen in einer anderen sichtbar wird, wählen die passende Art von Kopie und schreiben Standardargumente, die keinen Zustand zwischen Aufrufen weitergeben.

Lektion 6 von 6 in B4 Datenstrukturen

Ende des Moduls

Danach können Sie

  • Erklären, warum b = a eine Liste nicht kopiert, und is und == richtig einsetzen
  • Für verschachtelte Daten zwischen Slice, copy.copy() und copy.deepcopy() wählen
  • Ein veränderliches Standardargument durch das None-Muster ersetzen
  1. Aufwärmen · Aufgabe 1 von 7

    Aufwärmen: Welche dieser Objekte lassen sich nach dem Anlegen an Ort und Stelle ändern, zum Beispiel mit v[0] = 9 oder v["k"] = 2? Wählen Sie alle zutreffenden.

    Wählen Sie alle zutreffenden aus.

  2. Vorhersagen · Aufgabe 2 von 7

    Sagen Sie voraus, bevor Sie es ausführen: Was gibt das aus?

    a = [1, 2, 3]
    b = a
    b.append(4)
    print(a)
  3. Üben · Aufgabe 3 von 7

    Ausgangspunkt ist a = [1, [2, 3]]. Ordnen Sie jeder Codezeile zu, was sie Ihnen liefert.

    import copy
    a = [1, [2, 3]]
  4. Üben · Aufgabe 4 von 7

    Was gibt das aus?

    import copy
    
    grid = [[0, 0], [0, 0]]
    shallow = copy.copy(grid)
    deep = copy.deepcopy(grid)
    
    shallow[0][0] = 1
    deep[1][1] = 9
    print(grid)
  5. Üben · Aufgabe 5 von 7

    Diese Funktion hat den Fehler mit dem veränderlichen Standardwert: Nach add_tag("a") liefert add_tag("b") beim zweiten Aufruf ['a', 'b']. Bringen Sie die Zeilen der korrigierten Fassung in die richtige Reihenfolge.

    def add_tag(tag, tags=[]):
        tags.append(tag)
        return tags
    1. 1. tags = []
    2. 2. tags.append(tag)
    3. 3. if tags is None:
    4. 4.def add_tag(tag, tags=None):
    5. 5. return tags
  6. Denksport · Aufgabe 6 von 7

    Knobelaufgabe. Die Zeile mit += löst einen Fehler aus, den der try-Block abfängt. Was gibt die letzte Zeile aus?

    pair = (["foo"], "bar")
    try:
        pair[0] += ["item"]
    except TypeError as e:
        print("TypeError:", e)
    print(pair)
  7. Anwenden · Aufgabe 7 von 7

    Mini-Aufgabe. Dieses Modul hat zwei Fehler mit Referenzen. Wenn Sie es ausführen, erhalten Sie ['reader', 'admin'] und dann ['start', 'stop'] ['start', 'stop']. Korrigieren Sie beide Funktionen, und fügen Sie assert-Zeilen hinzu, die jede Korrektur belegen.

    def add_event(event, log=[]):
        log.append(event)
        return log
    
    def snapshot(state):
        return state.copy()
    
    state = {"user": "ana", "roles": ["reader"]}
    saved = snapshot(state)
    state["roles"].append("admin")
    print(saved["roles"])
    print(add_event("start"), add_event("stop"))

    Prüfen Sie Ihr Ergebnis anhand dieser Liste

Selbst programmieren

Lesen Sie das ausgearbeitete Beispiel und lösen Sie dann die Übungen. Ihr Code läuft in Ihrem Browser oder auf Ihrem Computer und wird nie hochgeladen.

Ausgearbeitetes Beispiel

Eine Woche Einkaufslisten

Ein Dict mit Einkaufslisten, auf drei Arten erreichbar: über einen zweiten Namen, eine flache Kopie und eine tiefe Kopie. Dann ändert sich das Original, und die Ausgabe zeigt, welche der drei was davon sieht. Die Funktion oben nutzt None als Standardwert, jeder Aufruf ohne Korb bekommt also eine eigene Liste. Ändern Sie eine Zeile, und sagen Sie die Ausgabe voraus, bevor Sie es ausführen.

main.py

import copy


def add_item(item: str, basket: list[str] | None = None) -> list[str]:
    if basket is None:
        basket = []  # a new list for every call without a basket
    basket.append(item)
    return basket


week = {"mon": ["bread"], "tue": ["milk"]}
alias = week  # a second name, not a copy
shallow = week.copy()  # new dict, same inner lists
deep = copy.deepcopy(week)  # new dict, new inner lists

week["mon"].append("eggs")
week["wed"] = ["tea"]

print(alias is week, alias == week, shallow is week)
print(shallow)
print(deep)

print(add_item("apple"), add_item("pear"))
mine = ["salt"]
print(add_item("rice", mine) is mine, mine)

Ausführen mit

python main.py

Ausgabe

True True False
{'mon': ['bread', 'eggs'], 'tue': ['milk']}
{'mon': ['bread'], 'tue': ['milk']}
['apple'] ['pear']
True ['salt', 'rice']
  • alias ist dasselbe Dict wie week und sieht deshalb jede Änderung.
  • shallow sieht "eggs", weil seine Montagsliste die Montagsliste von week ist, aber nicht den neuen Schlüssel "wed", weil es ein eigenes Dict ist.
  • deep teilt nichts und zeigt die Woche noch so, wie sie war.
Ändern und ausführen

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser Python herunter (bis zu 6.5 MB) und speichert es im Cache. Ihr Code bleibt auf Ihrem Gerät.

Übungen

Übung 1 von 2

Ein Korb pro Aufruf

add_item(item, basket) legt item in basket und gibt den Korb zurück; ohne Korb aufgerufen, soll die Funktion einen neuen anfangen. Der Starter hat den Fehler mit dem veränderlichen Standardwert: Der zweite Aufruf unten gibt ['apple', 'pear'] aus. Korrigieren Sie die Funktion, sodass jeder Aufruf ohne Korb eine neue Liste bekommt, während ein übergebener Korb, auch ein leerer, erweitert und zurückgegeben wird.

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser Python herunter (bis zu 6.5 MB) und speichert es im Cache. Ihr Code bleibt auf Ihrem Gerät.

Hinweise
  1. Hinweis 1

    Die Standardliste wird einmal angelegt, wenn def läuft. Nehmen Sie stattdessen None als Standardwert.

  2. Hinweis 2

    Legen Sie in der Funktion nur dann eine neue Liste an, wenn basket None ist. if not basket würde auch einen leeren Korb ersetzen.

  3. Hinweis 3

    def add_item(item: str, basket: list[str] | None = None), dann if basket is None: basket = []

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

def add_item(item: str, basket: list[str] | None = None) -> list[str]:
    """Add item to basket and return it. Without a basket, start a new one."""
    if basket is None:
        basket = []
    basket.append(item)
    return basket


print(add_item("apple"))
print(add_item("pear"))
Auf dem eigenen Computer ausführen

Installieren Sie Python 3.14 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.py

def add_item(item: str, basket: list[str] = []) -> list[str]:
    """Add item to basket and return it. Without a basket, start a new one."""
    basket.append(item)
    return basket


print(add_item("apple"))
print(add_item("pear"))

test_main.py

from main import add_item


def test_new_basket_each_call():
    """Zwei Aufrufe ohne Korb bekommen zwei verschiedene Listen"""
    add_item("a")
    got = add_item("b")
    assert got == ["b"], f"der zweite Aufruf lieferte {got!r}, erwartet: ['b']"


def test_uses_given_basket():
    """Ein übergebener Korb wird erweitert und zurückgegeben"""
    mine = ["x"]
    got = add_item("a", mine)
    assert got is mine, "add_item lieferte eine andere Liste, nicht den übergebenen Korb"
    assert mine == ["x", "a"], f"der Korb ist {mine!r}, erwartet: ['x', 'a']"


def test_empty_basket_kept():
    """Ein übergebener leerer Korb wird benutzt, nicht ersetzt"""
    mine: list[str] = []
    got = add_item("a", mine)
    assert got is mine, "ein leerer Korb wurde durch eine neue Liste ersetzt; prüfen Sie mit is None"

Unter macOS und Linux tippen Sie python3, wo in diesen Befehlen python steht, wie in der ersten Lektion.

Programm ausführen:

python main.py

Prüfungen ausführen (learnrun.py muss im selben Ordner liegen):

python learnrun.py test
learnrun.py herunterladen

Übung 2 von 2

Gitter, die nichts teilen

make_grid(rows, cols) soll ein Gitter aus Nullen liefern, dessen Zeilen eigene Listen sind, und copy_grid(grid) eine Kopie, die keine Zeile mit dem Original teilt. Der Starter nutzt * für die Zeilen und copy() für die Kopie, und beide teilen Zeilen: Das Programm unten gibt [[1, 0, 0], [1, 0, 0]] aus. Korrigieren Sie beide Funktionen.

Tab rückt ein, Umschalt+Tab rückt aus. Um den Editor mit der Tastatur zu verlassen, drücken Sie Esc und dann Tab.

Beim ersten Ausführen lädt Ihr Browser Python herunter (bis zu 6.5 MB) und speichert es im Cache. Ihr Code bleibt auf Ihrem Gerät.

Hinweise
  1. Hinweis 1

    [[0] * cols] * rows wiederholt einen Verweis auf eine einzige Zeile. Eine List Comprehension führt [0] * cols für jede Zeile neu aus.

  2. Hinweis 2

    grid.copy() ist flach. Kopieren Sie auch jede Zeile, mit einem Slice oder mit copy.deepcopy().

  3. Hinweis 3

    return [[0] * cols for _ in range(rows)] und return [row[:] for row in grid]

Eine Lösung zeigen

Ein möglicher Lösungsweg. Ihrer kann anders aussehen und trotzdem alle Prüfungen bestehen.

def make_grid(rows: int, cols: int) -> list[list[int]]:
    """Return a rows x cols grid of zeros whose rows are separate lists."""
    return [[0] * cols for _ in range(rows)]


def copy_grid(grid: list[list[int]]) -> list[list[int]]:
    """Return a copy of grid that shares no row with it."""
    return [row[:] for row in grid]


board = make_grid(2, 3)
board[0][0] = 1
print(board)
Auf dem eigenen Computer ausführen

Installieren Sie Python 3.14 oder neuer. Speichern Sie diese Dateien in einem Ordner, öffnen Sie dort ein Terminal und führen Sie die Befehle unten aus.

main.py

def make_grid(rows: int, cols: int) -> list[list[int]]:
    """Return a rows x cols grid of zeros whose rows are separate lists."""
    return [[0] * cols] * rows


def copy_grid(grid: list[list[int]]) -> list[list[int]]:
    """Return a copy of grid that shares no row with it."""
    return grid.copy()


board = make_grid(2, 3)
board[0][0] = 1
print(board)

test_main.py

from main import copy_grid, make_grid


def test_grid_shape():
    """make_grid(2, 3) sind zwei Zeilen mit je drei Nullen"""
    got = make_grid(2, 3)
    assert got == [[0, 0, 0], [0, 0, 0]], f"make_grid(2, 3) lieferte {got!r}"


def test_rows_independent():
    """Eine Zeile eines neuen Gitters zu ändern lässt die anderen Zeilen unverändert"""
    grid = make_grid(3, 2)
    grid[0][0] = 1
    assert grid == [[1, 0], [0, 0], [0, 0]], f"nach grid[0][0] = 1 ist das Gitter {grid!r}; die Zeilen sind eine gemeinsame Liste"


def test_copy_equal():
    """Die Kopie hat dieselben Werte"""
    grid = [[1, 2], [3, 4]]
    got = copy_grid(grid)
    assert got == grid, f"copy_grid lieferte {got!r}, erwartet: [[1, 2], [3, 4]]"


def test_copy_independent():
    """Die Kopie zu ändern, auch innerhalb einer Zeile, lässt das Original unverändert"""
    grid = [[1, 2], [3, 4]]
    new = copy_grid(grid)
    new[0][0] = 9
    assert grid == [[1, 2], [3, 4]], f"das Original wurde zu {grid!r}; die Kopie teilt noch seine Zeilen"

Unter macOS und Linux tippen Sie python3, wo in diesen Befehlen python steht, wie in der ersten Lektion.

Programm ausführen:

python main.py

Prüfungen ausführen (learnrun.py muss im selben Ordner liegen):

python learnrun.py test
learnrun.py herunterladen

Häufige Fehler

Ein „Backup“ mit b = a anlegen

scores = [3, 5]
backup = scores
scores.clear()
print(backup[0])

Was Python ausgibt

IndexError: list index out of range

Warum, und die Lösung

backup = scores ist ein zweiter Name für dieselbe Liste, clear() leert also beide. Machen Sie eine Kopie, bevor Sie das Original ändern: backup = scores.copy().

Eine flache Kopie verschachtelter Daten

settings = {"theme": {"dark": False}}
backup = settings.copy()
settings["theme"]["dark"] = True
assert backup["theme"]["dark"] is False, "the backup changed too"

Was Python ausgibt

AssertionError: the backup changed too

Warum, und die Lösung

settings.copy() macht ein neues äußeres Dict, aber backup["theme"] ist noch dasselbe innere Dict. Für verschachtelte Daten nehmen Sie copy.deepcopy(settings).

Den Standardwert mit if not prüfen

def add_tag(tag, tags=None):
    if not tags:
        tags = []
    tags.append(tag)
    return tags


mine = []
assert add_tag("a", mine) is mine, "the caller's list was replaced"

Was Python ausgibt

AssertionError: the caller's list was replaced

Warum, und die Lösung

Eine leere Liste gilt als falsch, also ersetzt if not tags die Liste, die der Aufrufer übergeben hat. Prüfen Sie auf den Standardwert selbst: if tags is None.

Python im Browser: Pyodide 314.0.7, MPL-2.0. Lizenz und Quellcode

Abschlussquiz

5 Fragen, ohne Hinweise. Ab 80 % ist die Lektion abgeschlossen.

Erledigen Sie zuerst alle Aufgaben oben, um das Abschlussquiz freizuschalten.

Problem melden

Etwas ist falsch oder unklar? Beschreiben Sie es kurz, dann wird es geprüft und korrigiert.

#

Mindestens 20 Zeichen.

Nur, wenn Sie eine Antwort wünschen.

Kernideen

Namen verweisen auf Objekte

Eine Zuweisung kopiert nie. Nach y = x verweisen beide Namen auf dasselbe Objekt. Ist dieses Objekt veränderlich, etwa eine Liste, ein Dict oder ein Set, ist eine Änderung über den einen Namen auch über den anderen sichtbar. Unveränderliche Objekte wie Ganzzahlen, Strings und Tupel können sich nicht ändern; Operationen darauf erzeugen neue Objekte. Der Operator is vergleicht die Identität (dasselbe Objekt), == vergleicht Werte. Nutzen Sie is für None und == für Zahlen und Strings.

Flache und tiefe Kopien

Eine flache Kopie baut einen neuen äußeren Container und füllt ihn mit Verweisen auf dieselben inneren Objekte. a[:], list.copy(), dict.copy() und copy.copy() sind alle flach. copy.deepcopy() baut einen neuen Container und kopiert seinen Inhalt rekursiv, sodass verschachtelte Listen und Dicts unabhängig sind. Tiefe Kopien kosten mehr und können Daten kopieren, die Sie eigentlich teilen wollten. Beachten Sie: [[0] * 2] * 3 ist kein Gitter aus Kopien, sondern enthält drei Verweise auf eine einzige innere Liste.

Standardwerte werden einmal ausgewertet

Standardwerte von Parametern werden einmal ausgewertet, wenn die def-Anweisung läuft, nicht bei jedem Aufruf. Ein veränderlicher Standardwert wie [] oder {} ist deshalb ein einziges gemeinsames Objekt, und was ein Aufruf hinzufügt, ist beim nächsten noch da. Die Lösung, die die Python-Dokumentation empfiehlt: None als Standardwert, und in der Funktion if param is None: param = [], damit jeder Aufruf ein neues Objekt bekommt.

Quellen

Zuletzt geprüft am 29. September 2026