Verbindung zu SQLITE in Funktion verwenden

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MariaDB/MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
Antworten
bfm
User
Beiträge: 88
Registriert: Donnerstag 14. März 2013, 09:42

Hallo zusammen,

ich bringe mir derzeit Python 3 im Selbststudium bei. Ich bin jetzt mal soweit vorgedrungen, dass ich meine 22 Jahre alte "Buchhaltung" in QBasic in Python umsetzen will. Programmieren lernt man ja bekanntlich, wenn man es praktisch anwendet.

Ich habe folgendes Programm:

Code: Alles auswählen

import sqlite3
import fibuopausgl

#Verbindung zur Datenbank und Cursor erstellen
fibucon = sqlite3.connect('fibu-2013-06-12.sqlite')
cursorfibu = fibucon.cursor()

#Anwender gibt die KontoNr ein, alternativ übergibt das zukünftige Programm, das die Funktion OP-Ausgleich verwendet, die Kontonummer
kontonrsu = input('KontoNr: ')

#Aufruf OP-Ausgleich Variante 1
print(fibuopausgl.opausgl1(kontonrsu))

#Aufruf OP-Ausgleich Variante 2
print(fibuopausgl.opausgl2(kontonrsu))


fibucon.close()
print('Ende')
quit()


und folgendes Modul, das die Funktion OP-Ausgleich in Variante 1 und Variante 2 enthält. Ich habe die Funktionen in ein Modul ausgelagert, weil ich die Funktionen ja evtl. später noch in anderen Programmen verwenden möchte.

Code: Alles auswählen

import sqlite3

#Defininition OP-Ausgleich Variante 1
def opausgl1(kontonr):

#Verbindung zur Datenbank und Cursor erstellen
#zum Testen deaktiviert
#    fibucon = sqlite3.connect('fibu-2013-06-12.sqlite')
#    cursorfibu = fibucon.cursor()

#Daten aus der DB beschaffen
    cursorfibu.execute("""SELECT satznr, kto, gegkto, datum, strftime('%Y-%m', datum), opnr, opausnr, betrag, sh FROM buchungen where kto=? and opausnr=0 order by opnr, datum""", (kontonr,))
    ergebnis = cursorfibu.fetchall()
#Verarbeitung der Daten noch nicht programmiert
#return von ergebnis, damit man sieht, dass überhaupt was passiert ist
    return ergebnis

#Definition OP-Ausgleich Variante 2
def opausgl2(kontonr):
    ergebnis = 'opausgl2'
    return ergebnis


Problem:
So wie das Programm jetzt ist kommt spätetestens bei der Select-Anweisung ein Fehler, dass das der "gobal name 'fibucon' is not defined".
Nachdem ich die Verbindung zur DB und den Cursor im Hauptprogramm definiert habe, müsste ich doch theoretisch in der Funktion "opausgl1" darauf zugreifen können oder bin ich da jetzt auf dem Holzweg?

Wenn ich die derzeit im Modul "fibuopausgl" auskommentierten Zeilen zur Verbindung und Cursorerstellung drin lassen, dann funktioniert das Programm wunschgemäß und gibt mir bei "'opausgl1" eine Liste mit den gefunden Datensätzen zurück.
Theoretisch könnte ich das ja schon machen, aber da ist das Modul "fibuopausgl" wieder an eine bestimmte Datenbank gebunden. Der Name der Datenbank kann sich ja auch ändern.
Wo liegt mein Denkfehler? Geht das überhaupt so, wie ich mir das vorstelle?
Das Hauptprogramm baut die Verbindung zur DB auf und ich nutze sie in den Untermodulen/Funktionen.

Danke für eure Hilfe!

Harald

PS: wo wäre es eigentlich angebracht die try/expect-Anweisungen unterzubringen. Die Fehlerbehandlung sollte ich ja irgendwann auch noch einbauen :wink:
BlackJack

@bfm: So besonders ist das „Hauptprogramm” nicht. Namen auf Modulebene sind nur in dem jeweiligen Modul bekannt und nicht auf magische Weise in jedem Modul verfügbar.

Du bekommst die Datenbankverbindung am saubersten in eine Funktion, indem Du sie als Argument übergibst. So als Faustregel: (Nicht konstante) Werte betreten Funktionen als Argumente und verlassen sie als Rückgabewerte. Und auf Modulebene gehören nur die Definition von Konstanten, also Konstante Werte, Funktionen, und Klassen. Veränderbare Werte und Programmlogik sollten dort nicht direkt stehen, weil man sich dann leicht unnötige Abhängigkeiten zwischen Funktionen und dem Kram auf Modulebene schafft. Ein übliches Idiom dafür ist folgendes:

Code: Alles auswählen

def main():
    # Hier kommt das Hauptprogramm rein.


if __name__ == '__main__':
    main()
Das hat zusätzlich den Vorteil dass man das Modul importieren kann ohne das `main()` ausgeführt wird, weil die Bedingung nur wahr ist, wenn man das Modul direkt als Programm ausführt.

