Python macht Sachen, die ich nicht nachvollziehen kann

Wenn du dir nicht sicher bist, in welchem der anderen Foren du die Frage stellen sollst, dann bist du hier im Forum für allgemeine Fragen sicher richtig.
Antworten
schaeggi
User
Beiträge: 3
Registriert: Sonntag 13. Januar 2019, 11:37

Hallo. Ich versuche mich gerade an Python zu gewöhnen, aber ich habe da so gewisse Punkte, die mich etwas verwirrt zurück lassen. Wäre super, wenn mir da jemand ein paar Worte dazu sagen könnte. Auf meinem System ist Python 3.6.7 installiert.

Das folgende Skript ...

Code: Alles auswählen

myList = ["A", "B", "C"]

def did_something(someotherthing):
    someotherthing.insert(2, "X")
    someotherthing = ""

did_something(myList)
print(myList)
did_something(myList)
print(myList)
liefert mir die Ausgabe:

Code: Alles auswählen

['A', 'B', 'X', 'C']
['A', 'B', 'X', 'X', 'C']
Wieso ändert sich hier myList, wo ich es doch gar nicht direkt anfasse? Beziehungsweise, wieso ändert sich mit someotherthing = "" dann nichts mehr? Ist dieses Verhalten so gewollt?
Tut mir Leid, wenn das gängiges Wissen oder eine oft gestellte Frage sein sollte, aber ich konnte irgendwie nichts finden, dass mir dieses Verhalten erklären kann.

Viele Grüße und Danke schonmal!
__deets__
User
Beiträge: 14539
Registriert: Mittwoch 14. Oktober 2015, 14:29

Von welcher Sprache kommst du denn, wenn du das NICHT erwartet hast?

Was du da siehst ist das Python

- Parameter “by reference” übergibt. Das heißt, das myList und someotherthing nur Namen sind, die in beiden fällen auf dasselbe Objekt zeigen.
- Listen veränderlich (“mutable” im englischen, und unter dem Begriff viel diskutiert) sind.

Wenn du also in did_something eine Liste verändern willst, ohne seiteneffekt, muss es eine neue Liste bekommen. Entweder selbst anfertigen, oder übergeben bekommen.
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

In Python sind alles Objekte und Variablennamen nur verweise auf diese Objekte. Du kannst Dir das als eine Menge Schuhkartons vorstellen, auf die Du Zettel mit Namen klebst, um sie zu identifizieren. Auf dem gleichen Schuhkarton kannst Du mehrere Namen kleben, Du kannst aber auch einen Namen abmachen und woanders hinkleben.
Wenn Du also `someotherthing` an einen leeren String klebst, dann hat das keine Auswirkung auf den Karton mit der Liste. Und da am Anfang someotherthing auf den gleichen Karton klebt wie myLListe, kannst Du in diesen auch ein 'X' packen, das genauso über myListe gefunden werden kann.
schaeggi
User
Beiträge: 3
Registriert: Sonntag 13. Januar 2019, 11:37

Danke für die schnellen Antworten.
Ich verstehe nur nicht, weshalb ich die original Liste mit einem insert ändere, wenn ich someotherthing einen neuen Wert bzw. Typ zuweise aber nicht. Das ist dann ja schon ein etwas eigenartiges Verhalten. Aber Ok, muss man wohl akzeptieren. Auch wenn ich jetzt eine neue Variable erstelle und dieser dann someotherthing zuweise, bleibt das ja wieder nur eine Referenz. Wie kann ich schnell und unkompliziert eine Kopie mit den Werten von myList zum bearbeiten erstellen? Ist das nicht ein gewisses Sicherheitsproblem? Konstanten gibt es ja auch keine, womit ich mich gegen "ungewollte" Änderungen schützen könnte.
__deets__
User
Beiträge: 14539
Registriert: Mittwoch 14. Oktober 2015, 14:29

