Adressdatenbank

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Mittwoch 12. Juli 2006, 14:44

CrackPod hat geschrieben:TypeError: tuple indices must be integers
Hi CrackPod!

Damit du die Rückgabe wie ein Dictionary verwenden kannst, musst du, vor dem Erstellen des Cursors, eine spezielle RowFactory zuweisen.

Code: Alles auswählen

self.conn.row_factory = sqlite3.Row

Code: Alles auswählen

[...]
    #----------------------------------------------------------------------
    def __init__(self, dbfilename = DBFILENAME):
        """
        :param dbfilename: Name und Pfad zur Datenbankdatei. Existiert
            sie nicht, wird sie automatisch erstellt.
        """

        # Verbindung herstellen
        self.conn = sqlite3.connect(dbfilename)

        # Abfrageergebnisse wie Dictionarys verwenden lassen.
        # Diese Zeile muss **vor** dem Erstellen des Cursors stehen.
        self.conn.row_factory = sqlite3.Row

        # Cursor erstellen
        self.cur = self.conn.cursor()

[...]

    def getall (self):
        '''Rückgabe von allen Kontakten des Telefonbuchs'''

        sql = '''SELECT
                    id,
                    vorname,
                    zuname,
                    [...]
                FROM
                    addresses'''

        self.cur.execute(sql)

        return self.cur.fetchall()


adr = Adress('./daten/address.sqldb')
adr.install()
adr.add('Max','Maximilian','Musterman','Herr','Musterstr 12',
'1234865','Musterstadt','Deutschland','004912326548', None,'01701234586')

addresses = adr.getall()
for address in addresses:
    print "vorname:", address["vorname"]
    print "zuname:", address["zuname"]
    print
lg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
CrackPod
User
Beiträge: 205
Registriert: Freitag 30. Juni 2006, 12:56

Mittwoch 12. Juli 2006, 18:58

Wenn ich jetz mit sqlite_master arbeite, dann bekomm ich folgendes:

Code: Alles auswählen

        sql = """SELECT name FROM sqlite_master WHERE type='table'"""

        self.cur.execute(sql)
        while True:
            row = self.cur.fetchone()
            print '-'*15
            print row
            print '-'*15
            if row == 'addresses':
                created = True
                break
            if row == None:
                break
---------------
(u'addresses',)
---------------
Wie kann ich jetz auslesen addresses auslesen? Mit dem if Teil oben geht schonmal nicht.
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Mittwoch 12. Juli 2006, 20:42

CrackPod hat geschrieben:(u'addresses',)
Hi CrackPod!

Ich habe die Methode create_dbstructure() komplett umgebaut. Bitte sieh sie dir an. Da drinnen zeige ich auf, wie man prüfen kann ob eine Tabelle oder ein Index bereits existieren.

Code: Alles auswählen

(u'addresses',)
Das ist ein Tupel. Dieser verhält sich ähnlich wie eine Liste. Der große Unterschied ist der, dass ein Tupel sich nich verändern lässt. Dadurch ist er aber auch schneller als eine Liste.

Code: Alles auswählen

>>> t = (u'addresses',)
>>> print t[0]
addresses
>>>
In unserem speziellen Fall allerdings, wurde dieser Tupel durch den Code ``self.conn.row_factory = sqlite3.Row`` in ein Objekt vom Type **pysqlite2.dbapi2.Row** "umgewandelt". Deshalb kann man darauf nicht nur mit einem Index, sondern auch mit einem Schlüssel, wie bei einem Dictionary zugreifen.

Code: Alles auswählen

print row[0]
# oder
print row["name"]
lg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
CrackPod
User
Beiträge: 205
Registriert: Freitag 30. Juni 2006, 12:56

Donnerstag 13. Juli 2006, 14:08

So bin jetz größtenteils fertig.
Jetz muss halt noch "Interaktivität" rein und fertig is der Spaß danach wird das ausgebaut :wink:
http://nopaste.info/7176b9c5ed.html
Könnt ihr mir sagen, was ich noch verbessern kann/soll?

