Minidatenbanken und Python?

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MariaDB/MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
Antworten
Benutzeravatar
SG ;-)
User
Beiträge: 38
Registriert: Dienstag 26. August 2014, 17:50

Als Pythonneuling mal eine Frage:

ist es möglich mit Python auch die guten alten "Minidatenbanken" aus den 70ern zu verwenden? Und wenn ja,wie geht das hier? Auch mit "Get" und "Put"? Und ggf. mit den entsprechenden Errorcode-Abfragen um festzustellen, ob die Datenbank gerade auch von jmd anderem benutzt wird?

Ich finde Minidatenbanken, gerade um schnell Werte aus Klassen abzuspeichern, und die u.U auch Multi-User-fähig zur Verfügung zu haben, extrem praktisch.

Info, ggf. auch Codeschnipsel wären nett, wenn Minidatenbanken in Python verwendbar sind :)

LG SG ;-)
Me.PEBKAC ^^
Benutzeravatar
/me
User
Beiträge: 3554
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

SG ;-) hat geschrieben:ist es möglich mit Python auch die guten alten "Minidatenbanken" aus den 70ern zu verwenden?
Mir ist jetzt nicht klar, was du unter "Minidatenbanken" verstehst. In der 70ern bzw. den 80ern habe ich noch IMS mit DL/I abgefragt.

Vielleicht suchst du so etwas wie shelve oder dbm.
Benutzeravatar
SG ;-)
User
Beiträge: 38
Registriert: Dienstag 26. August 2014, 17:50

Hallo /me

als (noch aktiver) VBAler, der Du bist, hätt ich eig gedacht, dass Du da schon mal was davon gehört hast. :)

http://www.office-loesung.de/ftopic426439_0_0_asc.php

LG SG ;-)
Me.PEBKAC ^^
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@SG ;-): ich wüßte jetzt nicht, dass in den 70ern irgendjemand stink normale Dateizugriffe "Datenbank" genannt hätte :P . Multi-User-Fähigkeit ist wohl auch eher Traum, außer man steckt wirklich viel Arbeit in die "Datenbank"-Umgebung.

Klar kann man binäre Dateien auch mit Python lesen und schreiben. Mit `struct` und `namedtuple` kann man problemlos das, was in Deinem verlinkten Artikel beschrieben wird, realisieren.

Aber warum sollte man? Mit deutlich weniger Aufwand kann man eine SQLite-Datenbank anlegen, und mit Daten füllen.
BlackJack

@SG ;-): Dateien auf dem Hintergrundspeicher werden heute eigentlich immer im ”wahlfreien” Zugriff geöffnet, wobei die ”Record-Grösse” ein Byte ist. Das war früher mal etwas besonderes weil wahlfreier Zugriff nicht bei jedem Dateisystem oder Speichermedium (Bandlaufwerke), möglich oder üblich war und es spezielle Dateitypen für verschiedene Aufgaben gab. Der im verlinkten Forenbeitrag erwähnte C64 beziehungsweise die dort verwendeten Dateisysteme auf den Disketten können bei normalen Dateien zum Beispiel nicht beliebige Stellen in der Datei ansteuern ohne vom Anfang der Datei bis zu der Stelle zu lesen. Aber es gab spezielle Dateien mit denen man Datensätze fester Grösse direkt ansteuern kann. Dafür kann man mit diesen Dateien auch wirklich nur das machen und sie nicht wie andere Dateien Byte für Byte sequenziell lesen.

Get und Put musst Du Dir also mit `read()`, `write()`, und `seek()` selber basteln.

Feststellen ob die Datei auch von jemand anderem benutzt wird ist plattformübergreifend nicht so einfach, insbesondere weil der andere das auch absichern muss, es also nicht reicht das er die Datei öffnet, sondern dass er das auch ”ansagt”. Ausserdem gibt es gewisse Konstellationen mit ”Netzlaufwerken” bei denen das Sperren von Dateien nicht zuverlässig funktioniert.

