Lesen und Schreiben von Klassen in CSV

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.
zegru
User
Beiträge: 60
Registriert: Freitag 9. Oktober 2020, 09:22

Hallo!

Ich würde gerne eine Liste von Objekten als CSV lesen und schreiben. Wie macht man das in Python am besten? Alles Suchen im Netz und im Forum hat leider nicht geholfen.

Folgenden Beispielcode habe ich bis jetzt:

Code: Alles auswählen

import csv
import sys


class Test:
    def __init__(self, a, b):
        self.a = a
        self.b = b

    def __repr__(self):
        return f"Test({self.a}, {self.b})"


# Das hier klappt
x1 = Test(1, 2)
x2 = Test(5, 6)
tests = (x1, x2)
print(tests)

# Das hier leider nicht
csv_writer = csv.writer(sys.stdout)
csv_writer.writerows(tests)  # Hier kracht es
Erwarten würde ich hier eigentlich sowas wie

1, 2
5, 6


Leider bekomme ich stattdessen die Fehlermeldung "_csv.Error: iterable expected, not Test". Etwas kryptisch, aber im Nachhinein verständlich, dass es kracht.
Wie kann man CSV Dateien am besten in Python lesen und schreiben?
Benutzeravatar
kbr
User
Beiträge: 1506
Registriert: Mittwoch 15. Oktober 2008, 09:27

Wenn du Listen von Objekten speichern und später wieder lesen möchtest, bietet sich pickle (aus der stdlib) an. Das funktioniert wunderbar, solange man die Warnungen beherzigt.

Wenn es unbedingt csv sein muss, hast du mehr Arbeit: einerseits pro Instanz alle Attribute in einem Iterable vorbereiten, welches dann von der csv-library akzeptiert wird. Und andererseits die Objekte wieder instanziieren, wenn die csv-Daten gelesen werden.
Benutzeravatar
__blackjack__
User
Beiträge: 14020
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@zegru: Du musst halt aus Deiner Liste (das sollte eher kein Tupel sein) aus `Test`-Objekten etwas machen, was `writerows()` erwartet. Und das ist ein iterierbares Objekt (hast Du) mit einem iterierbaren Objekt pro Datensatz, das die Zellenwerte liefert. Deine `Test`-Objekte sind nicht iterierbar, darum die Fehlermeldung.

Code: Alles auswählen

#!/usr/bin/env python3
import csv
import sys

from attr import astuple, attrib, attrs


@attrs(frozen=True)
class Test:
    a = attrib()
    b = attrib()


def main():
    tests = [Test(1, 2), Test(5, 6)]
    print(tests)
    csv.writer(sys.stdout).writerows(map(astuple, tests))


if __name__ == "__main__":
    main()
Ausgabe:

Code: Alles auswählen

[Test(a=1, b=2), Test(a=5, b=6)]
1,2
5,6
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
zegru
User
Beiträge: 60
Registriert: Freitag 9. Oktober 2020, 09:22

Ich habe das versucht, aber bei mir klappt 'attr' ganz und gar nicht.
Ich verwende ein virtuelles Environment, in das ich das Paket 'attr' mit pip installiert habe:

Code: Alles auswählen

pip freeze | grep attr
attr==0.3.1
Das Programm funktioniert bei mir gar nicht: Ständig beschwert sich Python, dass etliche Methoden nicht definiert sind. Kein Wunder, denn wenn ich das hier tippe:

Code: Alles auswählen