Danke Greetz
CrackPod
User
Beiträge: 205
Registriert: Freitag 30. Juni 2006, 12:56

Sonntag 16. Juli 2006, 23:05

naja irgendwie wollte mir wohl keiner so richtig auf die Frage antworten. Naja nich so tragisch. Hab jetz mal ein wenig weitergearbeitet um die Interaktivität herzustellen.
Hier is das Skript: http://nopaste.info/d22f2a9a91.html
Problem: Irgendwie bekomme ich Endlosschleifen, wenn ich bei dem Interaktiven Teil als Spalte vorname und als Wert Max eingeben. Auch, wenn ich mit der "Statischen" Klasse suche - mit denselben Daten - , bekomm ich eine Endlosschleife...
Ich weiß nur nich warum.
Bevor ich mit der Klasse `Menu` angefangen habe, hat alles wunderbar funktioniert und ich habe seitdem die Klasse `Adresses` nichmehr angerührt - außer um alles einmal zu kommentieren, aber das macht ja den Code nicht kaputt. Hab ja deswegen trotzdem nix an dem Code, der für den Interpreter wichtig is, geändert.
Kann mir einer Helfen?
Danke schonma im voraus

Greetz
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Montag 17. Juli 2006, 07:55

CrackPod hat geschrieben:Irgendwie bekomme ich Endlosschleifen
Hallo CrackPod!

Du musst deinen Code **debuggen**. Also, an strategisch guten Stellen Haltepunkte setzen und nachverfolgen in welcher Endlosschleife dein Programm stehen bleibt. Schritt für Schritt. Dann musst du deine Schleifen-Abbruchbedingung prüfen. Z.B. den Wert von Variablen abfragen. Daraus kannst du dann normalerweise schließen, warum das Programm in der Schleife stehen bleibt.

Ich verwende zum Debuggen WingIDE. Du kannst aber genauso gut mit dem PDB http://docs.python.org/dev/lib/module-pdb.html oder einer anderen IDE zum Debuggen arbeiten.

http://www.digitalpeers.com/pythondebugger/
http://bashdb.sourceforge.net/pydb/
http://www.thescripts.com/forum/thread22783.html

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

Montag 17. Juli 2006, 09:25

Das Programm ist ziemlich gross um mal eben drüber zu schauen. Und es lässt sich nicht starten weil die `./daten/settings.ini` fehlt. Wobei mir da im Quelltext erstmal aufgefallen ist, das Du Dateien immer mit '+' Modus öffnest, auch wenn Du gar nichts schreiben willst.

Den eigenen Parser für Konfigurationsdateien hättest Du Dir sparen können, so etwas bekommt man schon in der Standardbibliothek: `ConfigParser`.

Nachdem ich die Konfigurationsdatei erzeugt habe und mit den entsprechenden Werten gefüllt habe, scheitere ich an folgender Ausnahme:

Code: Alles auswählen

Traceback (most recent call last):
  File "address.py", line 508, in ?
    main()
  File "address.py", line 489, in main
    adr = Adress(cfgData.getDBPath())
  File "address.py", line 26, in __init__
    self.con.row_factory = sqlite3.Row
AttributeError: 'module' object has no attribute 'Row'
`Row` gibt's bei mir in `pysqlite2.dbapi2` nicht.

Also schnell(?) die Python 2.5b2 übersetzt, da geht's dann. Und eine Endlosschleife bei `vorname` und `Max` kann ich nicht nachvollziehen.

Stilkritik: Es gibt einige Zeilen, die länger als 80 Zeichen lang sind. Ein paar Namen sind nicht PEP008 "tauglich" und nach Kommata und vor und nach Operatoren ist ein Leerzeichen ganz nett. Wo es eher nicht hingehört ist zwischen Name und Parameterliste von Funktionsdefinitionen und das ausrichten von Gleichheitszeichen ist auch keine gute Angewohnheit. Spätestens wenn man mit einer Versionsverwaltung wie CVS oder Subversion arbeitet bringt das Probleme mit sich. Wenn man nämlich Änderungen an einem Namen vornimmt und dann alle Gleichheitszeichen neu ausrichtet, kann man im Diff nicht mehr so schnell sehen, was eigentlich die Änderung war und was nur Kosmetik.

