Variable mit fester Klasse erzeugen

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
Papp Nase
User
Beiträge: 139
Registriert: Dienstag 11. März 2014, 15:12

Hallo,

ich benutze zum Programmieren das Programm Komodo Edit. Dieses Programm hat eine schöne Funktion - es ergänzt mir während des Textschreibens in Minifenstern mögliche Eingräge.

Code: Alles auswählen

# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
# class database_init                                                                             *
# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
class database_init:
    def __init__(self):
        self.host          = ""
        self.db            = ""
        self.user          = ""
        self.passwd        = ""
        self.table         = ""
        self.table_entries = []
        self.error         = ""
Mache ich also:

Code: Alles auswählen

Hexentabelle = database_init ()
und tippe dann ein

Code: Alles auswählen

Hexentabelle.
dann werden mir nach dem Punkttippen alle Variablen angezeigt, die ich dieser Variablen zuordnete (host, db, user ...).

Mache ich jetzt eine Funktion:

Code: Alles auswählen

# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
# * Daten in Datenbank schreiben,                                                                 *
# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
def send_records_to_database (database, records):
    # Datenbank initialisieren
    
    db = MySQLdb.connect (
        host    = database.host,
        db      = database.db,
        user    = database.user,
        passwd  = database.passwd
    ) # Datenbank Init Ende#
...

Tippe ich in dieser Funktion jetzt ein database. - dann kommt nicht dieses schöne kleine Anzeigenfenster. Die Variable database ist aber vom Typ, den ich mit database_init () erzeuge und so von einem anderen Programmteil auch als Argument übergeben wird.

Kann ich sagen, dass in der Funktion send_records_to_database (database, records) die Variable database den Typ der Klasse database_init haben soll, damit der Editor weis, welche Zusatzfunktionen, die nach dem Punkt kommen, der Variable zuordnen kann? Und es würde noch einen zweiten Vorteil haben - der Interpreter müsste bei einer genauen Definition merken, wenn ich an anderer Stelle einen falschen Datentyp übermitteln tue.
BlackJack

@Papp Nase: Python ist halt nicht statisch typisiert. Mit Absicht.

Der Name der Klasse ist schlecht gewählt *und* entspricht nicht den Konventionen. Klassen werden in Python `MixedCase` benannt, und andere Werte mit `kleinbuchstaben_und_unterstrichen`. Schlecht gewählt weil Klassen für ”Dinge” stehen und `database_init` mehr nach einer Tätigkeit klingt, also ein Name für eine Funktion oder Methode wäre.

Die Funktion macht auch den Eindruck als würde sie ganz gut als Methode zu der Klasse passen.

Und eventuell solltest Du Dir mal SQLAlchemy anschauen bevor Du am Ende so etwas selber programmierst.
Papp Nase
User
Beiträge: 139
Registriert: Dienstag 11. März 2014, 15:12

BlackJack hat geschrieben: Die Funktion macht auch den Eindruck als würde sie ganz gut als Methode zu der Klasse passen.
Hmm, daran hab ich garnicht gedacht - bin halt noch beginnender Anfänger.

Ists so besser?

Code: Alles auswählen

#!/usr/bin/env python
import MySQLdb

# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
# class DatabaseFunctions                                                                         *
# * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
class DatabaseFunctions:
    def __init__(self):
        self.host           = ""
        self.db             = ""
        self.user           = ""
        self.passwd         = ""
        self.table          = ""
        self.column_entries = []
        self.error          = ""

    # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
    # * Funktion create_execute_command ()                                                            *
    # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
    #
    # Diese Funktion erstellt das SQL-Execeute-Kommando
    
    def create_execute_command (self):
        sql_command_placeholder = '%s'
        sql_command = "INSERT INTO {0} ({1}) VALUES ({2})".format (
            self.table,
            ', '.join(self.column_entries),
            ', '.join([sql_command_placeholder] * len(self.column_entries))
        )
        return (sql_command)

    # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
    # * Funktion send_records_to_database                                                             *
    # * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
    #
    # Diese Funktion sendet die Daten an die Datenbank
    
    def send_records_to_database (self, records):
        # Datenbank initialisieren
        db = MySQLdb.connect (
            host    = self.host,
            db      = self.db,
            user    = self.user,
            passwd  = self.passwd
        ) # Datenbank Init Ende#
            
        execute_command = self.create_execute_command ()
        print execute_command
        #try:
        #    cursor = db.cursor ()
        #    cursor.executemany (execute_command, records)
        #    db.commit()
        #    cursor.close()
        #except:
        #    print "error while sending data to database"
        db.close()


