R

ReflexKR

Reflex-Dokumentation

Der Etiketten-Flughafen: Pythons Objektmodell

·

Kursüberblick

1

Das Etikett ist nicht der Koffer: Namen und Objekte

2

Versiegelte Koffer: veränderlich und unveränderlich

3

Koffer im Koffer: Container und Kopien

4

Wenn ein Koffer übergeben wird: Funktionen und Argumente

5

Zonen und Ausweise: Gültigkeitsbereiche

6

Handgepäck: Closures

7

Der Koffer-Bauplan: Klassen und Instanzen

8

Flughafen-Standardverfahren: Dunder-Methoden

9

Der Bauplan hinter dem Bauplan: Vererbung und MRO

10

Die Kontrolle: das Geheimnis des Attributzugriffs

11

Herrenlose Koffer: Speicher und GC

12

Schließfachnummern: Hash, Gleichheit und Gesamtbild

Der Etiketten-Flughafen: Pythons Objektmodell
Lektion 4

Zeit 2:30–3:20

Wenn ein Koffer übergeben wird: Funktionen und Argumente

Verlustmeldung

LOST-004

Bei jedem Aufruf kommen die alten Bestellungen mit

Gemeldet von

Jin, Entwicklung bei einem Start-up für Liefer-Apps

Was fehlt

Eine neue Bestellliste. Bei jedem Aufruf der Funktion, die die Bestellliste anlegt, kommen die Bestellungen früherer Kundschaft gleich mit.

Erledigt, wenn

Herausfinden, wo die alten Bestellungen aufbewahrt wurden, und dafür sorgen, dass jeder Aufruf mit einer leeren Liste beginnt.

Internationaler Flughafen Haneulseom · Gepäckzentrum

Mittagspause. Jin aus der Start-up-Entwicklung stürmt mit dem Laptop herein. Der erste Gast hat nur einen Kaffee bestellt, aber auf dem Beleg des zweiten Gastes stehen Kaffee und Bagel zusammen.

Schichtleiterin Do Miran tippt mit dem Finger auf eine Zeile der Funktionsdefinition. „Wenn du einen Koffer übergibst, wandert nicht der Koffer, sondern es kommt ein Etikett dazu. Und diesen leeren Koffer hier hat man genau einmal gekauft, als die Funktion angelegt wurde.“

4-1. Eine Funktion hängt ein weiteres Etikett an den Koffer (11 min)

Beim Aufruf einer Funktion wird das übergebene Objekt nicht kopiert. Der Parametername in der Funktion wird als zusätzliches Etikett an dasselbe Objekt gehängt. Man nennt das Übergabe per Objektreferenz (call by object reference, call by sharing).

mine = ["laptop"]
def show(bag):
    print(bag is mine)
show(mine)
# True

Es ist nur ein Etikett hinzugekommen, also gelten die zwei Handgriffe aus Lektion 1 ganz genauso:

  • Koffer verändern: Ändert die Funktion den Inhalt, etwa mit bag.append(...), sieht man das auch über den Namen draußen. Es ist ja derselbe Koffer.
  • Etikett umhängen: Bindet die Funktion mit bag = ... neu, wandert nur das Etikett innerhalb der Funktion. Der Name draußen hängt weiter am ursprünglichen Koffer.
def pack(bag):
    bag.append("charger")

def swap(bag):
    bag = ["new"]
    bag.append("charger")

mine = ["laptop"]
pack(mine)
print(mine)
# ['laptop', 'charger']
swap(mine)
print(mine)
# ['laptop', 'charger']

Auf der Etikettentafel unten taucht zum ersten Mal ein Aufrufrahmen (Frame) auf. Beim Aufruf entsteht links ein Rahmen pack, und das Etikett bag darin zeigt auf denselben Koffer wie mine draußen. Endet die Funktion, verschwindet der Rahmen.

Etikettentafel

Schritt 1 / 0

Als Nächstes läuft: 0

Labor wird vorbereitet…

Gerade geändert

Versiegelter Koffer (unveränderlich)

Bei bag.append zeigen bag im Rahmen pack und mine in module auf denselben Koffer, und dieser Koffer ändert sich. Bei bag = bag + [...] wandert nur bag zu einem neuen Koffer, und wenn der Rahmen verschwindet, wird dieser neue Koffer mit entsorgt.