Namen zeigen in Python auf Dinge. Nur weil ein Name jetzt auf ein anderes Ding zeigt, ändern sich die anderen Namen nicht - sie zeigen immer noch auf ihr Ding. Und nochmal: wo ist das anders? In Java zB auch nicht. In C oder C++ mit Pointern auch nicht. Und C++ Referenzen kann man nicht zuweisen.

Eine Kopie ist zB mit einer listcomprehension schnell gemacht. Und das ist ein übliches Vorgehen: keine Veränderung bestehender Datenstrukturen, sondern eine Kopie erzeugen.
Benutzeravatar
snafu
User
Beiträge: 6740
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

In Python findet man eher selten Funktionen, die eine bestehende Datenstruktur ändern. Vor allem nicht, wenn das bloße Ändern deren einziger Zweck ist. Das erinnert dann eher an Funktionen vom Rückgabetyp void in C, die Veränderungen an einem struct vornehmen.

Wenn ich beispielsweise eine Liste mit Namen habe und alle Namen sollen großgeschrieben werden, dann geht das in Python so:

Code: Alles auswählen

def get_upper(names):
    upper_names = []
    for name in names:
        upper_names.append(name.upper())
    return upper_names
Also die Erzeugung einer neuen Liste und deren Rückgabe. Die ursprüngliche Liste bleibt dabei in ihrer Originalform erhalten.

Zur Vereinfachung gibt es in Python das Konzept der sogenannten List Comprehensions. Das sieht erstmal ein bißchen komplizierter aus, vereinfacht aber vieles. Man sollte sich das als Anfänger IMHO auch schnell aneignen. Die Funktion sähe dann so aus:

Code: Alles auswählen

def get_upper(names):
    return [name.upper() for name in names]
Die Struktur ist grob gesagt: Tu was mit X für alle X in Y.

Und falls diese Funktionen dann nur noch aus diesem Einzeiler besteht, dann kann man es natürlich auch direkt hinschreiben:

Code: Alles auswählen

upper_names = [name.upper() for name in names]
Und das ist halt das Schöne an Python, dass solche Dinge echt super-kompakt programmierbar sind...
Benutzeravatar
__blackjack__
User
Beiträge: 13102
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@schaeggi: Du siehst das von der falschen Seite. Du weist nicht `someotherthing` einen neuen Wert zu sondern Du gibst einem neuen Wert den Namen `someotherthing`. Das hat keinen Einfluss auf den Wert an dem vorher dieses Namensschild klebte.

Das ist ein gewisses Sicherheitsproblem, andererseits wäre ständig immer alles zu kopieren a) ein Problem was Speicher und Laufzeit angeht, und b) ist es ja auch nützlich das man Objekte verändern kann. Objektorientierte Programmierung basiert ja da drauf das man Objekte mit einem Zustand hat und der sich durch versenden von Nachrichten/aufrufen von Methoden ändern kann.

Und Python geht von Programmierern aus die wissen was sie tun, und nicht einfach in ”fremden” Datenstrukturen Veränderungen vornehmen. Wenn eine Funktion eine Liste übergeben bekommt, dann ist entweder dokumentiert das sie die verändert, ansonsten kann man davon ausgehen das sie es nicht tut. Entsprechend muss man eigene Funktionen/Methoden dann auch schreiben.

Eine (flache) Kopie eines beliebigen iterierbaren Objekts als Liste bekommst Du einfach mit `list()`, also beispielsweise ``someotherthing = list(someotherthing)`` als erste Zeile in der `did_something()`-Funktion. Die Kopie ist wie gesagt flach, das heisst wenn das z.B. eine verschachtelte Liste ist, ist nur die äussere Liste eine Kopie, die Elemente teilt die sich immer noch mit der Liste die übergeben wurde. Eine tiefe Kopie kann man mit `copy.deepcopy()` erstellen. Das ist aber nur ganz selten nötig. Falls man das ständig benötigt, macht man etwas falsch.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
schaeggi
User
Beiträge: 3
Registriert: Sonntag 13. Januar 2019, 11:37

Super, danke für die Antworten. Verstanden, und auch (hoffentlich) gemerkt.
Antworten