Ausnahmebehandlungen sollten immer eine konkrete Ausnahme behandeln. Beim importieren wäre das also ein `ImportError`.

Auf `__del__()` sollte man sich nicht verlassen. Eine Methode, welche die Verbindung zur Datenbank schliesst und die vom Benutzer aufgerufen werden muss, ist sicherer und deterministischer.

Die Liste mit den ganzen Attributnamen einer Adresse taucht viel zu oft im Programm auf. Alleine in `Adress.add()` wird jeder Name 5 mal wiederholt. Jede Wiederholung birgt die Gefahr von Fipptehlern und macht Änderungen aufwändiger.

Bei `Adress.get()` setzt Du Dich der Gefahr einer SQL-Injection aus. Jemand kann "böse" Suchbegriffe eingeben um beliebiges SQL ausführen zu lassen. Ausserdem ist das Konstrukt mit der ``while`` Schleife eigenartig. Warum speicherst Du Daten auf die über einen fortlaufenden Index zugegriffen wird in einem Dictionary? Eine Liste wäre die "natürlichere" Wahl dafür. Man kann über Cursor-Objekte iterieren, damit lässt sich der wiederholte Aufruf von `fetchone()` sparen. Eine Liste mit Dictionaries für die einzelnen Rows kann man zum Beispiel so bekommen:

Code: Alles auswählen

        def row_to_dict(row):
            return dict((name, row[name]) for name
                        in ('id', 'vorname', 'zuname', 'nachname', 'anrede',
                            'adresse', 'plz', 'ort', 'land', 'privatnummer',
                            'privatfaxnummer', 'mobilnummer', 'bueronummer',
                            'buerofaxnummer', 'email'))
        
        adresses = map(row_to_dict, self.cur)
Warum wird eigentlich `None` zurückgegeben, wenn kein Ergebnis gefunden wurde?

In `Adress.delete()` beim Löschen wäre es sinnvoller gleich über `adresses.values()` zu iterieren statt über die Schlüssel und dann in der Schleife den Wert zu holen. Das wäre ausserdem ein guter Anwendungsfall für `executemany()`.

Das `Adress.getall()` copy'n'paste Quelltext ist, fiel Dir laut Kommentar ja schon selbst auf. `getall()` ist nur ein Sonderfall von der Suche, als solchen sollte man ihn auch behandeln können.

Bei `Adress.out()` iterierst Du wieder über die Schlüssel statt über die Einträge. Diesmal resultiert daraus ganz viel Schreibarbeit weil Du etliche male ``adresses[key]`` tippen musst statt einfach gleich über die Adressen selbst zu iterieren.

Das Telefonbuch zu löschen, indem man die Datei auf die harte Tour "leert" ist ein wenig unelegant. Besser wäre es wohl die Tabelle per SQL zu leeren. Dann braucht man die nicht wieder anzulegen um neue Daten einzutragen.

Der Konfigurationsparser ist ziemlich fragil. In `TCfgP.__init__()` wird wieder eine komische ``while``-Schleife benutzt die man einfach durch iterieren über das Objekt ersetzen kann. In diesem Fall also einfach ``for line in f:``.

`TCfgP.getDBPath()` sollte auf jeden Fall `os.path.join()` für den Dateinamen benutzen. Ich musste meine Datenbank erstmal suchen.

Ein Entwurfsproblem ist das Mischen von Benutzeroberfläche und Logik. Das solltest Du besser trennen.

Sooo, ich hoffe das war erstmal genug. :-)
CrackPod
User
Beiträge: 205
Registriert: Freitag 30. Juni 2006, 12:56

Montag 17. Juli 2006, 17:48