Hier begegnet uns das += aus Lektion 2 wieder. Je nach Typ ist dasselbe += draußen sichtbar oder nicht.

def bump(n):
    n += 1          # int: neues Objekt, nur das Etikett in der Funktion wandert

def extend(lst):
    lst += [1]      # list: __iadd__, derselbe Koffer wird verändert

count, items = 0, []
bump(count)
extend(items)
print(count, items)
# 0 [1]

In Python gibt es keine „Argumente, die eine Variable draußen ändern“. Eine Funktion wirkt nur auf zwei Wegen nach außen: Sie verändert ein übergebenes veränderliches Objekt, oder sie gibt einen Wert zurück.

Pin sagt

→ Zerbrich dir nicht den Kopf über „Wertübergabe oder Referenzübergabe“. Python macht immer dasselbe: Es hängt das Parameter-Etikett zusätzlich an das übergebene Objekt. Ob man draußen etwas sieht, hängt nur davon ab, ob die Funktion das Objekt verändert oder bloß das Etikett umhängt.

4-2. Standardargumente entstehen einmal, bei der Definition (11 min)

Auf das Wesentliche gekürzt, sieht die Funktion aus der Meldung so aus:

def f(x=[]):
    x.append(1)
    return x

print(f())
# [1]
print(f())
# [1, 1]
print(f.__defaults__)
# ([1, 1],)

Der Standardwert [] wird nicht beim Aufruf berechnet, sondern einmal, wenn die def-Anweisung läuft. Die so entstandene Liste hängt im Tupel __defaults__ des Funktionsobjekts und bleibt erhalten, solange die Funktion lebt. Bei jedem Aufruf ohne dieses Argument wird der Parameter x an genau diese eine Liste gehängt.

print(f() is f.__defaults__[0])
# True

Verfolge auf der Etikettentafel unten die zwei Aufrufe. Die Aufrufrahmen entstehen und verschwinden wieder, aber die Liste in defaults bleibt, und die Bestellungen stapeln sich.

Etikettentafel

Schritt 1 / 0

Als Nächstes läuft: 0

Labor wird vorbereitet…

Gerade geändert

Versiegelter Koffer (unveränderlich)

Bei orders=[] hält das defaults-Tupel des function-Koffers eine Liste fest, und beide Aufrufe legen in dieselbe Liste. Bei orders=None steht in defaults nur None, und jeder Aufruf erzeugt eine neue Liste.

Die Lösung ist Standard: Als Vorgabe steht None, und in der Funktion wird jedes Mal eine neue Liste angelegt.

def add_order(item, orders=None):
    if orders is None:
        orders = []
    orders.append(item)
    return orders

print(add_order("coffee"), add_order("bagel"))
# ['coffee'] ['bagel']
print(add_order.__defaults__)
# (None,)

Ist None selbst ein sinnvoller Wert und muss von „kein Argument übergeben“ unterschieden werden, legt man ein Objekt an, das sonst niemand benutzt, und verwendet es als Sentinel. Es gibt dieses Objekt nur einmal, also vergleicht man mit is.

_MISSING = object()

def lookup(key, default=_MISSING):
    if default is _MISSING:
        return f"no default for {key}"
    return default

print(lookup("gate"), lookup("gate", None))
# no default for gate None
  • Gefährlich sind nicht nur [], {} und set() als Standardwerte. Die Regel „wird einmal bei def berechnet“ gilt für jeden Standardwert.
  • Die Standardwerte von Nur-Schlüsselwort-Argumenten hängen separat in f.__kwdefaults__.

4-3. Funktionen, die verändern, und Funktionen, die Neues zurückgeben (7 min)

Es hilft, Funktionen in zwei Gruppen einzuteilen: solche, die den übergebenen Koffer an Ort und Stelle verändern, und solche, die das Original in Ruhe lassen und einen neuen Koffer zurückgeben.

nums = [3, 1, 2]
print(nums.sort(), nums)
# None [1, 2, 3]

nums = [3, 1, 2]
out = sorted(nums)
print(out, nums, out is nums)
# [1, 2, 3] [3, 1, 2] False