Die Liste der Vorteile, beziehungsweise was sich sonst nur schwer lösen lässt, aus dem verlinkten Forenbeitrag, ist IMHO nicht ganz überzeugend. 1. Daten kann man auch anders speichern. 2. Daten kann man auch bei SQL-Datenbanken einfach als SQL-Dump per Mail weitergeben. Bei SQLite kann man auch die Datei weitergeben. 3. Mich hält ein einfaches flaches Dateiformat mit Datensätzen mit fester Länge nicht davon ab manuell einzugreifen. Und ich denke ich bin nicht der Einzige. 4. Der Aufwand ist verglichen mit fertigen Lösungen relativ hoch. 5. Welche Probleme mit Trennzeichen bei CSV? Darum kümmert sich das `csv`-Modul. 6. Unterschiedliche Datensatzlängen in CSV sind nur ein Problem wenn man wahlfreien Zugriff braucht ohne die Daten in den Speicher zu lesen.

Wie Sirius3 schon schrieb kann man sich so eine Datei mit Datensätzen fester länge mit dem `struct`-Modul und normalen Dateioperationen relativ einfach selber basteln. Bleibt alleine die Frage wozu das gut sein soll, weil man letztendlich dann sein eigenes DBMS schreibt, nur halt schlechter als das was es schon ausgereift und getestet gibt. SQLite3 ist Bestandteil der Python-Standardbibliothek, und auch `shelve`, `csv`, oder `json`, was man alles für kleine ”Datenbanken” verwenden kann. Wobei SQLite3 und `shelve` durchaus auch mit grösseren Datenmengen klar kommen können. Bei SQLite3 hätte man zudem den Vorteil eine SQL-Datenbank zu haben und die Daten deshalb auch auf ein grösseres DBMS umziehen zu können, wenn das nötig wird. Wenn man eine Abstraktionsschicht wie SQLAlchemy verwendet und nichts DB-spezifisches, geht das wahrscheinlich sogar recht problemlos.

Wenn man unbedingt eine Datei mit fester Datensatzgrösse haben möchte, würde ich auch nicht unbedingt selber etwas erfinden sondern etwas nehmen was auch in die 70er (oder davor) zurück geht, nämlich DBF-Dateien. Die können von den meisten Tabellenkalkulationen und Datenbank-GUIs in Office-Paketen verwendet werden. Ich würde so eine Datei aber nicht erstellen wenn ich nicht müsste, also wenn mich nicht eine andere Software dazu zwingt. Zum Beispiel sind DBF-Dateien Bestandteil von ESRI Shapefiles die man oft im GIS-Umfeld findet.
Benutzeravatar
MagBen
User
Beiträge: 799
Registriert: Freitag 6. Juni 2014, 05:56
Wohnort: Bremen
Kontaktdaten:

Bevor Du Dir ein eigenes binäres Format als Alternative zu Datenbanken ausdenkst, schau Dir mal hdf5 an.
In hdf5 werden die Daten binär in einer Datei gespeichert. Baumstruktur, Tabellen, Arrays und Bildformate werden unterstützt. Der Zugriff auf die Daten ist extrem performant. Du kannst mit externen Tools in die Daten reinschauen und auch mit anderen Programmiersprachen darauf zugreifen.
Die Python Schnittstelle zu hdf5 heißt PyTables.
a fool with a tool is still a fool, www.magben.de, YouTube
Benutzeravatar
SG ;-)
User
Beiträge: 38
Registriert: Dienstag 26. August 2014, 17:50

Alles klar,

(fast jedenfalls).^^ Ich werd mir die einzelnen Lösungsvorschläge auf jeden Fall genauer ansehen. SQL wäre möglicherweise wirklich eine Lösung, der Grund, warum ich unter VBA bisher darauf verzichtet hatte, war in erster Linie ein MS-Office-spezifischer: man müßte unter Office für jedes Office, das dann damit arbeiten soll, gesondert DLLS installieren, was erstens einmal schon ein gewisser Arbeitsaufwand für sich ist; - darüberhinaus, müsste man aber auch bei unterschiedlichen Betriebssystemskonfigurationen und unterschiedlichen Officeversionen das VBA-Script für jeden einzelnen Rechner anpassen, da die entsprechenden DLLS (32/64bit; Office 2007/2010 etc.) untereinander nicht kompatibel sind. Minidatenbanken benötigen unter Office keine zusätzlichen DLLs, weswegen das Problem dadurch schlicht und ergreifend entfällt.