Danke BlackJack. Ich werde mir mal deine Verbesserungsvorschläge zu Herzen nehmen und nach und nach alles ändern.
Ich hab mit dem SQL Code angefangen:

Code: Alles auswählen

#Aus dem Konstruktor:
        #Tupel mit den Spalten der Datenbank
        self.dbCols = ('id', 'vorname', 'zuname', 'nachname', 'anrede', 'adresse', 'plz', 'ort', 'land',
                       'privatnummer', 'privatfaxnummer', 'mobilnummer', 'bueronummer', 'buerofaxnummer', 'email')

#def add():
        sqlCols = ''
        sqlPlazeHolders = ''

        for cols in self.dbCols:
            sqlCols += cols + ','
            sqlPlazeHolders += ':' + cols + ','
        sqlCols = sqlCols[:-1]
        sqlPlazeHolders = sqlPlazeHolders[:-1]

        print sqlCols
        print sqlPlazeHolders

        sql = 'INSERT INTO addresses (%s) VALUES (%s)' % (sqlCols, sqlPlazeHolders)

        print sql

        #Führt den SQL-Befehl aus und ersetzt die Platzhalter (Per Dictionary)
        self.cur.execute(sql,{'vorname':vorname, 'zuname':zuname,
            'nachname':nachname, 'anrede':anrede, 'adresse':adresse, 'plz':plz,
            'ort':ort, 'land':land, 'privatnummer':privatnummer, 'privatfaxnummer':privatfaxnummer,
            'mobilnummer':mobilnummer, 'bueronummer':bueronummer, 'buerofaxnummer':buerofaxnummer, 'email':email})
        self.con.commit()
Spalten:
id,vorname,zuname,nachname,anrede,adresse,plz,ort,land,
privatnummer,privatfaxnummer,mobilnummer,
bueronummer,buerofaxnummer,email

Platzhalter
:id,:vorname,:zuname,:nachname,:anrede,:adresse,
:plz,:ort,:land,:privatnummer,:privatfaxnummer,:mobilnummer,
:bueronummer,:buerofaxnummer,:email

SQL:
INSERT INTO addresses (id,vorname,zuname,nachname,anrede,
adresse,plz,ort,land,privatnummer,privatfaxnummer,
mobilnummer,bueronummer,buerofaxnummer,email)
VALUES (:id,:vorname,:zuname,:nachname,:anrede,
:adresse,:plz,:ort,:land,:privatnummer,:privatfaxnummer,
:mobilnummer,:bueronummer,:buerofaxnummer,:email)