records = [[1,2,3], [4,5,6]]

lalala = DatabaseFunctions ()
lalala.table = "Hexentable"
lalala.column_entries = ["ABC", "Besen", "Hexenbesen"]
lalala.send_records_to_database (records)
Papp Nase
User
Beiträge: 139
Registriert: Dienstag 11. März 2014, 15:12

Ich habe mir grade eine Frage gestellt - das Execute-Kommando in meinem Beispiel muss nur einmal erzeugt werden, wenn der Wert self.column_entries gesetzt wurde (und natuerlich self.table gesetzt ist). Weil würde ich jetzt z.B. 100x die Funktion create_execute_command () aufrufen und würde in dieser Funktion jedes Mal das Execute-Kommando erstellt, dann wird dafür 100x Rechenleistung verbraten, die ich so sparen könnte.

Ich habe mir dazu eine zweite Funktion ausgedacht:

Code: Alles auswählen

    def create_execute_command_2 (self):
        sql_command_placeholder = '%s'
        self.sql_execute_command = "INSERT INTO {0} ({1}) VALUES ({2})".format (
            self.table,
            ', '.join(self.column_entries),
            ', '.join([sql_command_placeholder] * len(self.column_entries))
        )
Unten hab ich das Programm so verändert:

Code: Alles auswählen

records = [[1,2,3], [4,5,6]]
    
lalala = DatabaseFunctions ()
lalala.table = "Hexentable"
lalala.column_entries = ["ABC", "Besen", "Hexenbesen"]
---> lalala.create_execute_command_2 ()
lalala.send_records_to_database (records)
Oben so:

Code: Alles auswählen

class DatabaseFunctions:
    def __init__(self):
        self.host           = ""
        self.db             = ""
        self.user           = ""
        self.passwd         = ""
        self.table          = ""
        self.column_entries = []
        self.error          = ""
--->        self.sql_execute_command = ""
Und in send_records_to_database

Code: Alles auswählen

        execute_command = self.create_execute_command ()
        print execute_command
        
 -->       if self.sql_execute_command == "":
 -->           print "error no execute command"
 -->       else:
 -->           print self.sql_execute_command
        
        #try:
        #    cursor = db.cursor ()
        #    cursor.executemany (execute_command, records)
        #    db.commit()
Das funktioniert so auch. Ich würde gerne, dass der Wert automatisch gesetzt wird, wenn der Wert self.column_entries verändert wird - sonst könnte ich vergessen, die Funktion create_execute_command () aufzurufen, bevor ich send_records_to_database () aufrufe.

Ich könnte eine Funktion erstellen

Code: Alles auswählen

def add_column_entries_values (self, value):
    self.column_entries = value
    self.create_execute_command_2 ()
Diese Funktion wuerde jetzt, wenn ich den Wert über add_column_entries_values setze, selber aufgerufen.

Kann ich aber auch eine Funktion automatisch aufrufen lassen, wenn ich das mache:

Code: Alles auswählen

lalala.column_entries = ["ABC", "Besen", "Hexenbesen"]
- also wenn ich dierekt auf die Variable der Klasse drauf zugreifen tue, dass dann eine weitere Funktion ausgeführt wird, die sich alleine aus der Veränderung der Variable heraus ergibt?
Zuletzt geändert von Papp Nase am Montag 14. April 2014, 22:11, insgesamt 1-mal geändert.
Sirius3
User
Beiträge: 17737
Registriert: Sonntag 21. Oktober 2012, 17:20

@Papp Nase: was ist der Vorteil davon, dass Du Dir so eine komplizierte Konstuktion ausdenkst?
Papp Nase
User
Beiträge: 139
Registriert: Dienstag 11. März 2014, 15:12