Ein weiterer Vorteil von Minidatenbanken gegenüber aufwändigen relationalen Datenbanken ist allerdings ihre enorme Gechwindigkeit: da nicht jedesmal die ganze Datenbank durchsucht werden muss, um einen Datensatz zu finden und zu ändern, sondern man einfach einen Integer-Zeiger verwenden kann, läuft der Speichervorgang beim Ändern von Datensätzen in Micro- wenn nicht sogar Nanosekunden ab. Man kann das Speichern also direkt aus dem Property_Get einer Klassenvariablen feuern, ohne dadurch nennenswerte Verzögerungen im Programmablauf befürchten zu müssen. (Jedenfalls bei den Anwendungen, dich ich bisher geschrieben habe. Da ging es z.B. darum, einen Textstring oder eine Zahl während des Eintippens zu speichern, und diesen Wert damit SOFORT allen beteiligten Clients zur Verfügung zu stellen)

Dank an alle, für Eure zahlreichen Beiträge, jetzt muss ich erst mal ein bissel recherchieren :)

LG SG ;-)
Me.PEBKAC ^^
Benutzeravatar
/me
User
Beiträge: 3554
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

SG ;-) hat geschrieben:Ein weiterer Vorteil von Minidatenbanken gegenüber aufwändigen relationalen Datenbanken ist allerdings ihre enorme Gechwindigkeit: da nicht jedesmal die ganze Datenbank durchsucht werden muss, um einen Datensatz zu finden und zu ändern, sondern man einfach einen Integer-Zeiger verwenden kann, läuft der Speichervorgang beim Ändern von Datensätzen in Micro- wenn nicht sogar Nanosekunden ab.
Die Zeiten bekommst du aber nur, wenn die "Minidatenbank" im RAM liegt. Dann kannst du aber auch eine SQLite im RAM verwenden.
BlackJack

@SG ;-): Ich weiss nicht was für Vorstellungen Du von der Speicherung von Daten in relationalen Datenbanken hast, aber wenn man eine ganze Zahl als Primärschlüssel verwendet, also letztendlich den gleichen Typ den man bei einer ”Minidatenbank” als Schlüssel für den Zugriff benutzt, wird kein vernünftiges DBMS dafür alle Datensätze sequentiell durchgehen müssen um den passenden Eintrag in der Datei zu finden. Für einen Primärschlüssel wird üblicherweise implizit ein Index angelegt. Die dadurch entstehende Verzögerung dürfte auf aktueller Hardware so minimal sein das sie von anderen Faktoren, wie den Schreibzugriff selbst, locker überschattet wird.

Im Gegenteil sehe ich bei einer Datenbankdatei mit sequentiell gespeicherten Datensätzen mehr lineare Suchen über die gesamte Datei wenn man mal etwas anhand anderer Kriterien als der Datensatznummer haben möchte. Wo ich selbst bei sequentiellen Suchen in relationalen DBMS dann Vorteile bei mehreren Suchen durch Caches erwarte, oder halt auch die Möglichkeit Indexe anzulegen um das zu beschleunigen. Alles Sachen die man sich bei einer selbst verwalteten, einfachen Datenbankdatei erst selber programmieren müsste.

Das mit dem Multiclientzugriff halte ich übrigens schon micht mehr für trivial selber zu schreiben, im Gegensatz zum lesen und schreiben von Records in eine Datei. Dafür muss man sich eine sinnvolle API ausdenken und die dann auch noch fehlerfrei und robust implementieren. Alleine deswegen würde ich schon ein fertiges DBMS nehmen von dem man weiss, dass es das Problem bereits gelöst hat. Wenn man wirklich nur IDs als Schlüssel benötigt, kann man statt eines relationen DBMS auch etwas anderes nehmen was einen einfachen Key/Value-Store ermöglicht. CouchDB, MemcacheDB, MongoDB, oder Redis kommen mir da so spontan in den Sinn.
BlackJack

Mal nebenbei so was in der Richtung wie in dem verlinkten Forenbeitrag implementiert:

Code: Alles auswählen

#!/usr/bin/env python
# coding: utf-8
from __future__ import print_function
import errno
import os
from collections import namedtuple
from datetime import date as Date, datetime as DateTime
from random import randint, random, shuffle
from struct import Struct


