Liste mit wiederholten Objekten

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
Benutzeravatar
Goswin
User
Beiträge: 363
Registriert: Freitag 8. Dezember 2006, 11:47
Wohnort: Ulm-Böfingen
Kontaktdaten:

Erst einmal allen ein erbauliches neues Jahr! :D
Nachdem ich so oft Fehler gemacht hatte, wo sich ein Listenelement ungewollt veränderte, kann ich nun nicht einmal eine Liste bauen, die sich an mehreren Stellen gleichzeitig verändern soll.

Code: Alles auswählen

(a,b,c) = (1,2,3)
lst = [a,b,c,b,a,c]
#die Liste hat die Werte [1,2,3,2,1,3]

a=5
#jetzt soll die Liste die Werte [5,2,3,2,5,3] haben
#(was ich aber nicht hinkriege)
Was mache ich falsch?
Zuletzt geändert von Anonymous am Mittwoch 4. Januar 2017, 00:17, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
BlackJack

@Goswin: Was Du erreichen willst geht so schlicht und ergreifend nicht. Binden eines Wertes an einen Namen hat keinen Einfluss auf andere Werte die vielleicht vorher mal an den gleichen Namen gebunden waren oder andere Bindungen von Namen an Werte. Zudem sind ganze Zahlen Werte die unveränderbar sind, also bleibt eine 1 in einer Liste immer eine 1 die man nur durch einen anderen Wert ersetzen, aber nicht zu einem anderen Wert verändern kann.
Benutzeravatar
Kebap
User
Beiträge: 687
Registriert: Dienstag 15. November 2011, 14:20
Wohnort: Dortmund

Goswin hat geschrieben:

Code: Alles auswählen

#die Liste hat die Werte [1,2,3,2,1,3]
a=5
#jetzt soll die Liste die Werte [5,2,3,2,5,3] haben
#(was ich aber nicht hinkriege)
Wie wärs hiermit?

Code: Alles auswählen

original_list = [1,2,3,2,1,3]
new_list = [5 if value==1 else value for value in original_list]
print(new_list)
--> [5, 2, 3, 2, 5, 3]
MorgenGrauen: 1 Welt, 8 Rassen, 13 Gilden, >250 Abenteuer, >5000 Waffen & Rüstungen,
>7000 NPC, >16000 Räume, >200 freiwillige Programmierer, nur Text, viel Spaß, seit 1992.
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@Goswin: eine einmal erzeugte Liste enthält die Werte und weiß nicht, aus welchen Variablen sie entstanden ist. Willst Du die Werte ändern, mußt Du die Liste erneut erzeugen:

Code: Alles auswählen

def create_the_list(a,b,c):
    return [a,b,c,b,a,c]

(a, b, c) = (1, 2, 3)
lst = create_the_list(a, b, c)
#die Liste hat die Werte [1,2,3,2,1,3]

a = 5
lst = create_the_list(a, b, c)
#die Liste hat die Werte [5,2,3,2,5,3]
Benutzeravatar
Goswin
User
Beiträge: 363
Registriert: Freitag 8. Dezember 2006, 11:47
Wohnort: Ulm-Böfingen
Kontaktdaten:

Vielen Dank für all die Hinweise und guten Lösungsvorschläge. Zum Glück ist meine Liste relativ kurz, so dass eine Neuerstellung nicht allzuviel Zeit nimmt. Für längere Listen müsste <s>ich</s> es aber (mit tuple([a],,[c],,[c],[a])?) eine Möglichkeit geben, Pointer irgendwie zu simulieren wenn diese in Python nicht vorhanden sind.

(Und alles das, weil der dumme tkinter keine transparenten Polygone zeichnen kann)