In Python gilt eine Konvention: Funktionen und Methoden, die an Ort und Stelle verändern, geben None zurück. Das trifft auf list.sort(), list.append(), list.reverse(), dict.update() und random.shuffle() zu. Diese Abmachung soll nicht verschleiern, dass das Original geändert wurde.

Daraus entsteht dieser typische Fehler:

nums = [3, 1, 2]
nums = nums.sort()
print(nums)
# None
Verändert an Ort und Stelle (gibt None zurück)Gibt ein neues Objekt zurück
lst.sort()sorted(lst)
lst.reverse()lst[::-1], list(reversed(lst))
lst.append(x), lst.extend(xs)lst + [x]
d.update(other)d | other

Entscheide dich auch bei eigenen Funktionen für eine der beiden Arten. Eine Funktion, die verändert und zusätzlich etwas zurückgibt, verleitet den Aufrufer leicht dazu, die Änderung am Original zu übersehen.

4-4. *args, **kwargs und die defensive Kopie (7 min)

*args sammelt übrige Positionsargumente in einem neuen Tupel, **kwargs übrige Schlüsselwortargumente in einem neuen dict. Was darin steckt, sind aber die Etiketten der übergebenen Objekte.

def g(*args, **kwargs):
    return args, kwargs

items = ["a"]
opts = {"tag": items}
a, k = g(items, **opts)
print(type(a), type(k))
# <class 'tuple'> <class 'dict'>
print(a[0] is items, k is opts, k["tag"] is items)
# True False True

Auch wenn du mit **opts entpackt übergibst, bekommt die Funktion in kwargs ein neues dict. Fügt die Funktion kwargs einen Schlüssel hinzu, bleibt opts deshalb unverändert. Die Liste, die als Wert darin steckt, ist aber dasselbe Objekt.

Etikettentafel

Schritt 1 / 0

Als Nächstes läuft: 0

Labor wird vorbereitet…

Gerade geändert

Versiegelter Koffer (unveränderlich)

Im Rahmen pack ist items ein neues Tupel und labels ein neues dict. Das erste Fach des Tupels zeigt auf denselben Koffer wie bag draußen, deshalb ist das append aus Zeile 2 auch draußen zu sehen.

Besonders vorsichtig sein muss eine Funktion, die ein übergebenes veränderliches Objekt länger aufbewahrt. Ändert der Aufrufer das Objekt später, ändert sich das Aufbewahrte mit.

def make_flight(code, passengers):
    return {"code": code, "passengers": passengers}

names = ["Ari", "Bo"]
f1 = make_flight("HS1", names)
names.append("Cy")
print(f1["passengers"])
# ['Ari', 'Bo', 'Cy']

In solchen Fällen legt die Funktion beim Entgegennehmen gleich ihre eigene Version an. Das nennt man defensive Kopie (defensive copy).

def make_flight(code, passengers):
    return {"code": code, "passengers": list(passengers)}

names = ["Ari", "Bo"]
f1 = make_flight("HS1", names)
names.append("Cy")
print(f1["passengers"])
# ['Ari', 'Bo']

list(passengers) ist eine flache Kopie (Lektion 3). Weil darin nur Strings stecken, reicht das. Eine Funktion, die das Übergebene nur kurz liest, muss dagegen gar nichts kopieren.

4-5. Auch der Rückgabewert hängt nur ein Etikett um (4 min)

Auch return kopiert kein Objekt. Der Koffer, der in der Funktion entstanden ist, wird an den Aufrufer weitergereicht, und dessen Name hängt sein Etikett daran. Selbst wenn der Rahmen der Funktion verschwindet, bleibt der Koffer erhalten, weil draußen ein Etikett an ihm hängt.

def build():
    bag = ["boarding pass"]
    return bag

b = build()
print(b)
# ['boarding pass']

return a, b sieht aus, als würden mehrere Werte zurückgegeben, liefert aber ein einziges Tupel. Entpackt der Aufrufer es, bekommt jedes Element sein eigenes Etikett.

def stats(xs):
    return min(xs), max(xs)

res = stats([4, 9, 2])
print(res, type(res))
# (2, 9) <class 'tuple'>
lo, hi = stats([4, 9, 2])
print(lo, hi)
# 2 9

Eine Funktion ohne return gibt None zurück. Auch deshalb geben die „verändernden Funktionen“ aus 4-3 None zurück.

