ubuntuusers.de

Verschachtelte Listen in Python - Problem

Status: Gelöst | Ubuntu-Version: Nicht spezifiziert
Antworten |

Arubeto

Anmeldungsdatum:
4. Dezember 2007

Beiträge: 384

Moin,

ich hab mal wieder eine Frage zu Python. Wie kann ich einzelnen Listen, die als Elemente in einer weiteren Liste eingebettet sind, wiederum Elemente anhängen? Ich habe also ungefähr folgendes:

In [1]: l = [[]]*3

In [2]: l
Out[2]: [[], [], []]

Wenn ich nun über den Index der Liste l angebe, an welche Kind-List ich ein Element anhängen möchte, wird das übergebene Element immer an alle Kind-Listen angehängt. Wenn ich also in etwa so etwas mache

In [3]: l[1].append(8) 

würde ich eigentlich eine List wie

[[],[8],[]] 

erwarten, tatsächlich bekomme ich aber:

In [4]: l
Out[4]: [[8], [8], [8]]

Woran liegt das? Wie indiziere ich verschachtelte Listen korrekt? Ich weiß, dass das sicher eine blöde Anfängerfrage ist, aber ich komm leider nicht auf die Lösung. Danke!

Arubeto

Arubeto

(Themenstarter)

Anmeldungsdatum:
4. Dezember 2007

Beiträge: 384

Ok, habs schon. Wenn ich die Liste so anlege

l = [[]]*3

wird nur eine wirkliche Kindliste angelegt und die anderen beiden Elemente sind nur Referenzen auf die eigentliche Liste. Deswegen haben sich immer gleich alle "drei" Listen verändert, wenn ich eigentlich nur an einer etwas änder wollte.

So klappts:

l = []
for i in range(3):
   l.append([])

snafu1

Avatar von snafu1

Anmeldungsdatum:
5. September 2007

Beiträge: 2133

Wohnort: Gelsenkirchen

Sowas lässt sich übrigens auch mit dem is-Operator prüfen:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
In [12]: l = [[]]*3

In [13]: l[1] is l[2]
Out[13]: True

In [14]: l = []

In [15]: for i in range(3):
   ....:    l.append([])
   ....:    
   ....:    

In [16]: l[1] is l[2]
Out[16]: False

Ist es für dich denn wirklich nötig, mit drei leeren Listen zu starten? Ansonsten wäre es wohl eleganter, die Listen anzuhängen, wenn sie mit Inhalt gefüllt sind, anstatt so ein Konstrukt zu nehmen. Ist natürlich abhängig von der Aufgabenstellung.

Arubeto schrieb:

wird nur eine wirkliche Kindliste angelegt und die anderen beiden Elemente sind nur Referenzen auf die eigentliche Liste

Ich glaube, man kann präziser sagen, dass alle drei Listen eine Referenz auf eine interne Liste sind. Jede Änderung an einer der Listen bewirkt eine Änderung an der "Mutter-Liste" und damit auch eine Änderung an ihren Kindern. Zumindest habe ich das bei Python so verstanden.

Lunar

Anmeldungsdatum:
17. März 2006

Beiträge: 5792

snafu1 schrieb:

Ich glaube, man kann präziser sagen, dass alle drei Listen eine Referenz auf eine interne Liste sind.

Das ist keine magische interne Liste, dass sind einfach ganz banale Referenzen auf die selbe Liste. Das liegt daran, dass der Multiplikationsoperator auf Listen angewandt nur flache Kopien erzeugt. Die äußere Liste wird also dreimal kopiert und anschließend konkateniert. Die innere Liste aber wird nicht angefasst, so dass die drei Kopien die selben Elemente enthalten.

Das gilt auch für Zahlen und Zeichenketten, nur sind diese unveränderlich, so dass man das in der Regel nicht bemerkt.

Bei veränderbaren Objekten wie Mengen, Listen und Wörterbüchern dagegen sollte man bei der Verwendung des Multiplikationsoperators Vorsicht walten lassen 😉

snafu1

Avatar von snafu1

Anmeldungsdatum:
5. September 2007

Beiträge: 2133

Wohnort: Gelsenkirchen

Wahrscheinlich in solchen Fällen am besten mappen:

1
2
3
4
In [26]: l = map(list, [[]]*3)

In [27]: l[1] is l[2]
Out[27]: False

Marc_BlackJack_Rintsch Team-Icon

Ehemalige
Avatar von Marc_BlackJack_Rintsch

Anmeldungsdatum:
16. Juni 2006

Beiträge: 4677

Wohnort: Berlin

@snafu1: Damit erstellst Du als zweites Argument für map eine Liste, die gar nicht wirklich gebraucht wird. Der idiomatische Weg ist eine "list comprehension":

1
2
3
4
5
6
In [190]: lst = [[] for dummy in xrange(3)]

In [191]: lst[1].append(42)

In [192]: lst
Out[192]: [[], [42], []]

Ferio Team-Icon

Avatar von Ferio

Anmeldungsdatum:
24. April 2007

Beiträge: 383

Oder wenn man wirklich nichts wegwerfen will:

1
2
3
4
5
6
7
In [104]: l = [obj() for obj in [list]*3]

In [105]: l[1].append(42)

In [106]: l

Out[106]: [[], [42], []]

Marc_BlackJack_Rintsch Team-Icon

Ehemalige
Avatar von Marc_BlackJack_Rintsch

Anmeldungsdatum:
16. Juni 2006

Beiträge: 4677

Wohnort: Berlin

@Ferio: Das gleiche Problem: Du erstellst eine Liste mit Referenzen auf list, die unnötig ist und am Ende wieder verworfen wird. Wenn man es über den Typen machen will:

1
2
3
4
5
6
7
8
In [220]: from itertools import islice, repeat

In [221]: lst = [t() for t in islice(repeat(list), 3)]

In [222]: lst[1].append(42)

In [223]: lst
Out[223]: [[], [42], []]

Aber wie gesagt, der idiomatische Weg ist das nicht.

snafu1

Avatar von snafu1

Anmeldungsdatum:
5. September 2007

Beiträge: 2133

Wohnort: Gelsenkirchen

Marc 'BlackJack' Rintsch schrieb:

Aber wie gesagt, der idiomatische Weg ist das nicht.

Man sollte wohl noch erwähnen, dass dieser Weg nicht nur sprachlich komplizierter, sondern auch langsamer sein dürfte. islice() und repeat() brauchen ja intern auch wieder ein xrange-Objekt zum Iterieren. Ich denke, deine erste Lösung ist auf alle Fälle zu bevorzugen.

Marc_BlackJack_Rintsch Team-Icon

Ehemalige
Avatar von Marc_BlackJack_Rintsch

Anmeldungsdatum:
16. Juni 2006

Beiträge: 4677

Wohnort: Berlin

Mir fällt gerade auf, dass das islice() unnötig ist, weil repeat() auch eine Wiederholungsanzahl als zweites Argument entgegennimmt. ☺

Antworten |