Verständnisfrage zur Listenerzeugung

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
heyJo
User
Beiträge: 25
Registriert: Mittwoch 24. Januar 2018, 20:49
Wohnort: Köln

Hallo,

ich habe mal eine Verständnisfrage.
Auf der Suche nach der Möglichkeit aus jpgs eine animierte gif zu erstellen, bin ich auf folgendes Script gestoßen.

Code: Alles auswählen

from pathlib import Path
from PIL import Image

p = Path('.')

img,*imgs = [Image.open(f) for f in sorted(p.glob("*.jpg"))]

img.save(fp = "image.gif",
         format = "GIF",
         append_images = imgs,
         save_all = True,
         duration = 1000,
         loop=0)
Was da passiert habe ich dem Prinzip nach verstanden.
Es wird eine Liste mit Image-Objete erzeugt, die dann an als gif gespeichert werden. Was mir nicht klar ist:

Wie funktioniert/ welche Bedeutung hat der Abschnitt

img,*imgs = …

img ist ein Image-Objekt und imgs die Liste (tye(…)). Was mach das „*“ in diesem Zusammenhang? Auf der einen Seite habe ich die Variable img und imgs aber auf der andren Seite der Zuweisung nur eine „Operation“, oder? In Worte gefasst steht doch dort "Öffene, in sortierter Reigenfolge, alle jpgs und erzeuge ein Image-Objekt. Das Ergebnis lege in einer Liste (imgs) ab.

Als Anfänger hätte ich versucht mit einer Schleife die einzelnen Bilder zu öffnen und an die Liste anzufügen. Das oben angeführte Skript ist kürzer, aber für mich nicht durchschaubar.
Wer kann mir mal auf die Sprünge helfen?
Benutzeravatar
sparrow
User
Beiträge: 4538
Registriert: Freitag 17. April 2009, 10:28

Das nennt sich "tuple unpacking" und ist sehr praktisch.
Wenn du weißt, wieviele Elemente kommen, kannst du die direkt an Namen binden.
Wenn du nicht weißt, wieviele Elemente kommen, dann kannst du vor den Namen ein * schreiben und "fängst" so quasi alle restlichen Elemente.
Rechts von = steht also etwas, das Elemente liefert und das erste dieser Elemente wird an "img" gebunden, alle anderen als Liste an "imgs".

Hilft das? :

Code: Alles auswählen

>>> a, b = (1, 2)
>>> a
1
>>> b
2
>>> a, b = (1, 2, 3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: too many values to unpack (expected 2)
>>> a, *b = (1, 2, 3)
>>> a
1
>>> b
[2, 3]
>>> a, b, *rest = range(10)
>>> a
0
>>> b
1
>>> rest
[2, 3, 4, 5, 6, 7, 8, 9]
>>>
Benutzeravatar
__blackjack__
User
Beiträge: 14052
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@heyJo: Formal findet man die Beschreibung von Zuweisungen in der Python-Dokumentation bei der Beschreibung der Grammatik unter Assignment statements, wo am Ende auf PEP 3132 -- Extended Iterable Unpacking hingewiesen wird, wo man dieses Konstrukt mit Beispielen erklärt findet.

Zu der Stelle in der Referenzdokumentation wäre man beispielsweise über den Index in der Dokumentation gekommen, wenn man dort den Eintrag "*" sucht und aus der Auflistung dort in assignment target list auswählt, was direkt zu dem entsprechenden Abschnitt von „Assignment statements“ führt.

Grob kann man sagen, dass der * auf der linken Seite der Zuweisung in etwa das Gegenteil von dem macht, was er in Argumentlisten von Funktionsaufrufen oder in literalen Sequenzen macht. Statt wie dort aus einer Sequenz (bzw. iterierbaren Objekten allgemein) einzelne Elemente zu machen, sammelt er beim Ziel einer Zuweisung Elemente in einer Liste.
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
heyJo
User
Beiträge: 25
Registriert: Mittwoch 24. Januar 2018, 20:49
Wohnort: Köln

Vielen Dank für die schnellen Antworten! Ihr habt mir sehr geholfen und ich nun was schlauer ;-)
Benutzeravatar
__blackjack__
User
Beiträge: 14052
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@heyJo: An dem Code könnte man noch zwei, drei Sachen anders machen. Neben Namen die nicht kryptisch abgekürzt sind, braucht man den Pfad `p` auch gar nicht an einen Namen binden. Das Argument von `Path` kann man auch weg lassen, dann funktioniert das auch auf Systemen wo "." nicht für das aktuelle Arbeitsverzeichnis steht.

Die „list comprehension“ könnte man durch einen Generatorausdruck ersetzen, oder aber gleich `map()` verwenden, weil ja nur eine bereits vorhandene Funktion auf jedes Element angewendet werden soll.

Code: Alles auswählen

#!/usr/bin/env python3
from pathlib import Path

from PIL import Image


def main():
    first_image, *images = map(Image.open, sorted(Path().glob("*.jpg")))
    first_image.save(
        "image.gif",
        append_images=images,
        save_all=True,
        duration=1000,
        loop=0,
    )


if __name__ == "__main__":
    main()
“Vir, intelligence has nothing to do with politics!” — Londo Mollari
Antworten