@BlackJack:
Jetzt habe ich wieder etwas dazugelernt, nämlich dass in Python eine Liste ein Werte-Array und kein Pointer-Array ist. So ein wichtiges Hintergrundwissen sollte in jedem Einführungsbuch für Python hervorgehoben werden, was aber offensichtlich nicht der Fall ist.
Zuletzt geändert von Goswin am Mittwoch 4. Januar 2017, 13:20, insgesamt 2-mal geändert.
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@Goswin: was hat das mit Polygonen zu tun? Und nein, einelementige Listen in Tuplen sind höchst wahrscheinlich nicht die Lösung. In Python gibt es keine Pointer und das Wort Wert ist sehr allgemein. Vor allem wenn man von anderen Programmiersprachen kommt, ist es gefährlich, irgendwelche Begriffe zu übernehmen. Mir ist auf Anhieb auch keine Programmiersprache bekannt, die das von Dir erhoffte Verhalten zeigen würde (zum Glück, denn das würde nur riesiges Chaos nach sich ziehen).

In Python sind alles Objekte. Eine Liste ist also eine Liste von Objekten. Namen sind nur Referenzen auf Objekte. Wenn Du also ein neues Objekt (eine andere Zahl) an einen Namen bindest, dann ist der einzige Effekt der, dass der Name auf ein anderes Objekt verweist (abgesehen davon, dass das Objekt, das vorher an diesen Namen gebunden war, eventuell gar nicht mehr fest referenziert wird und deshalb einiges in Gang kommt).
BlackJack

@Goswin: Das sollte eher nicht in (allgemeinen) Einführungsbüchern stehen, denn das setzt voraus das man dem Leser etwas über C und Pointer beibringen muss damit er Python versteht, und das wo es in Python so etwas wie Pointer als Python-Datentyp überhaupt gar nicht gibt. Du denkst in Kategorien die in anderen Sprachen und bei der Implementierung von CPython in C Sinn machen, aber nicht in Python selbst. Vielleicht ist das zumindest ein Grund warum Du, wie Du beschreibst, Probleme mit Listen und auf der einen Seite unabsichtlichen Veränderungen, und auf der anderen Seite unmögliche Änderungswünsche hast.

In Python ist jeder Wert ein Objekt und bei Zuweisungen und Referenzen handelt es sich immer um den selben Wert, das heisst ein und das selbe Objekt ist über mehrere Wege erreichbar. Das ist weder „by value“ noch „by reference“ sondern „by object sharing“. Wenn es „by reference“ wäre, könnte man die Bindung beim Aufrufer beeinflussen, also zum Beispiel das machen was Du hier mit den Zahlen machen wolltest. Wenn es „by value“ wäre, dann würden bei Zuweisungen und Aufrufen Werte implizit kopiert, was Python nicht macht, und was zu den unbeabsichtigten Änderungen führt die Dir sonst manchmal passieren. Wenn einem so etwas häufiger passiert, dann denkt man sehr wahrscheinlich in diesen falschen Begriffen die sich auf C, Pascal, oder C++, aber eben nicht wirklich sinnvoll oder widerspruchsfrei auf Python anwenden lassen ohne das man die Sprache Python verlässt und über die Implementierung in einer anderen Sprache redet. Man sollte aber nicht C voraussetzen müssen um Python's Semantik zu erklären. Insbesondere bei Anfängern in Einführungsliteratur.
Benutzeravatar
Goswin
User
Beiträge: 363
Registriert: Freitag 8. Dezember 2006, 11:47
Wohnort: Ulm-Böfingen
Kontaktdaten:

Sirius3 hat geschrieben:In Python sind alles Objekte. Eine Liste ist also eine Liste von Objekten. Namen sind nur Referenzen auf Objekte.
Genau so hatte ich es mir vorgestellt. Aber können die Namen nicht auf veränderbare Objekte zeigen, welche ich nachträglich verändere? Und wenn ja, wie mache ich das??
Zuletzt geändert von Goswin am Mittwoch 4. Januar 2017, 13:43, insgesamt 1-mal geändert.
BlackJack

@Goswin: Ja können sie, genau so wie bei Listeneinträgen. Sie können aber auch auf unveränderbare Objekte zeigen. Und Zahlen sind unveränderbar. Es wäre auch sicher nicht gewollt wenn das anders wäre, denn wenn eine 1 keine 1 mehr ist, dann würde ``x += 1`` im *ganzen Programm* plötzlich nicht mehr bedeuten das `x` um *eins* erhöht wird. Bei so einem Verhalten von Zahlen könnte man nicht mehr wirklich sinnvolle Programme schreiben.
Benutzeravatar
Kebap
User
Beiträge: 687
Registriert: Dienstag 15. November 2011, 14:20
Wohnort: Dortmund