Die Namensgebung ist ziemlich grausam. `fibuopausgl` oder `kontonrsu` klingt nach Namen aus Fantasy-Romanen, aber nicht nach etwas was leicht lesbar und verständlich Werte benennt. Man sollte keine Abkürzungen verwenden die nicht allgemein bekannt sind. Oder zumindest domänenspezifisch. Also FiBu und OP sind vielleicht noch in Ordnung, aber nicht das dann so aneinander zu klatschen das man nicht leicht sehen kann wo die Grenzen zwischen den Worten sind. Also `fibu_op_ausgleich` statt `fibuopausgl`. Und bei `kontonrsu` komme ich einfach nicht drauf. `konto_nummer` ist noch klar, aber was soll das `su` am Ende?

Der `quit()`-Aufruf ist unnötig und die Funktion ist auch nicht garantiert vorhanden. Die dazu da eine interaktive Shell zu beenden und muss bei anderen Python-Implementierungen nicht vorhanden sein.

Im anderen Modul solltest Du die Kommentare entsprechend der Code-Struktur einrücken. ein Merkmal von Python ist ja, dass man die Code-Struktur eigentlich immer an der Quelltextformatierung ablesen kann. Was Du mit den Kommentaren dort ziemlich untergräbst.

``try``/``except`` wendet man dort an wo man eine oder mehrere konkrete Ausnahmen erwartet *und* sie auch *sinnvoll* behandeln kann.

Grundsätzlich sollte man IMHO nicht versuchen BASIC-Programme 1:1 in Python umzusetzen, sondern aus dem alten Programm die Anforderungen ablesen und damit dann ein Python-Programm entwerfen. Also zumindest wenn man Python lernen/schreiben will. Wenn es nur darum geht ein BASIC-Programm schnell auf Python zu portieren, kann man natürlich auch erst einmal eine „dreckige” Variante schreiben.

Falls es darum geht QBasic-Programme auf aktuellen Rechnern und anderen Systemen als DOS zum laufen zu bringen, lohnt sich übrigens ein Blick auf FreeBASIC. Das habe ich schon erfolgreich dazu eingesetzt.

Edit: Wie so etwas in Python (2) aussehen könnte:

Code: Alles auswählen

#!/usr/bin/env python
from __future__ import print_function
import sqlite3
from contextlib import closing
from .ausgleich_rechungen import op_ausgleich_a, op_ausgleich_b


class Buchung(object):
    def __init__(
        self, satz_nr, konto, gegenkonto, datum, op_nr, op_aus_nr, betrag, sh
    ):
        self.satz_nr = satz_nr
        self.konto = konto
        self.gegenkonto = gegenkonto
        self.datum = datum
        self.op_nr = op_nr
        self.op_aus_nr = op_aus_nr
        self.betrag = betrag
        self.sh = sh    # ?
        

class Konto(object):
    def __init__(self, connection, kontonummer):
        self.connection = connection
        self.kontonummer = kontonummer

    def get_buchungen(self):
        with closing(self.connection.cursor()) as cursor:
            cursor.execute(
                "SELECT satznr, gegkto, datum, opnr, betrag, sh"
                " FROM buchungen"
                " WHERE kto=? AND opausnr=0"
                " ORDER BY opnr, datum",
                (self.kontonummer,)
            )
            result = [
                Buchung(
                    satz_nr,
                    self,
                    Konto(self.connection, gegenkontonummer),
                    datum,
                    op_nr,
                    0,
                    betrag,
                    sh
                )
                for satz_nr, gegenkontonummer, datum, op_nr, betrag, sh
                in cursor.fetchall()
            ]
        return result



class Konten(object):
    def __init__(self, connection):
        self.connection = connection

    def __getitem__(self, kontonummer):
        return Konto(self.connection, kontonummer)


def main():
    with sqlite3.connect('fibu.sqlite') as connection:
        konten = Konten(connection)
        kontonummer = raw_input('Kontonummer: ')
        konto = konten[kontonummer]
        print(op_ausgleich_a(konto))
        print(op_ausgleich_b(konto))


if __name__ == '__main__':
    main()
bfm
User
Beiträge: 88
Registriert: Donnerstag 14. März 2013, 09:42

Hallo,

danke für die Hinweise! Dann werde ich mich mal dranmachen und die umsetzen. Dann bin ich am Wochenende schon beschäftigt ;-)

Ich habe hier zwar eine 900-Seite-Schwarte, aber die gibt halt auch nur einen Überblick was Python kann und was möglich wäre. Vieles sucht man sich dann halt auch im Internet zusammen. Und wenn es dann noch auf Englisch ist......

