Variale ändert sich mit, obwohl nicht angesprochen

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
rown
User
Beiträge: 7
Registriert: Mittwoch 11. Januar 2012, 01:23

Hallo Leute,

bin recht neu in Python und sehe das Problem nicht. Ich habe zwei Funktionen geschrieben, die mir zum einen den Inhalt einer verschachtelten Liste entsprechend des Pathes ausgibt und zum anderen den Inhalt nach Path durch Value ersetzt. Im Beispiel wird b durch die Funktion b=ChangeListValue(a, [1,1], 999) definiert. Allerdings ändert sich dadurch auch a. Warum?

Code: Alles auswählen

import c4d
#Welcome to the world of Python

# Wenn path bspw. [0,0] ist, dann wird der Inhalt liste[0][0] angesprochen 

def GetListValue(list_, path):
    value=list_
    for i in path:
        value=value[i]
    return value

def ChangeListValue(list_, path, value):
    for i in range(len(path)):
        temp=GetListValue(list_,path[:len(path)-i-1])
        temp[path[len(path)-1-i]]=value
        value=temp
    return temp

def main():
    a = [[1,2],[3,4],6]
    b = ChangeListValue(a, [1,1], 999) 

    print a #output: [[1, 2], [3, 999], 6]
    print b #output: [[1, 2], [3, 999], 6]
Grüße
Rown
deets

Weil du nunmal ein Element der Liste veraenderst, auf die a zeigt, statt eine neue Liste. Python kopiert a nicht automatisch, nur weil du das an eine Funktion uebergeben hast. Dazu gaebe es zB das Modul copy, und darin die Funktion deepcopy.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Ein Objekt wird in Python nicht automatisch kopiert, wenn man es an eine Funktion übergibt oder an einen neuen Namen bindet; es bleibt stets dasselbe Objekt!

Code: Alles auswählen

In [3]: a = list(range(5))

In [4]: a
Out[4]: [0, 1, 2, 3, 4]

In [5]: b = a

In [6]: b.append(42)

In [7]: b
Out[7]: [0, 1, 2, 3, 4, 42]

In [8]: a
Out[8]: [0, 1, 2, 3, 4, 42]

In [9]: a is b
Out[9]: True
Du suchst das `copy`-Modul ;-)

Generell kann man aber einiges zum Code sagen...

- Achte auf PEP8, das verlangt kleine Funktionsnamen und kein CamelCase

- Was glaubst Du, macht Deine `GetListValue`-Funktion? Die kann übrigens nicht funktionieren, wenn Du eine Liste mit mehr als zwei Elementen oder einem Element, welches einen größeren Wert hat, als der höchste Index der Liste `list_`

- Typen sollte man nicht in Namen packen.

Was soll die Funktion `ChangeListValue` eigentlich machen?
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
/me
User
Beiträge: 3555
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

Hyperion hat geschrieben:Du suchst das `copy`-Modul ;-)
Aufgrund der Verschachtelung sogar explizit copy.deepcopy().
Hyperion hat geschrieben:Generell kann man aber einiges zum Code sagen...
Das kann man in der Tat. Ich habe damit gerade experimentiert und der Code ist hochgradig unverständlich und fehlerträchtig.

Warum nicht einfach:

Code: Alles auswählen

a = [[1, 2], [3, 4], 6]
b = copy.deepcopy(a)
b[1][1] = 999
Das Ergebnis entspricht der Ausgabe des bisherigen Codes. Es mag natürlich sein, dass ich in dem Gewusel irgendwelche besonders magischen Dinge übersehen habe.
Benutzeravatar
/me
User
Beiträge: 3555
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

/me hat geschrieben:Es mag natürlich sein, dass ich in dem Gewusel irgendwelche besonders magischen Dinge übersehen habe.
Vielleicht ist die Idee, dass man eine beliebige Position übergeben kann. Dann bekämen wir beispielsweise so etwas:

Code: Alles auswählen

def change_value(list_, position, new_value):
    element = new_list = copy.deepcopy(list_)
    for idx in position[:-1]:
        element = element[idx]
    element[position[-1]] = new_value
    return new_list

data_in = [[1, 2], [3, 4, [5, 6]], 6]
data_out = change_value(data_in, (1, 2, 1), 42)
print(data_out)
data_out = change_value(data_in, (2,), 23)
print(data_out)
Ich bin allerdings mit dem Namen change_value nicht ganz glücklich. Für mich klingt das so, als würde man die bestehende Liste ändern wollen. Entweder lässt man also das deepcopy (und das return) weg oder man sollte einen besseren Namen finden.

