for Schleife Frage

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
wusa
User
Beiträge: 30
Registriert: Dienstag 18. Februar 2014, 11:08

Freitag 17. Mai 2019, 21:39

Hallo Zusammen,

Ich habe angefangen die docs.python.org Dokumentation durchzuarbeiten.

Momentan bin ich noch am Anfang habe aber dazu gleich eine Frage. Diese Liste wird erzeugt:

Code: Alles auswählen

words = ['cat', 'window', 'defenestrate']
for w in words:
    print(w, len(w))

cat 3
window 6
defenestrate 12
Gefolgt von diesem Code:

Code: Alles auswählen

for w in words[:]: 
    if len(w) > 6:
    words.insert(0, w)
    
words
['defenestrate', 'cat', 'window', 'defenestrate']
In diesem Fall wird die Schleife genau einmal durchlaufen Und das Wort wird eingefügt.

Code: Alles auswählen

for w in words: 
    if len(w) > 6:
    words.insert(0, w)
    
words
Füge ich den Coke allerdings ohne[:] ein, endet die Schleife in einer Endlosschleife.

Muss ich das jetzt einfach so hin nehmen, dass es so ist, oder gibt es hierfür eine Erklärung?

Danke
Benutzeravatar
sparrow
User
Beiträge: 1360
Registriert: Freitag 17. April 2009, 10:28

Freitag 17. Mai 2019, 21:55

Die Einrückung für den Block der If-Bedingung stimmt nicht.

Edit: Mein Fehler - es ist natürlich eine schlechte Idee aber mit dem Slicen [:] legst du eine Kopie (oder einen Kon) der Liste an.
Es ist spät ;)

Ansonsten ist es immer eine schlechte Idee Listen zu ändern, während du über sie iterierst. Wenn du immer am Anfang der Liste etwas einfügst, kannst du nie am Ende ankommen.
Benutzeravatar
__blackjack__
User
Beiträge: 4228
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Freitag 17. Mai 2019, 22:33

Die funktionierende Variante kann man auch so:

Code: Alles auswählen

words = ['cat', 'window', 'defenestrate']
for w in words.copy(): 
    if len(w) > 6:
        words.insert(0, w)

print(words)
oder so schreiben:

Code: Alles auswählen

words = ['cat', 'window', 'defenestrate']
for w in list(words): 
    if len(w) > 6:
        words.insert(0, w)

print(words)
Beides wahrscheinlich ein bisschen lesbarer/verständlicher wenn man nicht weiss was ``words[:]`` macht.

Und um das Phänomen der Endlosschleife noch mal etwas ausführlicher zu beschreiben: Beim dritten Schleifendurchlauf ist man bei 'defenestrate' angekommen. Das ist länger als 6 Zeichen. Du fügst das Wort am Anfang der Liste ein – damit rücken ja alle Elemente der bisherigen Liste eine Position weiter und die Liste hat nun vier Elemente. Und der nächste Schleifendurchlauf liefert dann das vierte Wort – das was vorher an dritter Stelle war und was länger als 6 Zeichen ist. Und das wiederholt sich von nun an endlos – es wird vorne eingefügt, die Liste wird ein Element länger, das nächste Element in der Schleife ist wieder das mit dem das Einfügen angefangen hat…
“Give a man a fire and he's warm for a day, but set fire to him and he's warm for the rest of his life.”
— Terry Pratchett, Jingo
wusa
User
Beiträge: 30
Registriert: Dienstag 18. Februar 2014, 11:08

Samstag 18. Mai 2019, 06:30

__blackjack__ hat geschrieben:
Freitag 17. Mai 2019, 22:33

Code: Alles auswählen

words = ['cat', 'window', 'defenestrate']
for w in words.copy(): 
    if len(w) > 6:
        words.insert(0, w)

print(words)
Das erschließt sich mir. Ich lege ein Kopie an und nur dort füge ich das Wort > 6 ein.
__blackjack__ hat geschrieben:
Freitag 17. Mai 2019, 22:33

Code: Alles auswählen

words = ['cat', 'window', 'defenestrate']
for w in list(words): 
    if len(w) > 6:
        words.insert(0, w)