Goswin hat geschrieben:können die Namen nicht auf veränderbare Objekte zeigen, welche ich nachträglich verändere?
Vielleicht erklärst du mal, was du eigentlich damit bezwecken willst, und dann kann dir hier sicherlich jemand eine Lösung vorschlagen, die mit Python simpel funktioniert.
MorgenGrauen: 1 Welt, 8 Rassen, 13 Gilden, >250 Abenteuer, >5000 Waffen & Rüstungen,
>7000 NPC, >16000 Räume, >200 freiwillige Programmierer, nur Text, viel Spaß, seit 1992.
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Die Sinnhaftigkeit ist fraglich, wenns aber ungedingt sein muss, kannst du Dir selbst einen Referenzdatentypen simulieren:

Code: Alles auswählen

class Ref(object):
  def __init__(self, value):
    self.value = value
  def __repr__(self):
    return 'Ref(%s)' % repr(self.value)

a = Ref(1)
b = Ref('Wurst')

l1 = [a, b]
l2 = [a, b]
print l1, l2

a.value = 'Kuchen'
b.value = 2

print l1, l2
Die Standardcontainertypen zeigen übrigens auch das Verhalten, siehe mutable vs immutable.
Benutzeravatar
Goswin
User
Beiträge: 363
Registriert: Freitag 8. Dezember 2006, 11:47
Wohnort: Ulm-Böfingen
Kontaktdaten:

Wie bereits gesagt, sind meine Listen relativ kurz und die obigen Lösungen für praktische Zwecke zufriedenstellend. Für den rein theoretischen Fall, dass ich eine sehr große Liste hätte, bin ich nun selbst nach einigem Herumprobieren auf folgendes gekommen:

Code: Alles auswählen

class pt():
   def __init__(self,zahl): self.zahl = zahl
   __repr__  =  __str__ = lambda self: "{}`".format(repr(self.zahl))

(pt0, pt1, pt2) = (pt(1), pt(2), pt(3))
lst = [pt0, pt1, pt2, pt1, pt2, pt0, pt2, pt0, pt1]
print(lst)
#druckt [1`, 2`, 3`, 2`, 3`, 1`, 3`, 1`, 2`]

pt0.zahl = 5
print(lst, lst[5], lst[5].zahl, 2*lst[5].zahl)
#druckt [5`, 2`, 3`, 2`, 3`, 5`, 3`, 5`, 2`] 5` 5 10
pt0.zahl += 1
print(lst, lst[5], lst[5].zahl, lst[5].zahl+3)
#druckt [6`, 2`, 3`, 2`, 3`, 6`, 3`, 6`, 2`] 6` 6 9
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@Goswin: was hat das mit kurzen oder langen Listen zu tun? Das Erzeugen einer Liste mit Zahlen ist immer deutlich schneller, als das Erzeugen einer Liste mit Deinen pt-Objekten. Genauso das Lesen der Werte. Das generelle Problem bei Deiner „Lösung“ ist, dass überall dort, wo man etwas mit der Liste machen möchte, man umständlich mit Referenzen arbeiten muß. Den Anwendungsfall, wo das ein Vorteil sein soll, habe ich noch nicht gesehen.
Benutzeravatar
Goswin
User
Beiträge: 363
Registriert: Freitag 8. Dezember 2006, 11:47
Wohnort: Ulm-Böfingen
Kontaktdaten:

Sirius3 hat geschrieben:Den Anwendungsfall, wo das ein Vorteil sein soll, habe ich noch nicht gesehen.
Soll das ein schlechter Witz sein? Es gibt Tausende von Anwendungen, die Pointer benutzen (zB verkettete Listen usw).

Mein Problem mit Python ist nicht, das es sich ANDERS als Fortran, C, oder Java verhält, sondern dass es sich nicht DURCHGEHEND anders verhält. In den obigen Beispielen des Themenstrangs ist KEIN Unterschied bemerkbar, aber zwischen