Ich habe allerdings das Gefühl, als müsse es da noch eine schönere Lösung für meinen spontan zusammengehackten Code geben. Mag jemand eine andere Idee vorschlagen? Irgendwas mit itemgetter vielleicht?
rown
User
Beiträge: 7
Registriert: Mittwoch 11. Januar 2012, 01:23

Danke für euren schnellen Antwort. Das mit copy.deepcopy habe ich geändert und funktioniert auch.

ich habe mal noch eine Funktion angehängt...

Code: Alles auswählen

import c4d
import copy
#Welcome to the world of Python

def GetListValue(list_, path):
    value=list_
    for i in path:
        value=value[i]
    return value

def ChangeListValue(list_, path, value):
    temp_list=copy.deepcopy(list_)
    for i in range(len(path)):
        temp=GetListValue(temp_list,path[:len(path)-i-1])
        temp[path[len(path)-1-i]]=value
        value=temp
    return temp

def GetListPath(data, path=()):
    for i, sub_data in enumerate(data):
        sub_path = path + (i,)
        if isinstance(sub_data, list):
            for result in GetListPath(sub_data, sub_path): yield result
        else: yield sub_path

def main():
    a = [[1,2],[3,4],6,[[2,4.32,0.1],[2,1]]]
    p = list(GetListPath(a))
    i = 3
    b = GetListValue(a, p[i])
    c = ChangeListValue(a, p[i], 999) 

    print a #output: [[1, 2], [3, 4], 6, [[2, 4.3200000000000003, 0.10000000000000001], [2, 1]]]
    print p #output: [(0, 0), (0, 1), (1, 0), (1, 1), (2,), (3, 0, 0), (3, 0, 1), (3, 0, 2), (3, 1, 0), (3, 1, 1)]
    print b #output: 4
    print c #output: [[1, 2], [3, 999], 6, [[2, 4.3200000000000003, 0.10000000000000001], [2, 1]]]
Im Gesamten möchte ich einfach einen schnellen Zugriff in Listen hinbekommen, bei dem ich vorallem mit Iterationen arbeiten kann. Das ich innerhalb der Funktionen nicht in jedem Fall überprüfen lasse, ob die Eingaben weiterverarbeitet werden können, liegt daran, dass ich das schon vorher im größeren Condex erledigt habe. Durch die Funktion GetListPath() beispielsweise, wird garantiert, dass es den Index einer Liste auch gibt. ChangeListValue() soll einen Wert in der List durch einen anderen ersetzen.
Ich bin mir sicher, dass der Code nicht sauber oder sogar hochgradig fehlerträchtig ist. Allerdings fehlt es mir noch gehörig an Verständnis, um beispielsweise die Verständlichkeit eines Codes beurteilen zu können.

Viele Grüße
Rown
BlackJack

@rown: Ich verstehe wie /me auch nicht was `ChangeListValue()` eigentlich machen soll, beziehungsweise warum die Funktion so kompliziert ist. Wenn ich das richtig sehe, gibt sie eine Kopie der Struktur zurück, bei der *ein* Wert *am Ende* des gegebenen Pfads verändert ist!? Warum tust Du dann nicht genau *das*, nämlich den Wert am Ende des Pfads ändern — ohne in einer Schleife den gesamten Pfad entlang Neuzuweisungen vor zu nehmen, bei denen nur *die erste* tatsächlich einen Effekt hat.

Die Namen der Funktionen sind schlecht gewählt. Das `List` stimmt nicht, denn die beziehen sich alle nicht auf eine Liste, sondern auf eine verschachtelte Datenstruktur. `Tree` wäre zum Beispiel ein passenderer Name.

Da sie alle auf so einer Datenstruktur arbeiten, würde sich vielleicht auch ein eigener Datentyp dafür anbieten.