Fall abgeschlossen (10 min)

Hier die Funktion aus der Meldung und die korrigierte Fassung:

def new_order(item, cart=[]):
    cart.append(item)
    return cart

print(new_order("coffee"))
# ['coffee']
print(new_order("bagel"))
# ['coffee', 'bagel']
print(new_order.__defaults__)
# (['coffee', 'bagel'],)

def new_order(item, cart=None):
    if cart is None:
        cart = []
    cart.append(item)
    return cart

print(new_order("coffee"), new_order("bagel"))
# ['coffee'] ['bagel']

Die alten Bestellungen lagen in der Liste, die im __defaults__ des Funktionsobjekts hing. Der Server definiert die Funktion einmal und ruft sie dann immer wieder auf, also hat diese Liste bis zum Herunterfahren des Servers die Bestellungen aller Kundschaft gesammelt.

Als Jin gegangen ist, ergänzt die Schichtleiterin: „Die Etiketten fallen ab, wenn die Funktion endet. Aber ein Koffer, den das Funktionsobjekt festhält, fällt nicht ab. Auch eine Funktion ist ein Koffer.“

Abschlussbericht — LOST-004. Das Standardargument cart=[] wurde bei def einmal erzeugt und blieb in __defaults__. Alle Aufrufe teilten sich dieselbe Liste. Gelöst mit dem Sentinel None, sodass jeder Aufruf eine neue Liste erzeugt.

Übungen

  1. def add(x, seen=set()) ruft seen.add(x) auf und gibt len(seen) zurück. Sag die Ergebnisse von add(1), add(2) und add(1) voraus und sieh dir add.__defaults__ an.
  2. Wie wirken def clear(lst): lst = [] und def clear(lst): lst.clear() jeweils auf die Liste draußen?
  3. Schreib auf zwei Arten (sorted sowie Kopieren und dann sort) eine Funktion, die eine Liste entgegennimmt und eine neue, sortierte Liste zurückgibt, ohne das Original anzurühren.

Das Notizbuch der Schichtleiterin

  • Argumente werden per Objektreferenz übergeben. Ein Parameter ist ein weiteres Etikett am selben Objekt.

  • Verändert die Funktion das Objekt, sieht man es draußen. Bindet sie neu, sieht man nichts.

  • Standardargumente werden bei def einmal berechnet und bleiben in __defaults__. Statt veränderlicher Standardwerte: None.

  • Funktionen, die an Ort und Stelle verändern, geben None zurück (sort gegenüber sorted).

  • *args und **kwargs sind ein neues Tupel und ein neues dict, die Objekte darin bleiben dieselben. Was lange aufbewahrt wird, defensiv kopieren.

  • Auch return hängt nur ein Etikett um. Mehrere Rückgabewerte sind ein einziges Tupel.

← Zurück

Lektion 3 · Koffer im Koffer: Container und Kopien

Weiter →

Lektion 5 · Zonen und Ausweise: Gültigkeitsbereiche

Über neue Lektionen informiert werden

Wir schicken dir eine E-Mail, wenn dieser Kurs neue Lektionen bekommt oder ein Folgekurs erscheint.

Erhoben: deine E-Mail-Adresse · Zweck: Hinweise auf neue Lektionen oder den Start dieses Kurses · Speicherdauer: bis du die Löschung verlangst. Keine andere Nutzung, keine Weitergabe. Antworte einfach auf eine Benachrichtigung, dann löschen wir sie sofort.

Fragen & Antworten

Frag alles zu dieser Lektion. Fragen aus allen Sprachversionen dieser Seite erscheinen hier gemeinsam.

Wird als reiner Text angezeigt, ohne Links oder Formatierung.

R

ReflexKR

Python und Reflex zum Anfassen. Alle Beispiele wurden mit Reflex 0.10.0 ausgeführt und geprüft.

ReflexKR ist kostenlos und ohne Werbebanner. Wenn es dir geholfen hat, kannst du es mit einem Kaffee unterstützen. ☕ Spendier uns einen Kaffee

Hinweis zu Affiliate-Links
한국어English日本語简体中文EspañolDeutsch

© 2026 ReflexKR · Since 2026 · CC BY-NC 4.0

Built with Reflex