Code: Alles auswählen

a=[2];
b=a;
a=[3];
print(b)
und

Code: Alles auswählen

a=[2];
b=a;
a[0]=3;
print(b)
ist urplötzlich ein Riesenunterschied. Das nehme ich zwar zur Kenntnis, aber was da hinter den Kulissen (bei der Implementierung) vorgeht, habe ich auch nach 8_Jahren Pythonprogrammierung nicht verstanden. In Tutorials wie http://www.python-kurs.eu/python3_variablen.php wird zwar gesagt, dass Python anders ist, aber auf den obigen Unterschied wird gar nicht eingegangen.

Und jetzt bitte nicht damit anfangen, ich solle mir eine andere Sprache suchen (ein Lieblingsargument in diesem Forum). Andere Sprachen haben eben andere Nachteile; eine die keine hat, müsste ich mir selber erfinden (aber die hätte ja den Nachteil, dass sie nicht implementiert ist).
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

@Goswin: Vielleicht solltest du mal ein Lisp implementieren. Also einen Lisp-Interpreter. Das ist nicht zwar ganz trivial, aber auch nicht sonderlich schwierig. Danach sollte dir klar sein, wie Python funktioniert. Python ist nämlich ein Lisp mit weniger Klammern, kein C mit weniger Klammern.

Nicht, dass ich der Ansicht wäre, man könne Python nur verstehen, wenn man schon Lisp kann, oder nur dann, wenn man schon mal einen Lisp-Interpreter geschrieben hat, aber in deinem Fall wäre es evtl. hilfreich, da du zu sehr in Fortran/C/Java denkst. Es gibt eben auch andere Programmiermodelle.

Marvin Minsky hat es auf den Punkt gebracht: "Anyone could learn Lisp in one day, except that if they already knew Fortran, it would take three days." Für Fortran kann man nach Belieben auch C/C#/C++/Java einsetzen und für Python auch Ruby/Perl.
In specifications, Murphy's Law supersedes Ohm's.
BlackJack

Goswin: Verkettete Listen sind in Python kein Problem und sehen im Grunde so aus wie in C, nur das man statt einem `struct` und Funktionen eine Klasse schreibt, weil das in Python der Verbunddatentyp ist. Man hat nur keine Pointer als *Datentyp* in der Sprache.

Code: Alles auswählen

class Node(object):

    def __init__(self, data, next_node=None):
        self.data = data
        self.next_node = next_node


class LinkedList(object):

    def __init__(self):
        self.items = None

    def __iter__(self):
        node = self.items
        while node:
            yield node.data
            node = node.next_node

    def __len__(self):
        return sum(1 for _ in self)

    def prepend(self, item):
        self.items = Node(item, self.items)


def main():
    linked_list = LinkedList()
    linked_list.prepend(23)
    linked_list.prepend(42)
    linked_list.prepend(4711)
    print(len(linked_list))
    for item in linked_list:
        print(item)


if __name__ == '__main__':
    main()
Wie man sieht sind verkettete Listen in Python kein Problem. Eher selten in der Praxis so zu sehen, weil der eingebaute `list`-Typ in der Regel alles mitbringt was man benötigt. Ausser wenn man wirklich mal auch am Anfang der Datenstruktur effizient hinzufügen oder entfernen möchte, dann nimmt man `collections.deque`. Was dann noch fehlt ist ein Datentyp bei dem man auch in der Mitte effizient Elemente einfügen oder entfernen kann. Dann könnte man sich in der Tat eine doppelt verkette Liste einfach selbst implementieren. Und natürlich Graphen, die man ähnlich mit eigenen Knotenklassen erstellen kann.

Ich denke was Sirius3 meinte war der Code den Du da geschrieben hast. Denn so etwas habe ich in Python noch nicht gesehen und sehe auch nicht wirklich wo man das braucht. Wenn ich mal *vermuten* dürfte was Du da tust, dann sieht das eher nach einer eigenen Koordinaten-Klasse und einer Polygon-Klasse aus und nach Code um zwischen der besch…eidenen API von Tk hin und her zu wandeln.

