Datenspeicherung mit Python (file, pickle, shelve, db...)

Gute Links und Tutorials könnt ihr hier posten.
Gesperrt
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Datenspeicherung mit Python

Ich fasse hier mal ein paar Möglichkeiten zum Speichern von Daten zusammen. Das sind so die gängigsten Varianten, aber trotzdem nur ein kleiner Teil.

Du kannst einen Text oder auch andere Daten direkt in eine Datei schreiben. Das geht mit dem file-Objekt. file kann direkt verwendet werden. Man kann aber auch den Befehl open verwenden um an ein file-Objekt zu kommen. file-Objekte verwendet man auch zum Lesen der Daten.

Du kannst einen Text oder auch andere Daten strukturiert speichern. Je nachdem wie komplex die Struktur ist, bieten sich verschiedenste Möglichkeiten an.

Eine Möglichkeit der strukturierten Datenspeicherung: Du wandelst deine Daten in einen strukturierten Text um (z.B. XML, YAML, JSON, CSV, ...) und schreibst diesen mit file in eine Datei.

Eine andere Möglichkeit ist: Du erstellst dir ein Objekt (ein Dictionary, eine Liste, eine Klasse,...) und speicherst dieses Objekt mit Pickle in eine Datei. Diese Datei kann dann mit Pickle auch wieder ausgelesen werden. Du bekommst dann direkt wieder das vorher gespeicherte Objekt zurück. -- Keinen Text.

Pickle hat den Nachteil, dass das Objekt komplett eingelesen werden muss. Um diesen Nachteil zu umgehen, gibt es (unter anderem) das Modul shelve.
Damit kannst du ein dictionaryartiges Objekt erzeugen, das die Daten in einer Datei hält. Der Vorteil: Eine 1 GB große Datei ist auch kein Problem, da die Daten ja nicht im Speicher gehalten werden müssen. Und trotzdem ist der Zugriff auf die einzelnen Daten sehr schnell.

Für stark strukturierte Daten, wie z.B. Daten einer Warenwirtschaft, wo es sich auch um verschiedenste Daten handelt (Adressen, Konten, Artikel,...), die gespeichert und gefunden werden müssen, empfiehlt es sich ein Datenbanksystem zu verwenden. Hier empfehle ich für kleinere Anwendungen, also grob gesagt "unter einem GB Daten" (das mit der Größe ist aber nur eine Schätzung), SQLite zu verwenden. SQLite ist seit Python 2.5 als Modul mit dabei.
Für größere Anwendungen und/oder wenn mehrere Programme/Personen/Computer auf die Daten gleichzeitig zugeifen müssen, dann sind PostgreSQL oder MySQL keine schlechte Wahl.


Textspeicherung mit dem file-Objekt

Beim Speichern kann man angeben, wie Python den Zeilenumbruch in die Datei schreibt. Die Angabe des Schreibmodus "w" bedeutet, dass Python den Zeilenumbruch mit der für das verwendete Betriebssystem üblichen Zeichenkette darstellen soll. Das ist zum Beispiel unter Linux das Zeichen "\n" (LineFeed) und unter Windows die Zeichenkette "\r\n" (CarriageReturn + LineFeed).

Der Text:

Code: Alles auswählen

"""Zeile1
Zeile2"""
wird unter Windows also so in die Textdatei geschrieben:

Code: Alles auswählen

Zeile1\r\nZeile2
Unter Linux wird der Text so in die Datei geschrieben:

Code: Alles auswählen

Zeile1\nZeile2
Der Schreibmodus "wb" bedeutet schlicht gesagt, dass wir uns selber um die richtige Codierung der Zeilenumbrüche kümmern müssen. Das ist bei Texten etwas umständlicher, aber für andere Daten meist lebensnotwendig. Es ist nicht immer erwünscht, dass Python diese Sache für uns in die Hand nimmt.

Der Modus ist auch beim Lesen der Datei wichtig. Liest man eine Datei mit dem Modus "r" (Standard) ein, dann kümmert sich Python wieder darum, dass Zeilenumbrüche von der Codierung des Betriebssystems in ein einfaches "\n" umgewandelt werden. Wir müssen also nicht ständig prüfen ob eine Zeile mit "\r\n" oder mit "\n" umgebrochen wurde. Wir bekommen ein einfaches "\n" als Zeilenumbruch. Und das unter Linux wie auch unter Windows.
Wenn wir Dateien zwischen Linux und Windows hin und her schieben, dann kann es sein, dass wir eine Datei, die unter Windows geschrieben wurde, unter Linux auslesen müssen und umgekehrt. Um diesen Umstand abzudecken, gibt es den Lesemodus "rU". Dieser kümmert sich darum, dass auch unter diesen Umständen immer ein "\n" als Zeilenumbruch verwendet wird. Allerdings funktioniert das Flag "U" nur beim Lesen.