Selbststudium hat halt auch den gravierenden Nachteil, dass man sich vieles zusammen suchen muss. In einem Kurs kann ich einen von den 10 Mitstreiten fragen oder der Prof/Lehrer gibt einem einen Hinweis auf den Lösungsweg oder greift ein, wenn sich etwas in eine ganz falsche Richtung entwickelt. Im Selbststudium läuft vieles dann auch mit Versuch und Irrtum. Oft ist man dann nach zwei Stunden einfach froh, dass es endlich funktioniert.

Die alten Basic-Programme am Leben zu erhalten, ist glaub wenig sinnvoll. Die Programm haben teilweise noch Zeilennummer und den "netten" GOTO-Befehl. Den Dateizugriff mit einer for/next-Schleife noch selber programmieren, wenn es heute ein einfaches "select" tut? Zahlen ins Strings umwandeln bevor man sie in die Datei schreiben kann? :roll:
Die heutigen Programmiersprachen bieten da ganz andere Möglichkeiten, die einem das Programmieren einiges einfacher machen. Und wenn es die gibt dann sollte man die auch nutzen, finde ich.

Also dann mache ich mich mal an die Arbeit!

lg
Harald
bfm
User
Beiträge: 88
Registriert: Donnerstag 14. März 2013, 09:42

super! Danke! Ich habe jetzt erst die Ergänzung gesehen.

Ein gutes Beispiel, mal die OOP anhand einer für mich bekannten Aufgabe nachvollziehen zu können. Das Kapitel mit OOP habe ich durchgelesen und ich könnte auch mit etwas Nachlesen eine eigene Klasse basteln, aber ich kann noch nicht so richtig nachvollziehen wieso OOP. Derzeit hat das eher den Status: wieso einfach, es geht doch auch kompliziert. Der Vorteil von OOP gegenüber meiner bisherigen Programmierweise ist bei mir logisch noch nicht so richtig im Kopf angekommen.

lg
BlackJack

@bfm: Also ich finde es *ohne* OOP komplizierter. Du bist ja selbst in das Problem reingelaufen wie man mit der Datenbankverbindung umgehen soll. Und letztendlich ist das ja OOP auf der Ebene von QBasic's ``TYPE``/``END TYPE`` und ein paar dazugehörigen Funktionen. Oder umgekehrt: Verbundtypen in QBasic lassen sich ziemlich einfach auf Klassen übertragen, denn die Klasse ist in Python das Sprachmittel um „zusammengesetzte Datentypen” zu erstellen. Nur dass man in OOP-Sprachen nicht nur die Daten, sondern auch noch die darauf operierenden Funktionen mit in den Typ steckt.

Dein Ansatz hat übrigens auch noch das Problem, dass die Datenbank in den OP-Ausgleichsfunktionen eigentlich nichts zu suchen hat. Die Funktionen sollten etwas mit den Konten machen, ohne wissen zu müssen wie die letztendlich gespeichert werden. So wie es jetzt aussieht endest Du mit Funktionen die alle selber die Datenbank abfragen und schreiben, also am Ende viel nahezu identischen Quelltext enthalten werden.
bfm
User
Beiträge: 88
Registriert: Donnerstag 14. März 2013, 09:42

BlackJack hat geschrieben: Dein Ansatz hat übrigens auch noch das Problem, dass die Datenbank in den OP-Ausgleichsfunktionen eigentlich nichts zu suchen hat. Die Funktionen sollten etwas mit den Konten machen, ohne wissen zu müssen wie die letztendlich gespeichert werden. So wie es jetzt aussieht endest Du mit Funktionen die alle selber die Datenbank abfragen und schreiben, also am Ende viel nahezu identischen Quelltext enthalten werden.
Da wäre es fast sinnvoll alle DB-Funktionen in eine eigene Klasse zu schreiben. Es sind ja eigentlich immer wieder die gleichen DB-Funktionen, die später irgendwo im "Hauptprogramm" aufgerufen werden. zB Salden neu ermitteln, Salden abfragen, Buchungssätze abfragen/einfügen, Kontostammdaten abfragen/einfügen......

Bei SAP ist das ja glaub auch so, dass man da in den eigenen Programmen sogenannte Funktionsbausteine verwenden kann. Diese sind von SAP bereits programmiert und man muss sich selber nicht mehr die Mühe machen und sich aufwändig selber um die Datenbeschaffung/Datenänderung in der DB kümmern.

lg
BlackJack

@bfm: Man könnte es auch auf mehere Klassen verteilen, denn eine „god class” die alles mit der Datenbank macht, klingt nach keinem guten Entwurf. Die Frage ist auch ob man das unbedingt von der Datenbank ausgehend entwerfen muss. Man kann den Entwurf auch von der Programmlogik her aufziehen und schauen das es weitestgehend egal ist ob das in CSV-Dateien, einer relationalen Datenbank, oder vielleicht in einer NoSQL-Datenbank gespeichert wird. Und wenn relational, dann würde ich für eine praktische Umsetzung auch ein ORM wie das von SQLAlchemy dem selbst schreiben von SQL vorziehen.
Antworten