class RecordFile(object):
    def __init__(self, file_or_filename, struct):
        if isinstance(file_or_filename, basestring):
            try:
                self.file = open(file_or_filename, 'r+')
            except IOError as error:
                if error.errno == errno.ENOENT:
                    self.file = open(file_or_filename, 'w+')
                else:
                    raise
        else:
            self.file = file_or_filename
        self.struct = struct

    def __enter__(self):
        return self

    def __exit__(self, *_args):
        self.close()

    def __len__(self):
        current_position = self.file.tell()
        self.file.seek(0, os.SEEK_END)
        result = self.tell_record()
        self.file.seek(current_position)
        return result

    def __getitem__(self, index):
        if index < 0:
            index += len(self)
        if not 0 <= index < len(self):
            raise IndexError
        return self.read_record(index)

    def __setitem__(self, index, data):
        self.write_record(data, index)

    def tell_record(self):
        return self.file.tell() // self.struct.size

    def seek_record(self, number):
        self.file.seek(self.struct.size * number)

    def read_record(self, number=None):
        if number is not None:
            self.seek_record(number)
        return self.struct.unpack(self.file.read(self.struct.size))

    def write_record(self, data, number=None):
        if number is not None:
            self.seek_record(number)
        self.file.write(self.struct.pack(*data))

    def close(self):
        self.file.close()


Record = namedtuple('Record', 'id name date value')


class RecordStruct(object):
    
    DATE_FORMAT = '%Y%m%d'

    def __init__(self, type_=tuple):
        self.struct = Struct('<I 20s 8s d')
        self.size = self.struct.size
        self.type_ = type_

    def pack(self, id_, name, date, value):
        return self.struct.pack(
            id_, name, format(date, self.DATE_FORMAT), value
        )

    def unpack(self, data):
        id_, name, date, value = self.struct.unpack(data)
        return self.type_(
            id_,
            name.rstrip('\0'),
            DateTime.strptime(date, self.DATE_FORMAT),
            value
        )


def create_records(count=100):
    ids = range(count)
    shuffle(ids)
    return [
        Record(
            id_,
            'Test {0}'.format(id_),
            Date(randint(1900, 2014), randint(1, 12), randint(1, 28)),
            random() * 1000
        )
        for id_ in ids
    ]


def struct_test(filename):
    records = create_records()
    with RecordFile(filename, RecordStruct(Record)) as record_file:
        print(len(record_file))
        for record in records:
            record_file.write_record(record)
        print(len(record_file))
        print(record_file[0])
        for record in record_file:
            print(
                '#{0.id}\n'
                '  {0.name:>20} = {0.value:-7.3f} @ {0.date:%Y-%m-%d}\n'
                '----'.format(record)
            )
        record_file[42] = Record(4711, 'The Answer', Date.today(), 42.23)
        print(record_file[-1])
        print(record_file[42])


def main():
    filename = 'test.dat'
    try:
        os.remove(filename)
    except OSError:
        pass
    struct_test(filename)


if __name__ == '__main__':
    main()
So sieht die Datei als Hexdump aus (also der Anfang, ganz wäre etwas lang :-)):

Code: Alles auswählen

bj@god:~$ ls -l test.dat
-rw-rw-r-- 1 bj bj 4000 Sep  1 20:55 test.dat
bj@god:~$ hd test.dat | head
00000000  11 00 00 00 54 65 73 74  20 31 37 00 00 00 00 00  |....Test 17.....|
00000010  00 00 00 00 00 00 00 00  31 39 36 39 30 31 31 32  |........19690112|
00000020  72 0e a6 32 f4 b5 7e 40  06 00 00 00 54 65 73 74  |r..2..~@....Test|
00000030  20 36 00 00 00 00 00 00  00 00 00 00 00 00 00 00  | 6..............|
00000040  31 39 34 33 30 32 30 39  c1 cd fd 68 51 35 8d 40  |19430209...hQ5.@|
00000050  46 00 00 00 54 65 73 74  20 37 30 00 00 00 00 00  |F...Test 70.....|
00000060  00 00 00 00 00 00 00 00  31 39 34 30 31 32 31 39  |........19401219|
00000070  58 cb ab d8 8a 88 86 40  3e 00 00 00 54 65 73 74  |X......@>...Test|
00000080  20 36 32 00 00 00 00 00  00 00 00 00 00 00 00 00  | 62.............|
00000090  31 39 35 32 30 31 32 38  d9 68 20 fe e3 e8 8c 40  |19520128.h ....@|
Antworten