print(words)
Das wiederum könnte man auch als Endlosschleife verstehen?
Da ich mir alle Elemente von der Liste(words) anzeigen lasse. Hier nicht so ersichtlich, dass eine Kopie angelegt wird, oder dass nur ein Durchlauf stattfindet.
__blackjack__ hat geschrieben:
Freitag 17. Mai 2019, 22:33
Das ist länger als 6 Zeichen. Du fügst das Wort am Anfang der Liste ein – damit rücken ja alle Elemente der bisherigen Liste eine Position weiter und die Liste hat nun vier Elemente. Und der nächste Schleifendurchlauf liefert dann das vierte Wort – das was vorher an dritter Stelle war und was länger als 6 Zeichen ist.
Merkt sich diese Methode, die Wörter an welcher Stelle diese stehen oder ist das einfach eine ganz normale Schleife ohne irgendwelche Argumente?

Code: Alles auswählen

for w in words[:]: 
    if len(w) > 6:
    words.insert(0, w)
Dieser Code mit [:] legt quasi auch ein Kopie an und nur dort wird das zusätzliche Wort eingefügt. So habe ich es jetzt verstanden.
Benutzeravatar
ThomasL
User
Beiträge: 774
Registriert: Montag 14. Mai 2018, 14:44
Wohnort: Kreis Unna NRW

Samstag 18. Mai 2019, 08:39

mit words[:], words.copy() und list(words) wird eine Kopie der Liste words erstellt und über diese wird iteriert,
in deinem Beispiel über die 3 Wörter.
In der Schleife wird dann die originale Liste verändert.
Ist die Schleife am Ende, wird die Kopie verworfen.
Ich bin Pazifist und greife niemanden an, auch nicht mit Worten.
Für alle meine Code Beispiele gilt: "There is always a better way."
https://projecteuler.net/profile/Brotherluii.png
Benutzeravatar
__blackjack__
User
Beiträge: 4228
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Samstag 18. Mai 2019, 10:44

@wusa: Also von der Formulierung her hast Du es falsch verstanden. Es wird eine Kopie angelegt, aber die Werte werden nicht in die Kopie eingefügt. Dann wären sie danach ja auch gar nicht sichtbar, weil die Kopie an keinen Namen gebunden wird. `word` ist ja an die ursprüngliche Liste gebunden, nicht an die Kopie.

Das man mit `list(iterable)` eine neue Liste mit allen Elementen aus `iterable` erstellt mag vielleicht ganz am Anfang nicht offensichtlich sein, das braucht man aber und es ist flexibler als der `copy()`-Aufruf. Diese Methode muss nicht jede Sequenz haben. Der `list`-Typ hat die Methode auch erst relativ spät in der Python-Geschichte bekommen.

Und es findet in jedem Fall, auch in dem nicht funktionierenden, nur ein Durchlauf über die Liste statt. Nur das der da halt nie fertig wird, weil während des Durchlaufs die Liste kontinuierlich länger wird.

Noch eine Variante, die eine neue Liste für das Ergebnis anlegt, was in Python üblich ist(!):

Code: Alles auswählen

words = [w for w in words if len(w) > 6] + words
Und die wohl flexibelste Variante die vom Argument nur erwartet, das es iterierbar ist, und auch nicht sofort eine komplette Liste erstellt, sondern die Elemente ”lazy” erzeugt:

Code: Alles auswählen

from itertools import chain, tee


def i_cannot_think_of_a_good_name_here(iterable):
    items_a, items_b = tee(iterable)
    return chain(filter(lambda s: len(s) > 6, items_a), items_b)

# ...
words = list(i_cannot_think_of_a_good_name_here(words))
“Give a man a fire and he's warm for a day, but set fire to him and he's warm for the rest of his life.”
— Terry Pratchett, Jingo
wusa
User
Beiträge: 30
Registriert: Dienstag 18. Februar 2014, 11:08

Dienstag 21. Mai 2019, 10:01

Vielen Dank für die ganzen Antworten. Habe mich am Wochenende versucht, und ich komme damit zurecht.

Thema daher für mich abgeschlossen.
Antworten