python
Python 3.9.5 (default, May 11 2021, 08:20:37) 
[GCC 10.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import attr
>>> import inspect
>>> inspect.getmembers(attr, predicate=inspect.ismethod)
[]
Was nun?
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Nun gibst du mal “import attr; print(attr)” ein, und zeigst, was dabei herumkam.
zegru
User
Beiträge: 60
Registriert: Freitag 9. Oktober 2020, 09:22

Code: Alles auswählen

>>> print(attr)
<module 'attr' from '/home/chris/devel/test/venv/lib/python3.9/site-packages/attr.py'>
Benutzeravatar
__blackjack__
User
Beiträge: 14020
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Das ist das falsche Package. Beziehungsweise das ist gar keines sondern ein Modul. Das Python-Package heisst `attr`, das Python Package Index Package das man installieren muss heisst `attrs` — mit "s" am Ende.
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Laut https://github.com/python-attrs/attrs/b ... n/setup.py heißt das Paket attrs. Und ist nicht nur eine Datei. Was auch immer du dir da eingefangen hast - das scheint falsch.

Edit: das hier: https://github.com/denis-ryzhkov/attr - ist das falsche.
zegru
User
Beiträge: 60
Registriert: Freitag 9. Oktober 2020, 09:22

Nach einem 'pip uninstall attr; pip install attrs' klappt es jetzt. Danke!
zegru
User
Beiträge: 60
Registriert: Freitag 9. Oktober 2020, 09:22

Ich habe mir die Doku von attr angesehen. Will man das nicht immer verwenden?
Benutzeravatar
sparrow
User
Beiträge: 4535
Registriert: Freitag 17. April 2009, 10:28

Was ist die Substanz der Frage?
So ist die Antwort: Warum sollte man es verwenden, wenn man die Funktionalität nicht braucht?
zegru
User
Beiträge: 60
Registriert: Freitag 9. Oktober 2020, 09:22

Es wird unter anderem die __init__ und die __repr__ Methode automatisch erstellt. Auch wenn man __repr__ nicht von vorne herein benötigt, habe ich sie schon im Nachhinein oft zum Debuggen erzeugt.
Meine generelle Meinung, dass trotz Zen es Python gut täte, wenn es etwas impliziter wäre. Eigentlich wäre es gut, wenn es direkt in die Sprache eingebaut wäre.
Benutzeravatar
__blackjack__
User
Beiträge: 14020
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@zegru: Also ich will das immer benutzen. 🙂 Unter anderem wegen der `__init__()` und der `__repr__()` und dann auch wegen `frozen`, `__eq__()`, `__ne__()`, `__lt__()`, den ganzen anderen magischen Vergleichsmethoden, und `__hash__()` die man dann nicht selbst schreiben muss.
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Hynak, der Autor, hat mal einen Blogpost oder Vortrag darueber gehalten, warum er das nicht in die Standardbibliothek einbauen will. Da ging es im Grunde um die Entkopplung von Release-Zyklen.

Wenn man will, kann man ja aber auch https://docs.python.org/3/library/dataclasses.html benutzen. Das ist vielleicht nicht ganz so umfangreich, aber vieles kann es eben auch schon.
narpfel
User
Beiträge: 690
Registriert: Freitag 20. Oktober 2017, 16:10

@zegru: Noch als Hinweis, warum dein `inspect`-Aufruf eine leere Liste zurückgegeben hat: Du hast mit `inspect.ismethod` gefiltert, aber ein Modul hat (weil es keine Klasse ist) logischerweise keine Methoden, sondern nur Funktionen. IMHO wäre es auch einfacher gewesen, einfach `dir(attr)` zu benutzen.
zegru
User
Beiträge: 60
Registriert: Freitag 9. Oktober 2020, 09:22

Noch eine Frage, in diesem Zusammenhang, wo ebenfalls alles Recherchieren und Probieren nichts geholfen hat: Ich möchte die Objekte nicht nur schreiben, sondern auch lesen, und sie kommen dann in eine Datenbank, und verwenden hierfür SQLAlchemy. Habt ihr einen weiteren Tipp für mich?
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das geht nur, indem du zusätzlich den Typnamen speicherst, und eine Factory für die Objekte baust. Sowas kann man zb mit Metaklassen machen.
Benutzeravatar
__blackjack__
User
Beiträge: 14020
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Hm, bei SQLAlchemy hat man dann doch schon die ORM-Klassen, beziehungsweise kann man sich ja Mapper auf vorhandene Klassen erstellen.
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
zegru
User
Beiträge: 60
Registriert: Freitag 9. Oktober 2020, 09:22

Hm, ich sehe schon: Ich muss mich noch tiefgehender mit Python beschäftigen, Kenntnisse anderer Programmiersprachen erweitert um Comprehensions reicht nicht aus. Die Python-Welt ist doch größer als vermutet. Vor allem das Konzept der Metaklassen ist mir ziemlich neu. Gibt es da eine Einführung, die nicht bei 0 anfängt? Also die nicht unbedingt damit anfängt, wie man den Rechner einschaltet und Python installiert? Und auch keine, die bei 150 anfängt ("Was unterscheidet dieses obskure Modul von jenem?").

Ich habe einstweilen mal probiert, attrib und SQLAlchemy zu verheiraten. Leider kein Erfolg bis jetzt.
Geht, aber halt ohne attr:

Code: Alles auswählen

class Test(Base):
    __tablename__ = "test"
    a = Column(Integer, primary_key=True)
    b = Column(Integer)
Der naive Ansatz mit attr geht nicht, weil angeblich kein Primärschlüssel definiert ist:

Code: Alles auswählen

@attrs(frozen=True)
class Test(Base):
    __tablename__ = "test"
    a = attrib(Column(Integer, primary_key=True))
    b = attrib(Column(Integer))
__deets__
User
Beiträge: 14545
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das man mit SQLAlchemy CSV lesen/schreiben kann, halte ich für unwahrscheinlich. Und eine Kombination mit attrs ist nicht sinnvoll, SA selbst ist ja hoch declarative, und baut sich eigene Klasse - das kommt sich alles in die Quere. - ok, snafu hat gezeigt, dass es geht. Ich finde es immer noch etwas komisch, aber gut.

Statt Metaklassen sind vielleicht auch eher Klassendekoratoren (wie attrs selbst sie benutzt) eine gute Wahl. Damit kannst du zb die erzeugte Methode zum iterieren der Attribute so ummanteln, dass sie vorher noch den Klassennamen ausgibt. Und gleichzeitig eine globale Registry für die genannte Factory füttert, du musst ja eine Zuordnung von Klassenname auf Klasse hinbekommen, und die restlichen Werte dann Typwandeln und einlesen basierend auf der Attribut-Deklaration.

Das ist aber auch alles ganz schön viel Maschinerie. Über wieviele Klasse reden wir denn hier? Denn man kann das bei einigen wenigen ja auch einfach erstmal “zu Fuß” programmieren. Die ganze Meta-Magie ist fein, aber auch oft etwas undurchdringbar.
Antworten