Auch beim Lesen gilt wieder, dass es nicht immer erwünscht ist, dass sich Python um die Zeilenumbrüche kümmert. Dann öffnet man eine Datei mit dem Modus "rb".

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: iso-8859-1 -*- 

# Schreiben
s = "Das ist mein Text"
f = file("dateiname.txt", "w")
f.write(s)
f.close()

# Lesen
f = file("dateiname.txt", "r")
s = f.read()
f.close()
Mehzeiliger Text mit dem file-Objekt

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: iso-8859-1 -*- 

# Schreiben
s = """Das ist die erste Zeile.
Das ist die zweite Zeile.
Das ist die dritte Zeile.
"""

f = file("dateiname.txt", "w")
f.write(s)

s = "Das ist die vierte Zeile.\n" \
    "Das ist die fuenfte Zeile.\n"
f.write(s)

s_list = [
    "Das ist die sechste Zeile.\n",
    "Das ist die siebte Zeile.\n"
]
f.writelines(s_list)

f.close()

# In einem Stueck lesen
f = file("dateiname.txt", "r")
s = f.read()
f.close()
print s
print

# Zeilenweise lesen
f = file("dateiname.txt", "r")
for line in f:
    print line[:-1] # ohne Zeilenumbruch
f.close()
print

# In eine Liste einlesen
f = file("dateiname.txt", "r")
lines = f.readlines()
f.close()
print lines
Das Ergebnis sieht dann so aus:

Code: Alles auswählen

Das ist die erste Zeile.
Das ist die zweite Zeile.
Das ist die dritte Zeile.
Das ist die vierte Zeile.
Das ist die fuenfte Zeile.
Das ist die sechste Zeile.
Das ist die siebte Zeile.


Das ist die erste Zeile.
Das ist die zweite Zeile.
Das ist die dritte Zeile.
Das ist die vierte Zeile.
Das ist die fuenfte Zeile.
Das ist die sechste Zeile.
Das ist die siebte Zeile.

['Das ist die erste Zeile.\n', 'Das ist die zweite Zeile.\n',
'Das ist die dritte Zeile.\n', 'Das ist die vierte Zeile.\n',
'Das ist die fuenfte Zeile.\n', 'Das ist die sechste Zeile.\n',
'Das ist die siebte Zeile.\n']
Weitere Informationen über file bzw. open bekommst du hier:
- http://docs.python.org/lib/built-in-funcs.html#l2h-27
- http://docs.python.org/lib/built-in-funcs.html#l2h-54
- http://docs.python.org/lib/bltin-file-objects.html


Daten strukturieren und in eine Datei speichern

In diesem Beispiel, werden die Daten einer Person in eine Zeile geschrieben. Die Felder werden durch die Zeichenkette "|||" getrennt. Das ist praktisch, da diese Zeichenkette sehr selten vorkommt und mit split() leicht wieder herausgeparst werden kann.

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: iso-8859-1 -*- 

# Schreiben
personen = [
    {"first name": "Gerold", "last name": "Penz", "age": "31"},
    {"first name": "Ludwig", "last name": "Bucher", "age": "29"},
]

zeilen = []
for person in personen:
    zeilen.append(
        "%s|||%s|||%s" % (
            person["first name"], person["last name"], person["age"]
        )# Vorname|||Nachname|||Alter
    )
strukturierter_text = "\n".join(zeilen)

f = file("dateiname.txt", "w")
f.write(strukturierter_text)
f.close()

# Lesen
f = file("dateiname.txt", "r")
personen = []
for zeile in f:
    vorname, nachname, alter = zeile.strip().split("|||")
    personen.append(
        {"first name": vorname, "last name": nachname, "age": alter}
    )
f.close()
print personen
Ergebnis::

Code: Alles auswählen

[{'last name': 'Penz', 'first name': 'Gerold', 'age': '31'},
{'last name': 'Bucher', 'first name': 'Ludwig', 'age': '29'}]
Daten mit Pickle speichern

Wie man am nächsten Beispiel sehen wird, ist diese Variante kürzer. Allerdings ist das Ergebnis keine menschenlesbare Textdatei. Die Daten werden in einem eigenen Format abgespeichert. Auf die verschiedenen Speicherprotokolle, die es bei Pickle gibt, gehe ich hier nicht ein.

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: iso-8859-1 -*- 

import pickle

# Schreiben
personen = [
    {"first name": "Gerold", "last name": "Penz", "age": "31"},
    {"first name": "Ludwig", "last name": "Bucher", "age": "29"},
]

f = file("dateiname.bin", "wb")
pickle.dump(personen, f)
f.close()

# Lesen
f = file("dateiname.bin", "rb")
personen = pickle.load(f)
f.close()
print personen
Ergebnis:

Code: Alles auswählen

[{'last name': 'Penz', 'first name': 'Gerold', 'age': '31'},
{'last name': 'Bucher', 'first name': 'Ludwig', 'age': '29'}]
Erklärungen zu Pickle:
- http://docs.python.org/lib/module-pickle.html


Daten mit Shelve verwalten

Shelve hat gegenüber Pickle den entscheidenden Vorteil, dass man damit wie mit einem Dictionary arbeiten kann. Die Datei muss nicht, wie bei Pickle, immer komplett gelesen werden, bevor man an die Daten kommt. Shelve liest gezielt nur die Daten aus der Datei, die benötigt werden. Durch Caching werden die Lese- und Schreibzugriffe optimiert, so dass Shelve ziemlich schnell ist. Shelve lässt sich fast so wie ein Dictionary verwenden, mit dem Vorteil, dass die Daten nicht im Speicher, sondern in einer Datei auf der Festplatte liegen. So können auch Daten verarbeitet werden, die im Ganzen nicht im Speicher platz haben. Ich habe zum Testen schon Shelve-Dateien mit über einem Gigabyte erstellt und beim Abrufen der Daten keinen Unterschied zu einer kleinen Shelve-Datei bemerkt.

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: iso-8859-1 -*- 

import shelve

# Schreiben
sh = shelve.open("dateiname.slv")
sh["Person001"] = {"first name": "Gerold", "last name": "Penz", "age": "31"}
sh["Person002"] = {"first name": "Ludwig", "last name": "Bucher", "age": "29"}
sh.close()

# Lesen
sh = shelve.open("dateiname.slv")

# .. so
print sh["Person001"]
print sh["Person002"]

#.. oder so
for person in sh.itervalues():
    print person

sh.close()
Ergebnis:

Code: Alles auswählen

{'last name': 'Penz', 'first name': 'Gerold', 'age': '31'}
{'last name': 'Bucher', 'first name': 'Ludwig', 'age': '29'}
{'last name': 'Penz', 'first name': 'Gerold', 'age': '31'}
{'last name': 'Bucher', 'first name': 'Ludwig', 'age': '29'}
Hier noch der obligatorische Link zur Beschreibung von shelve:
- http://docs.python.org/lib/module-shelve.html


Daten in einer Datenbank verwalten

Irgendwann kommt man drauf, dass die Datenhaltung in Textdateien, in XML-Dateien, in Pickle-Dateien usw. mehrere entscheidende Nachteile hat.

- Nicht flexibel
- Bei einer Suche nach NICHT-Schlüsselfeldern muss immer der gesamte Datenbestand durchsucht werden.
- Suchkombinationen müssen umständlich programmiert werden. (z.B. eine Suche nach Personen eines bestimmten Ortes, die im Jahr 1974 geboren sind)
- ...

Um solchen Problemen aus dem Weg zu gehen, hat man Datenbanken erfunden. In relationalen Datenbanken wie z.B. SQLite, PostgreSQL, MySQL, usw. werden die Daten in logischen Tabellen gespeichert. Um bei Suchanfragen schneller die gewünschten Daten zu finden, können Indizes (Indexe) angelegt werden. Sind diese Indizes einmal erstellt, werden diese vom Datenbanksystem automatisch aktuell gehalten.

Die meisten relationalen Datenbanksysteme können mit der Abfrage-/Anfragesprache SQL (Structured Query Language) konfiguriert, mit Daten befüllt und abgefragt werden.

Mit Datenbanken umzugehen ist also nicht so einfach wie das Schreiben von Daten in eine Textdatei. Allerdings bieten Datenbanken eine mit dem Programm wachsende Flexibilität, die mit Textdateien nur sehr mühsam nachzuprogrammieren ist.

Für dieses Beispiel braucht man entweder schon Python 2.5 oder pySQLite von dieser Internetseite: http://initd.org/tracker/pysqlite

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: iso-8859-15 -*-

import os
import sqlite3 # Ab Python 2.5

#
# Schreiben
#

# Nur zum Testen: Alte Datei vorher loeschen
try:
    os.remove("dateiname.s3db")
except:
    pass

# Verbindung herstellen
conn = sqlite3.connect("dateiname.s3db")

# Tabelle erstellen
sql = """
CREATE TABLE adressen (
  id INTEGER PRIMARY KEY,
  first_name TEXT,
  last_name TEXT,
  age INTEGER
)
"""
conn.execute(sql)
conn.commit()

# Werte in die Tabelle schreiben (das Feld "ID" muss nicht befüllt werden)
personen = [
    {"first_name": "Gerold", "last_name": "Penz", "age": "32"},
    {"first_name": "Ludwig", "last_name": "Mustermann", "age": "30"},
]
sql = """
INSERT INTO adressen (
    first_name,
    last_name,
    age
) VALUES (
    :first_name,
    :last_name,
    :age
)
"""
conn.executemany(sql, personen)
conn.commit()