Sirius3 hat geschrieben:@Papp Nase: was ist der Vorteil davon, dass Du Dir so eine komplizierte Konstuktion ausdenkst?
Wenn Du mir sagen könntest, wie ich das etwas einfacher machen könnte, dann würde ich mich darüber freuen.

Meine Vorgabe lautet: In einer Ini-Datei stehen neben den Zugangsdaten zur MySQL-Datenbank auch der Tabellenname und die Spaltennamen. Die Spaltennamen sollen, wenn sie verändert werden, in der Ini-Datei verändert werden. Es sollen auch flexibel in der Datei weitere Spalten in der Tabelle eingefügt werden können, wenn sich auf der Messerfassungsseite die Anzahl der Geräte verändert.
Sirius3
User
Beiträge: 17737
Registriert: Sonntag 21. Oktober 2012, 17:20

@Papp Nase: woher kommen die Spaltennamen? Wer ändert wann welche Spalten und Tabellen? Was soll passieren, wenn Spalten gelöscht oder umbenannt werden?
BlackJack

@Papp Nase: Das sind dann aber letztendlich fast globale Werte. Ich würde da nicht wild die Attribute ändern sondern die einmal festlegen. Das heisst man bräuchte ein Exemplar pro Tabelle. Und die Informationen zur Datenbank könnte/sollte man dann dort in einen eigenen Datentyp heraus ziehen. Und es läuft dann doch immer mehr darauf hinaus das man SQLAlchemy nachprogrammiert.

Beim Datenbankentwurf könnte man auch mal ansetzen. Spalten hinzufügen wenn neue Messgeräte hinzukommen klingt unflexibel. Das sollte eigentlich nicht sein. Ein Datenbankentwurf ist eher etwas festes was im laufenden Betrieb nicht mehr so einfach geändert wird. Ein neues Messgerät sollte man also eigentlich in eine vorhandene Datenbasis integrieren können ohne das Schema anpassen zu müssen.
Papp Nase
User
Beiträge: 139
Registriert: Dienstag 11. März 2014, 15:12

Vielen Dank für Eure Antworten.
BlackJack hat geschrieben:@Papp Nase: Das sind dann aber letztendlich fast globale Werte. Ich würde da nicht wild die Attribute ändern sondern die einmal festlegen. Das heisst man bräuchte ein Exemplar pro Tabelle. Und die Informationen zur Datenbank könnte/sollte man dann dort in einen eigenen Datentyp heraus ziehen. Und es läuft dann doch immer mehr darauf hinaus das man SQLAlchemy nachprogrammiert.
Ich schaue mir das SQLAlchemy-Programm mal an.

Das Problem ist nur - ich muss doch erstmal klein anfangen. Ich komme mir grade vor wie in einer Bücherei, wo grade alle Bücher auf mich herabregnen. Natürlich kann man jetzt auch wieder auf der anderen Seite anfangen und am Datenbankentwurf etwas ändern. Aber dort bin ich auch noch am Experimentieren und Lernen. Nur - ich muss - damit es nicht zu viel wird - erst einmal eine Seite konstant halten und deshalb verändere ich am Datenbankentwurf zunächst nichts. Im Netzwerk hängen aber auch mehrere MySQL-Server mit unterschiedlichen Systemen, mal mit Debian, mal mit Windows, mal mit ReadHat. Ich habe mich deshalb auch für Python entschieden, weil ich so nicht wie in C für jedes System spezielle Anpassungen machen muss, hier bei Python muss ich lediglich bei den Pfaden aufpassen, ob ich in Windows oder unter Linux bin. Und über die Variante mit der Ini-Datei kann ich schnell im Experimentierstadium die Namen einfach verändern, ohne, dass ich im Quellcode als Variablen verändern muss. Außerdem kann ich mir auch ein kleines Tool schreiben, mit dem ich die Ini-Datei bearbeite. Und die Tabellennamen ändern sich immer dann, wenn ich neue Tabellen mit Einträgen erstelle. Ich habe auch etwas Angst, mit zu vielen unterschiedlichen Programmversionen. Wenn ich jetzt das gleiche Programm mal unter Debian teste, dann mal unter Windows - ich habe mir so ein Schema eingerichtet, dass ich das Programm nur an einer Stelle verändere, damit es mir nicht passiert, dass ich aus versehen mal ein Programm auf Debian verändere, dann aber unter Windows eine alte Version verwende. Ich kopiere also immer nur die Dateien zwischen den Verzeichnissen hin- und her, die Ini-Datei bleibt aber in jedem System so stehen, und so brauch ich nur die .PY-Dateien hin- und herzuschaufeln.