Zu Deinen beiden Codebeispielen: Namen sind in Python nicht gleichzusetzen mit Adressen oder Schachteln wo man Dinge/Werte hinein tut sondern Haftnotizen die man an Objekte klebt. Das ist eine Zuweisung. Nicht etwas in eine Schachtel mit dem Namen `x` stecken, sondern eine Haftnotiz wo `x` drauf steht an ein Objekt kleben. Die Adressen gehören in Python fest zu den Werten.

Dein erstes Codebeispiel:

a=[2]: Es wird eine Liste mit dem Element 2 erstellt und da wird ein Zettel mit `a` draufgeklebt.
b=a: Dann wird ein Zettel mit `b` auf das Objekt geklebt wo der Zettel mit `a` dran klebt. An der Liste kleben nun zwei Haftnotizen.
a=[3]: Es wird eine Liste mit dem Element 3 erstellt und da wird der Zettel mit `a` dran geklebt, wofür er von der anderen Liste entfernt werden muss, weil es (in einem Namensraum) immer nur einen Zettel mit dem gleichen Namen gibt, und der immer nur maximal an einem Objekt kleben kann.
print(b): Es wird das Objekt an dem der Zettel `b` klebt ausgegeben, also die Liste mit der 2 darin. Denn das umkleben der Zettels mit `a` hat keinen Einfluss auf irgend etwas anderes. Keines der beiden Objekte, weder das wo der Zettel vorher klebte, noch das wo er dann drauf geklebt wird, hat irgend einen Einfluss auf die beteiligten Objekte.

Zweites Codebeispiel:
a=[2]: Wie im ersten Beispiel.
b=a: Ebenfalls wie im ersten Beispiel.
a[0]=3: Hier wird das Objekt mit den Zettel `a` genommen und dessen erstes Element referenziert nun das Objekt 3. Und nicht mehr das Objekt 2, weil an einem Index immer nur *ein* Objekt referenziert werden kann.
print(b): Wie im ersten Beispiel wird das Objekt ausgegeben auf dem Zettel `b` klebt. Das ist die Liste auf der beide Zettel kleben, weil Zettel `a` ja nicht zu einem anderen Objekt bewegt wurde, also die Liste in der wegen der Zeile hiervor das Element 3 steckt.

Das ist ein ”Riesenunterschied” weil die vorletzte Zeile in den beiden Beispielen etwas unterschiedliches macht. Es wird nicht der Name neu gebunden, an ein *anderes* Listenobjekt, sondern es wird die eine Liste an die beide Namen gebunden sind *verändert*. Und riesig ist der Unterschied IMHO auch nur weil Du im Denkmuster von C (oder einer ähnlichen Sprache) fest hängst, wie man an den Semikolons erahnen kann.

Wobei ich auch sagen würde das Java im grossen und ganzen auch wie Python funktioniert. Bei den Wertdatentypen unter den Grunddatentypen macht es keinen Unterschied das Java die anders behandelt, weil die unveränderlich sind, also vom Effekt her kein Unterschied besteht. Wenn man diese Art des Objektmodells nicht versteht, dann kann man weder Java noch Python sinnvoll programmieren.

Und JavaScript funktioniert an der Stelle auch so wie Python, Ruby, Perl, Java, …. Das ist also wirklich nichts ”exotisches” was ausgerechnet Python anders machen würde als alle anderen Programmiersprachen.

Da pillmuncher Lisp ins Spiel brachte: Auch dort gibt es keinen Pointer-Datentyp aber verkettete Listen sind *der* Grunddatentyp und sie werd aus Cons-Zellen erstellt, also im Grunde so etwas wie Tupel die aus einem Wert und einer nächsten Cons-Zelle oder einem ”Endkennzeichen” bestehen, letztlich also so etwas wie die `Node`-Klasse im Python-Beispiel weiter oben.

Mal als Beispiel in Hy (in Python implementierter Lisp-Dialekt):
[codebox=clojure file=Unbenannt.txt]#!/usr/bin/env hy
(require [hy.contrib.loop [loop]])