Das `Change` für die meisten Leser impliziert, dass ein Objekt verändert wird, und nicht das eine Kopie mit einer Änderung zurück gegeben wird, wurde ja schon gesagt. Gründsätzlich würde ich auch sagen, dass es in objektorientierten Programmiersprachen üblicher ist komplexe Datenstrukturen zu verändern, statt veränderte Kopien zu erzeugen. Das ist einfach effizienter. Für die Fälle wo man dann doch mal eine Kopie benötigt, kann man das auch explizit vor dem Verändern tun. Ansonsten gab es irgenwann letztes Jahr hier im Forum mal einen Link auf eine Bibliothek mit Datenstrukturen, die man nicht „in place” verändern kann, die aber laufzeit- und speichereffizienter sind als tatsächlich Kopien zu erzeugen. Ich finde den leider gerade nicht wieder. :-(
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

@BlackJack: Ich habe es auch erst durch /mes Beitrag kapiert. Im Grunde genommen hat er eine Baustruktur, bei dem die Äste "nummeriert" sind. Du navigierst da durch, indem Du eine Sequenz von Nummern angibst: (1, 2, 1) bedeutet, zweige unterhalb der Wurzel auf das zweite Element ab, am resultierenden Knoten am 3. Element und ändere dann darunter am zweiten Knoten den Wert.

Ich denke einfach, dass die Datenstruktur besser gewählt werden sollte. Eine verschachtelte Liste ist da nicht so einfach zu handeln - wie man ja sieht. Imho wäre es dann sinnvoller, sich wirklich eine Baumstruktur zu überlegen, die man dafür benutzt und die dann eine solche "Index-Pfad"-Suche / Manipulation bietet.

Edit: @/me: Mit der `compose`-Funktion aus dem Paket `brownie.functional` in Kombi mit `operator.itemgetter` sollte man das elegant hinbekommen. Ich bekomme `brownie` unter ArchLinux leider nicht zum Laufen, da es wohl nicht mit Python3 läuft.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
rown
User
Beiträge: 7
Registriert: Mittwoch 11. Januar 2012, 01:23

Hallo Leute,

danke für die Anregungen. Hier vielleicht ein Versuch ein wenig Klarheit zu schaffen.
Python benutze ich in Cinema 4d. Da gibt es die Möglichkeit Benutzerdaten (wie Fließkomma, Text (also Strings), Bool usw.) anzulegen. Praktisch daran ist, dass diese Daten durch die Aktivierung eines Objektes zusehen und veränderbar sind. Leider fehlt ein Datentyp wie Liste. Diese wären aber praktisch, weil man da einfach mehr Daten auf kleinen Raum packen kann. Somit war mein Vorhaben Strings zu schreiben, diese in Listen umzuwandel und dann auszuwerden. Da ich da aber flexibel rangehen wollte, weil ich eigentlich noch kein richtiges Ziel verfolge, habe ich den Weg über die Pfade gewählt. So muss ich am Ende nur noch die Pfade verteilen. Über GetListPath() bekomme ich die Pfade und quasi die Hierarchie. Vorteil daran ist noch, dass man damit nicht eingeschränkt ist, was Anzahl und Verteilung der Listeneinträge angeht.
Danach wollte ich gerne auch eine Möglichkeit finden einzelne Werte in der Liste zu ändern um -für was auch immer- eine Animation hinzukriegen. Daher die Funktion ChangeListValue(). Habe ich auf Frame 0 einen Key gesetzt, der in Pfadposition [0][0] ein Wert von 0 und auf Frame 10 einen Key mit Wert 10 hat, dann wird dieser Wert zwischen Frame 0 und 10 ausgetauscht (in diesem Beispiel also ChangeListValue(Liste, (0,0), 1 oder 2 oder 3 oder 4 oder 5 oder 6 oder 7 oder 8 oder 9).
Was die Bezeichnungen anbelangt gebe Euch vollkommen recht. Es sollten welche sein, die man versteht. Allerdings fände ich Bezeichnungen wie CopyListWithChangedValue() iregndwie zu lang.
Was die Datenstruktur angeht, so ist das alles was mein Hirn bis jetzt bereit war zu denken. Wenn jemand elegante Lösungen hat und sie der Welt mitteilen möchte, denke ich, bin ich nicht der einzige, der sich darüber freuen würde.

Mit besten Grüßen
Rown
BlackJack

@rown: Du willst also komplexere Datenstrukturen haben bei denen die Werte über kryptische Zahlenfolgen angesprochen werden!? Das ist ziemlich unschön und skaliert nicht wirklich.

Ich würde noch Wörterbücher dazu nehmen. Und das ganze dann als JSON (de)serialisieren. Damit ist man sprachunabhängig(er) und hat mit dem `json`-Modul hat einen sicheren Parser in der Standardbibliothek.

Als API könnte man dieser Struktur dann eigene Datentypen verpassen die Attributzugriffe auf Schlüssel von Wörterbücher zulassen und Indexzugriffe.

`copy_list_with_changed_value()` ist tatsächlich etwas lang. Wenn man das kopieren nicht in der Funktion macht, würde `change_value()` ja ausreichen.

Hast Du eigentlich schon einen Anwendungsfall für die Pfade als Werte? Statt ``change_value(liste, (0, 0), 42)`` könnte man ja zum Beispiel auch einfach ``liste[0][0] = 42`` schreiben.
Antworten