Wenn wir diese Diskussionen jetzt mal ausklammern - und auch wenn man sagen könnte, dass man SQLAlchemie nachprogrammieren würde - mich interessiert hier jetzt einfach nochmal - sieht das zweite Programm in meinem Beitrag von 21:59 jetzt optisch in Sachen Konventionen besser aus oder kannst Du mir da noch einen Tipp geben, wo ich nachbessern sollte? Man muss es auch als Einstieg betrachten. Wenn man mehr Erfahrungen hat, dann programmiert man anders, als zu Beginn. Wenn ich also jetzt etwas Funktionierendes hinbekomme und setze mich dann zu einem späteren Zeitpunkt nochmal neu hin und entwickele es nochmal neu, dann kann ich dann auf einen größeren Erfahrungsschatz zurückgreifen, doch soweit bin ich doch jetzt noch nicht, deshalb möchte ich jetzt erstmal gerne meinen kleinen eigenen Entwurf beibehalten. Das Programm funktioniert, es schreibt die Daten sauber in die Datenbank, es funktioniert, dass ich mit den Daten in der Ini-Datei spielen kann, die gut verändern kann und der Execute-Befehl angepasst wird, ich habe jetzt gelernt, was eine SQL-Injection ist und wie man sie vermeidet, lauter kleine Bausteine, die ich Dank Eurer Hilfe ih mich aufnehme. Nur - ich muss es dosiert machen und ich werde mich auch noch mit der SQL-Alchemie befassen, wo Du es mir genannt hast.
BlackJack

@Papp Nase: Wenn Du das mit dem Datenbankentwurf so lässt, dann löst Du jetzt aber Probleme, die eigentlich gar nicht gelöst werden müssen. Ein DB-Entwurf ist IMHO wichtiger, denn wie gesagt, Tabellen und Spalten legt man in der Regel ”für die Ewigkeit” fest, während Programmcode deutlich öfter und leichter umgeschrieben wird.

Wenn man erst einmal Daten in einem Schema gespeichert hat, ziehen Änderungen daran Aufwand für das DBMS nach sich und man läuft Gefahr das Programme nicht auf die geänderte Struktur vorbereitet sind. Das was Du gerade durch die Konfigurationsdatei auszugleichen versuchst.

Bei Python vs. C ist die Plattformunabhängigkeit eigentlich kein grosser Entscheidungsfaktor, denn mit C kann man auch plattformunabhängig programmieren. Es ist bloss mehrere Grössenordungen aufwändiger etwas in C zu programmieren was nicht hardwarenah sein muss, als das in Python zu tun.

Zu den Programmversionen würde ich dringend zu einer (verteilten) Versionsverwaltung raten. Ich persönlich mag Mercurial, Git dürfte allerdings verbreiteter sein. Quelltext auf mehreren Rechnern zu entwickeln und immer hin und her zu kopieren ist unsicher. Da ist es nur eine Frage der Zeit bis die Anfangen sich auseinander zu entwickeln und man irgendwann Änderungen aus versehen mit einer älteren Version überschreibt. Mit einer Versionsverwaltung kann man deutlich entspannter Änderungen auf den verschiedenen Rechnern vornehmen ohne Angst zu haben, dass man davon etwas verliert wenn man beim Kopieren nicht aufpasst.

Zum Quelltext: `DatabaseFunctions` ist immer noch nicht gut als Name. Wir haben eine Klasse, also sind das keine Funktionen sondern Methoden, und das braucht man nicht extra im Namen erwähnen, denn das Klassen Methoden haben ist klar ohne das man das extra erwähnen muss. Bleibt `Database`. Das trifft den Inhalt der Klasse aber nicht wirklich weil so ein Objekt nicht die Datenbank repräsentiert, sondern zu einem gegebenen Zeitpunkt nur *eine* Tabelle aus der Datenbank. Die beiden Änderungen die ich daran machen würde habe ich ja schon erwähnt, das in zwei Datentypen auftrennen, einen der tatsächlich nur die Datenbank kapselt und einen für Tabellen. Also das was bei SQLAlchemy `Engine` und `Table` sind. Wobei ich zumindest bis jetzt noch nichts an dem Code sehe den man für ein `Database`-Objekt braucht was noch nicht durch das Objekt abgedeckt wird, welches man von `MySQLdb.connect()` bekommt. Es sei denn man will tatsächlich jedes mal eine Verbindung zur DB aufbauen und nach dem Einfügen der Daten wieder abbauen.