Traceback (most recent call last):
File "/home/gottesworkstation/Python/Adressenverwaltung.py", line 499, in -toplevel-
main()
File "/home/gottesworkstation/Python/Adressenverwaltung.py", line 477, in main
adr.add('Tobias','Helmut','Hoebel','Herr','Togostr 19','81827','Muenchen','Deutschland','08943571732','','01793248225','','','tobsi-h@gmx.de')
File "/home/gottesworkstation/Python/Adressenverwaltung.py", line 116, in add
self.cur.execute(sql,{'vorname':vorname, 'zuname':zuname, 'nachname':nachname, 'anrede':anrede, 'adresse':adresse, 'plz':plz,
ProgrammingError: You did not supply a value for binding 1.
Versteh den Fehler nicht. Das execute hat davor prima funktioniert, der SQL Code stimmt egtl auch. Erkennt ihr den Fehler?

Zu den SQLInjections: Ich weiß nicht, wie ich die umgehen soll, bei search... Andererseits hab ich mir gedacht, dass es egal ist, da es ja eh nur ein Adressbuch ist und man da nich so viel vertrauliche Daten draus gewinnen kann :)

Greetz

EDIT:
Ich habe den Fehler gefunden. Ich hatte keinen Wert für die Spalte ID angegeben.
BlackJack

Montag 17. Juli 2006, 22:13

CrackPod hat geschrieben:

Code: Alles auswählen

#Aus dem Konstruktor:
        #Tupel mit den Spalten der Datenbank
        self.dbCols = ('id', 'vorname', 'zuname', 'nachname', 'anrede', 'adresse', 'plz', 'ort', 'land',
                       'privatnummer', 'privatfaxnummer', 'mobilnummer', 'bueronummer', 'buerofaxnummer', 'email')

#def add():
        sqlCols = ''
        sqlPlazeHolders = ''

        for cols in self.dbCols:
            sqlCols += cols + ','
            sqlPlazeHolders += ':' + cols + ','
        sqlCols = sqlCols[:-1]
        sqlPlazeHolders = sqlPlazeHolders[:-1]
Die sieben letzten Zeilen lassen sich auch in zwei ausdrücken:

Code: Alles auswählen

        sql_cols = ','.join(self.DB_COLS)
        sql_place_holders = ','.join(':' + name for name in self.DB_COLS)
CrackPod
User
Beiträge: 205
Registriert: Freitag 30. Juni 2006, 12:56

Dienstag 18. Juli 2006, 15:30

Hm, stimmt. An join hab ich gar nich gedacht...
Gibts dafür auch ne Kurzversion:

Code: Alles auswählen

sqlCols = ''
        sqlPlazeHolders = ''
        colException = ('id',)
        for col in self.dbCols:
            if col not in colException:
                sqlCols += col + ', '
                sqlPlazeHolders += ':' + col + ', '
        sqlCols = sqlCols[:-2]
        sqlPlazeHolders = sqlPlazeHolders[:-2]

        sql = 'INSERT INTO addresses (%s) VALUES (%s)' % (sqlCols, sqlPlazeHolders)
        print sql
Ich will nur Spalten haben, die ich auch brauch, weswegen ich colException brauch. Da frag ich ab, ob der Wert von dbCols auch in colException steht - wenn ja, wird er nich eingetragen.

Code: Alles auswählen

        def row_to_dict(row): 
             return dict((name, row[name]) for name 
                         in ('id', 'vorname', 'zuname', 'nachname', 'anrede', 
                             'adresse', 'plz', 'ort', 'land', 'privatnummer', 
                             'privatfaxnummer', 'mobilnummer', 'bueronummer', 
                             'buerofaxnummer', 'email')) 
         
         adresses = map(row_to_dict, self.cur)
Kannst du mir bitte die map() Funktion erklären? Irgendwie versteh ich die nich. Aus der Doku werd ich nich schlau.
Am besten gleich die ganze Funktion

Greetz
BlackJack

Mittwoch 19. Juli 2006, 07:28

CrackPod hat geschrieben:Hm, stimmt. An join hab ich gar nich gedacht...
Gibts dafür auch ne Kurzversion:

Code: Alles auswählen

sqlCols = ''
        sqlPlazeHolders = ''
        colException = ('id',)
        for col in self.dbCols:
            if col not in colException:
                sqlCols += col + ', '
                sqlPlazeHolders += ':' + col + ', '
        sqlCols = sqlCols[:-2]
        sqlPlazeHolders = sqlPlazeHolders[:-2]

        sql = 'INSERT INTO addresses (%s) VALUES (%s)' % (sqlCols, sqlPlazeHolders)
        print sql
Ich will nur Spalten haben, die ich auch brauch, weswegen ich colException brauch. Da frag ich ab, ob der Wert von dbCols auch in colException steht - wenn ja, wird er nich eingetragen.
Ermittle vorher einfach die Spalten, die Du brauchst und benutz dann `join()` wie vorher.

Code: Alles auswählen

columns = set(DB_COLS) - set(col_exception)
Durch die `set`\s sind die Namen dann nicht mehr zwingend in der gleichen Reihenfolge wie in den Listen, aber das macht in diesem Fall ja nichts aus.

Code: Alles auswählen

        def row_to_dict(row): 
             return dict((name, row[name]) for name 
                         in ('id', 'vorname', 'zuname', 'nachname', 'anrede', 
                             'adresse', 'plz', 'ort', 'land', 'privatnummer', 
                             'privatfaxnummer', 'mobilnummer', 'bueronummer', 
                             'buerofaxnummer', 'email')) 
         
         adresses = map(row_to_dict, self.cur)
Kannst du mir bitte die map() Funktion erklären? Irgendwie versteh ich die nich. Aus der Doku werd ich nich schlau.
Am besten gleich die ganze Funktion
Die Funktion bekommt eine Funktion und ein oder mehrere iterierbare Objekte und wendet dann die übergebene Funktion auf jedes Element an und speichert das Ergebnis in einer Liste.

Code: Alles auswählen

In [4]: def double(n):
   ...:     return n * 2
   ...:

In [5]: map(double, [1, 2, 23, 42])
Out[5]: [2, 4, 46, 84]
Wenn man die Funktion `map()` selbst implementieren wollte, würde sie ungefähr so aussehen:

Code: Alles auswählen

def my_map(func, iterable):
    result = list()
    for item in iterable:
        result.append(func(item))
    return result
CrackPod
User
Beiträge: 205
Registriert: Freitag 30. Juni 2006, 12:56

Mittwoch 19. Juli 2006, 15:36

ok, aber self.cur enthält doch keine Daten oder doch?!
Und was macht set genau?
Return a set[...]
Was ist ein "set"?
The elements must be immutable.
Was bedeutet, dass es "immutable" sein muss und was is das egtl?
[...]inner sets should be frozenset objects.
Was ist ein "frozenset"? (Wär wahrscheinlich schnell beantwortet, wenn ich wüsste was ein set wär :wink: )

Das sind warscheinlich mal wieder ziemliche newbie Fragen :oops:
Leonidas
Administrator
Beiträge: 16024
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Donnerstag 20. Juli 2006, 16:34

CrackPod hat geschrieben:ok, aber self.cur enthält doch keine Daten oder doch?!
Und was macht set genau?
Return a set[...]
Was ist ein "set"?
Set ist das englische Wort für Menge. Das Set in Python ist einfach eine Art Liste, die nur verschiedene Elemente enthält (also keine Elemente doppelt), die im Gegensatz zu einer Liste auch keine bestimmte Reihenfolge haben. Wie eine Menge eben.
CrackPod hat geschrieben:
The elements must be immutable.
Was bedeutet, dass es "immutable" sein muss und was is das egtl?
Immutable das heißt so viel wie unveränderbar - das die Objekte, nachdem sie erstellt worden sind, nicht mehr geändert werden können. Strings und Ints sind Beispiele für immutable Datentypen, mutable Datentypen sind zum Beispiel Listen.
CrackPod hat geschrieben:
[...]inner sets should be frozenset objects.
Was ist ein "frozenset"? (Wär wahrscheinlich schnell beantwortet, wenn ich wüsste was ein set wär :wink: )
Frozenset, auch bekannt als ImmutableSet ist einfach die immutable Version des Sets.
My god, it's full of CARs! | Leonidasvoice vs Modvoice
BlackJack

Donnerstag 20. Juli 2006, 23:38

CrackPod hat geschrieben:ok, aber self.cur enthält doch keine Daten oder doch?!
Cursor Objekte sind "iterierbar" das heisst man kann von ihnen einen Iterator über die Ergebniszeilen bekommen. Also kann man so einen Cursor zum Beispiel so benutzen:

Code: Alles auswählen

cursor.execute('SELECT * FROM a_table')
for row in cursor:
    print row
CrackPod
User
Beiträge: 205
Registriert: Freitag 30. Juni 2006, 12:56

Freitag 21. Juli 2006, 10:29

Achso... Ich habs nur mit nem Print versucht und hab mir gedacht, dann müsste ja sowas wie ein Tupel oder eine Liste rauskommen(isses aber nich :wink: )
Und wie sieht das mit set() aus? was macht das? Ich komm mit englischen Dokumentationen von Programmiersprachen überhaupt nich klar - zu kompliziert :oops:
Antworten