# Verbindung schliessen (nur zum Testen)
conn.close()

#
# Lesen
#

# Verbindung wieder herstellen
conn = sqlite3.connect("dateiname.s3db")

sql = """
SELECT
    id, first_name, last_name, age
FROM
    adressen
"""
cur = conn.cursor()
cur.execute(sql)
rows = cur.fetchall()

for row in rows:
    id, vorname, nachname, alter = row
    print "Datensatz:", row
    print "ID:       ", id, type(id)
    print "Vorname:  ", vorname, type(vorname)
    print "Nachname: ", nachname, type(nachname)
    print "Alter:    ", alter, type(alter)
    print

# Verbindung schliessen
conn.close()
So sieht die Ausgabe dieses Beispiels aus:

Code: Alles auswählen

Datensatz: (1, u'Gerold', u'Penz', 32)
ID:        1 <type 'int'>
Vorname:   Gerold <type 'unicode'>
Nachname:  Penz <type 'unicode'>
Alter:     32 <type 'int'>

Datensatz: (2, u'Ludwig', u'Mustermann', 30)
ID:        2 <type 'int'>
Vorname:   Ludwig <type 'unicode'>
Nachname:  Mustermann <type 'unicode'>
Alter:     30 <type 'int'>
Hier noch ein paar Links zum Thema pySQLite:
- http://initd.org/tracker/pysqlite
- http://initd.org/pub/software/pysqlite/ ... guide.html
- http://sqliteadmin.orbmu2k.de/ (feines Programm zum Administrieren der SQLite-Datenbank; läuft leider nur unter Windows und hat Schwierigkeiten mit Umlauten)
- http://sqlitebrowser.sourceforge.net/ (SQLite-Browser; kommt mit Umlauten klar)

Außerdem gibt es bereits eine recht gute Hilfe zum neuen SQLite3-Modul in der Python-Dokumentation: http://docs.python.org/lib/module-sqlite3.html

Und hier findest du ein ausführlich kommentiertes Beispiel, das die Verwendung von Datenbanken demonstriert: http://www.python-forum.de/post-74749.html


Das war sie -- die kleine Übersicht über die, meiner Meinung nach, wichtigsten Möglichkeiten, Daten als Datei auf die Festplatte zu speichern.

Ich lade hiermit jede(n) dazu ein, über eine Art der Datenspeicherung zu schreiben. Ob ich sie bereits aufgezählt habe oder nicht. Auch wenn es nur wenige Zeilen sind. Besser wenig als nichts. ;-)

mfg
Gerold
:-)
Zuletzt geändert von gerold am Donnerstag 9. April 2009, 06:13, insgesamt 25-mal geändert.
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Zuletzt geändert von gerold am Mittwoch 20. Februar 2008, 22:49, insgesamt 1-mal geändert.
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Hi!

Weil es so schön hier her passt. SQLite und Umlaute: http://www.python-forum.de/post-39457.html#39457

lg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Hi!

Das passt auch recht gut hier her. Eine Möglichkeit (von mehreren), wie man einen Unicode-String in eine Textdatei im "Standard-Encoding" des Betriebsystems schreiben kann.

Es wird auch aufgezeigt, wie man diese Textdatei ausliest und automatisch wieder einen Unicdode-String zurück bekommt.

Siehe: http://www.python-forum.de/post-56356.html#56356

lg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
BlackJack

Immer die vom System bevorzugte Kodierung zu verwenden halte ich für keine gute Idee. Man produziert damit letztendlich Dateien die von einem bestimmten System abhängig und nicht so portabel sind. Ich denke es ist besser Unicode grundsätzlich in einer Kodierung zu speichern, die auch den gesamten Umfang wirklich abbilden kann. Meine Wahl ist UTF-8, weil das zusätzlich noch den Vorteil hat, weitgehend "verständlich" für nicht unicode-fähige Textwerkzeuge zu sein.
Benutzeravatar
mkesper
User
Beiträge: 919
Registriert: Montag 20. November 2006, 15:48
Wohnort: formerly known as mkallas
Kontaktdaten:

Für Logfiles sind diese beiden Optionen interessant:

Code: Alles auswählen

# Anhängen (statt überschreiben), nicht puffern
logfile = open('Logfile.txt', 'a', 0)
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Hallo!

Ich habe dieses Thema gesperrt, da unser phpBB Probleme bei vielen Codebeispielen in einem Forenthread macht. Noch ein paar Beiträge und der Server streikt.

Falls es Fragen oder Ergänzungen zu diesem Thema gibt. --> Bitte in einem neuen Forenthread.

lg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
Gesperrt