Klassen die sonst von nichts erben sollte man in Python 2 von `object` ableiten, denn sonst hat man keine „new style”-Klasse und ein paar Sachen funktionieren nicht so wie in der Dokumentation beschrieben.

Die Kommentarteile mit den Sternchen würde ich weglassen. Solange der Sternchenrahmen nicht vom Editor (halb)automatisch bearbeitet werden kann ist das nur unnötige Arbeit, und man ist auf diesen Editor festgelegt, wenn man das nicht von Hand anpassen möchte wenn man mal einen anderen benutzen muss. Und der Textinhalt ist überflüssig. In einem Kommentar den Namen der Klasse oder Funktion zu schreiben die gleich danach definiert wird, bringt dem Leser keinen Mehrwert. Dem Autor aber Mehrarbeit wenn der Name mal geändert werden muss, denn dann muss man immer daran denken auch den Namen im Kommentar zu ändern, oder man hat Kommentare die nicht mehr zum Code passen, was in den meisten Fällen schlimmer ist als gar kein Kommentar.

Der Kommentarteil unter den Sternchen wäre besser als Docstring platziert. Wobei ich die beiden aus dem Beitrag auch überflüssig finde. Bei einer Funktion mit dem Namen `send_records_to_database()` zu kommentieren „Diese Funktion sendet die Daten an die Datenbank” bringt auch wieder keinen Mehrwert.

Es ist zwar auskommentiert, aber bei einem ``try``/``except`` *alle* Ausnahmen zu behandeln in dem ein allgemeiner Text ausgegeben wird, das ein Fehler aufgetreten ist und dann einfach so weiter zu machen als wenn nichts passiert wäre, ist keine vernünftige Fehlerbehandlung. Wenn man nicht weiss welche Ausnahmen man bekommen kann, und was man dann konkret damit macht, sollte man in der Regel die Ausnahme auch nicht behandeln.

Dazu irgendwie verwandt: Wenn man einen Fehler feststellt, sollte man den nicht nur per ``print`` mitteilen und dann weitermachen, sondern eine Ausnahme auslösen und dem Aufrufer (oder dessen Aufrufer, oder…) die Möglichkeit geben darauf zu reagieren. Das bezieht sich auf die Stelle wo `self.sql_execute_command` auf Inhalt geprüft wird.

Bei den Objekten die geschlossen werden können/müssen kann man auch ``with``, gegebenenfalls zusammen mit `contextlib.closing()`, verwenden um sicherzustellen, dass die auf jeden Fall geschlossen werden, auch wenn der Codeblock durch eine Ausnahme, ``break``, ``continue``, oder ein ``return`` verlassen würde.
Papp Nase
User
Beiträge: 139
Registriert: Dienstag 11. März 2014, 15:12

Vielen Dank für Deine Hilfe, ich habe mich sehr drüber gefreut. Ich brauche aber erstmal eine Weile, bis ich das alles verstanden habe, damit hab ich erstmal ein paar Tage dran zu tun.

Vielen Dank für den Tipp mit der Versionsverwaltung, ich werde mir die Programme mal anschauen. Bis jetzt mache ich es so, dass ich ein ftp-Verzeichnis im Netzwerk habe, auf dem ich entwickele und dann wird die Datei in das Programmverezichnis auf dem jeweiligen Betriebssystem kopiert - ich kopiere also nichts vom Betriebssystem zurück - also nur ein Speicherort für die Datei an einem Ort und nicht an zwei Orten. Bei der Versionsverwaltung stellt sich die Frage, ob sie auf allen Betriebssystemen komfortabel läuft, aber das lässt sich ja herausfinden.
Antworten