(defn length [a-list]
(if (empty? a-list) 0 (inc (length (cdr a-list)))))

(defmain [&rest args]
(setv a-list '())
(setv a-list (cons 23 a-list))
(setv a-list (cons 42 a-list))
(setv a-list (cons 4711 a-list))
(print (length a-list))
(loop [[items a-list]]
(unless (empty? items)
(print (car items))
(recur (cdr items)))))[/code]
Also so könnte `length` in Lisp aussehen wenn man es selber implementieren müsste, natürlich gibt es das schon und da solche Listen *die* Grunddatenstruktur in Lisp sind in der sogar die Programme repräsentiert werden, gibt es auch eine syntaktische Möglichkeit solche Listen zu schreiben: Mit Klammern, wie das Programm selbst auch. Das hier wäre dann etwas näher an dem was man in Lisp schreiben würde:
[codebox=clojure file=Unbenannt.txt]#!/usr/bin/env hy
(require [hy.contrib.loop [loop]])

(defmain [&rest args]
(let [a-list '(4711 42 23)]
(print (len a-list))
(loop [[items a-list]]
(unless (empty? items)
(print (car items))
(recur (cdr items))))))[/code]
Und da Hy auf Python abgebildet wird, gibt es die `for`-Form, die das `loop`-Makro ersetzen kann und man bei folgendem landet:
[codebox=clojure file=Unbenannt.txt]#!/usr/bin/env hy

(defmain [&rest args]
(let [a-list '(4711 42 23)]
(print (len a-list))
(for [item a-list] (print item))))[/code]
Benutzeravatar
Goswin
User
Beiträge: 363
Registriert: Freitag 8. Dezember 2006, 11:47
Wohnort: Ulm-Böfingen
Kontaktdaten:

@pillmuncher & BlackJack:
Vielen Dank für die ausführlichen Erklärungen und Beispiele. :D Ich werde mir Lisp einmal etwas genauer anschauen.
BlackJack hat geschrieben: Wenn ich mal *vermuten* dürfte was Du da tust, dann sieht das eher nach einer eigenen Koordinaten-Klasse und einer Polygon-Klasse aus und nach Code um zwischen der besch…eidenen API von Tk hin und her zu wandeln.
Genau. Falls du meine Bemerkung weiter oben nicht gelesen hast, bin ich über deine korrekte Vermutung extrem erstaunt

Beim Code "a=[3]", wenn ich dich richtig verstanden habe, läuft folgendes ab:
Es werden zwei Objekte erzeugt. Erstens ein Listenobjekt, auf dem der Zettel mit Namen 'a' klebt und an welchem ein Bindfaden angebunden ist, an dem ein weiterer Zettel mit einem neuen, mir unbekannten internen Namen hängt, zum Beispiel '_a0_'. Im allgemeinen wären Listenobjekte wie Kipus vorzustellen, woran viele Bindfäden mit Namenszetteln hängen. Zweitens wird noch ein Zahlenobjekt erstellt, auf den der Zettel mit dem neuen Namen '_a0_' geklebt wird. Ein anderes Objekt, auf dem der Zettel 'a0' vorher klebte, könnte frei werden.

Beim Code "a[0]=3" würde hingegen nur ein einziges Objekt erstellt, und zwar ein Zahlenobjekt, auf welches der Zettel '_a0_' geklebt wird. Ein anderes Objekt, auf dem der Zettel '_a0_' vorher klebte, könnte frei werden.
BlackJack

@Goswin: Ja genau so kann man sich den Unterschied vorstellen. Das mit den Bindfäden ist gut. Muss ich mir merken. :-)
Benutzeravatar
pyHoax
User
Beiträge: 84
Registriert: Donnerstag 15. Dezember 2016, 19:17

So würde ichs machen...

Code: Alles auswählen

pt = [1,2,3]
lst = [0, 1, 2, 1, 2, 0, 1, 0, 1]
print [ pt[i] for i in lst ]
pt[0] = 5
print [ pt[i] for i in lst ]
Antworten