Anfänger: SQLite oder MySQL / Hilfsprogramm

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MariaDB/MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
Antworten
Benutzeravatar
Schwarzer Wolf
User
Beiträge: 56
Registriert: Donnerstag 5. Januar 2017, 05:24

Ich Grüße Euch

Ich bin nun seit kurzer Zeit python am lernen und fange nun an in den Büchern etwas zu Springen, um durch eigene Projekte zu lernen.

Ich möchte mehrere unterschiedliche Programme mit der Zeit machen, die Datenbanken beinhalten. Teilweise sollen die Daten vom Web abrufbar sein, aber auch in Lokalen Programmen. Hauptsystem ist Linux. Sofern man mit Python android apps machen kann, würde das vermutlich dazu kommen.

Nun meine eigentliche Frage:

Sollte ich erst mal mit SQLite anfangen und dann irgendwann zu MySQL gehen, oder reicht SQLite bzw. sollte ich gleich mit MySql anfangen?

Zudem bin ich dabei, ein kleines Hilfsprogramm zu programmieren. primär zum Lernen. Sekundär, dass wenn ich auf einem Server in der ssh console bin, mir die Arbeit mit einem tool so leicht wie möglich zu machen.

Ich wäre dankbar, wenn jemand mal kurz über den Code sieht und mir sagen kann, ob ich auf dem richtigen Weg bin.

Ich bin wie gesagt noch nicht lange dabei, deshalb wird der Code eher anfängermäßig sein.

Danke im Voraus für Eure Zeit und Mühe.

Auuuuuuuuu :D

Code: Alles auswählen

def funktion_serverlogin():

    # modul -> importieren
    import getpass

    # initialisierung


    # server -> daten
    print("Serveraddresse:")
    server = input()
    print("Benutzer:")
    benutzer = input()
    # passwortabfrage
    passwort = getpass.getpass()
    print("Datenbankname:")
    datenbank = input()

    return server, benutzer, passwort, datenbank


def funktion_datenbank_anlegen(server, benutzer, passwort, datenbank):
    # modul -> importieren
    import mysql.connector

    try:
        # verbindung -> aufbauen
        verbindung = mysql.connector.connect(host = server, user = benutzer, passwd = passwort)

        # executiv -> zeiger
        zeiger = verbindung.cursor()

        # db -> falls nicht vorhanden -> anlegen
        zeiger.execute("CREATE DATABASE {}".format(datenbank))
        verbindung.commit()

        # executiv _> beenden
        zeiger.close()

        # server -> verbindung -> schliessen
        verbindung.close()

        print()
        print("Datenbank angelegt", datenbank)
        print()

    except:
        print("Negativ: Keine Serververbindung")
        exit(1)


# basis

# schleife -> initialisieren
basis_menue = - 1

# programm -> startmenue
while basis_menue != 0:
    # menu
    print("Hauptmenü:")
    print("[*1]: Datenbank anlegen [erfordert: mysql.connector]")
    print("[*0]: Programm beenden")
    print()

    # eingabe
    eingabe = int(input())

    # menüpunkte
    if eingabe == 0:
        exit(0)
    elif eingabe == 1:
        basis_server, basis_benutzer, basis_passwort, basis_datenbank = funktion_serverlogin()
        funktion_datenbank_anlegen(basis_server, basis_benutzer, basis_passwort, basis_datenbank)
Wer in der Wildnis lebt, muss zum Wolf werden, oder als Schaf sterben.
(Syrisches Sprichwort)
Benutzeravatar
noisefloor
User
Beiträge: 3843
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,
Sollte ich erst mal mit SQLite anfangen und dann irgendwann zu MySQL gehen, oder reicht SQLite bzw. sollte ich gleich mit MySql anfangen?
Nicht vom "Lite" in SQLite verwirren lassen. SQLite ist auch ein vollwertiges RDBMS (sogar das meistgenutzte weltweit :-) ). Was SQLite halt nicht kann ist:
* parallele Schreibzugriffe
* netzwerkfähig

Heißt, wenn du ein von beiden zwingend brauchst, dann möchtest du vielleicht lieber nicht SQLite nehmen. Oder auch, wenn du ein paar dutzend Million Datensätze hast - ist aber denke ich unwahrscheinlich.

Es ist aber auch durchaus empfehlenswert, für den Datenbankzugriff ein ORM wie SQLAlchemy zu nehmen. Das hat u.a. den Vorteil, dass die eine einheitliche Programmierschnittstelle hast und auch ganz einfach von SQLite zu MySQL wechseln kannst, mit nur einer Änderung in einer Zeile Code.

Und ob du wirklich MySQL nehmen möchtest... Auch, wenn man das in vielen Beispielen findet, muss man ja permanent ein Fragezeichen hinter die Zukunft von MySQL machen. IMHO ist PostgreSQL das "bessere" RDBMS - aber das ist ein andere Thema.

Zum Code:

* ìmport Statement gehören an den Anfang, nicht in die Funktion
* Kommentare sollen den Quelltext _zusätzlich_ dokumentieren. Etliche Kommentare beschreiben einfach, was danach so wie so offensichtlich ist und sind damit überflüssig.
* Ein nacktes `try... except` fängt stumpf alle Fehler ab, dass will man in der Regel nie. Du solltest im `except`nur die Ausnahme behandeln, die dich wirklich interessiert.
* SQL-Statement formatiert man nicht mit den String-Methoden, weil das dann anfällig gegen SQL-Injections ist. Die Python DB API 2.0 macht das über Platzhalter, die dann mit Werten aus einen Tupel befüllt werden und die Werte werden vorher escaped. Wie das genau bei dem von dir gewählten DB-Modul ist, müsstest du in der Doku nachlesen. Für SQLite ist es in der Python Doku beschrieben.
* `basis_menue = - 1; while basis_menue != 0:;`ist so zu umständlich, zumal du nie den Wert von Basismenü änderst. Schreib' einfach `while True:`
* statt Menüs selber zu basteln kann man das `cmd-Modul nutzen. Macht dann Sinn, wenn es mehr als eine handvoll Menüpunkte gibt.
* `exit(0)`ist so nicht der korrekte Weg (und AFAIK auch nicht dokumentiert). Du solltest das exit aus dem sys-Modul nehmen.

Gruß, noisefloor
BlackJack

@Schwarzer Wolf: Das mit dem einsetzen von Werten in SQL-Anweisungen das noisefloor erwähnte gilt allerdings tatsächlich nur für SQL-Werte. Datenbank- und Tabellennamen gehören da nicht dazu, die muss man dann tatsächlich in die Zeichenkette hinein formatieren. Wobei das oft ein „code smell“ ist wenn man variable SQL-Namen hat. Ein allgemeines Werkzeug zum anlegen von Datenbanken wäre da eine Ausnahme.

Zusätzlich zu der Behandlung von Ausnahmen die man *erwartet* würde ich auch keine Funktion die etwas macht was sich ausserhalb des Hauptprogramms befindet, das Programm komplett abbrechen lassen. Das nimmt dem Aufrufer die Möglichkeit *anders* mit Fehler- oder Ausnahmesituationen umzugehen und die Funktion einfach wieder zu verwenden und/oder zu testen.

Auf Modulebene gehört nur Code der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst und nur dann aufgerufen wird, wenn das Modul als Programm ausgeführt wird, aber *nicht* wenn es nur importiert wird.

Bei Funktionen in den Namen zu schreiben das es Funktionen sind, macht keinen Sinn. Funktionen und Methoden sind üblicherweise nach der Tätigkeit benannt die sie durchführen, wodurch man sie von anderen, eher passiven Werten unterscheiden kann.

Der `basis_`-Präfix bei den Namen im Hauptprogramm erscheint mir auch überflüssig, weil der keinen Mehrwert an Information bietet.

Bei einer Funktion `serverlogin()` würde man vom Namen her erwarten, das die eine Anmeldung am Server vornimmt und nicht nur die Anmeldedaten vom Benutzer erfragt.

Der Style Guide for Python Code sagt, das um Zuweisungen bei Schlüsselwortargumenten keine Leerzeichen gesetzt werden.

Datenbank-Cursor habe ich in deutsch noch nie als „Zeiger“ übersetzt gesehen. Zumal die Cursor-Objekte von der DB-API V2 auch nicht wirklich wie Cursor in SQL verwendet werden und selbst wenn das verwendete DBMS SQL-Cursor kennt, diese nicht verwenden müssen.

Ich würde sowieso keine deutschen Namen verwenden. Das bringt schnell Probleme mit der Konvention das Exemplare von Containerdatentypen mit der Mehrzahl des Namens eines Elementes aus dem Container benannt werden. Weil es im deutschen wesentlich mehr Worte gibt, bei denen Einzahl und Mehrzahl sich nicht unterscheiden, während das im englischen fast immer der Fall ist, und diese Unterscheiden fast immer das Mehrzahl-”s” ist. Beispiel:

Code: Alles auswählen

for cursor in cursors:
    # do something with cursor...

# versus:

for behaelter in behaelter:
    # ups, jetzt hat das einzelne Element den Namen des Containers
    # mit den Elementen ”verdrängt”.
Um Cursor und Verbindung ganz sicher in jedem Fall wieder zu schliessen würde man ``try``/``finally`` beziehungsweise ``with`` und `contextlib.closing()` verwenden.

Ein `commit()` macht nur bei DML-SQL-Anweisungen Sinn, also bei solchen die Daten in Tabellen betreffen (inklusive Anweisungen die Daten nur abfragen und nicht verändern), aber nicht beim Anlegen von Datenbanken oder Tabellen. Das kann sowieso nicht zurück gerollt werden.

Ungetestet:

Code: Alles auswählen

from contextlib import closing
from getpass import getpass

import mysql.connector


def anmeldedaten_erfragen():
    print('Serveraddresse:')
    server = input()
    print('Benutzer:')
    benutzer = input()
    passwort = getpass()
    print('Datenbankname:')
    datenbank = input()
 
    return server, benutzer, passwort, datenbank
 
 
def datenbank_anlegen(server, benutzer, passwort, datenbank):
    verbindung = mysql.connector.connect(
        host=server, user=benutzer, passwd=passwort
    )
    with closing(verbindung):
        with closing(verbindung.cursor()) as zeiger:
            zeiger.execute('CREATE DATABASE {}'.format(datenbank))
    print()
    print('Datenbank angelegt', datenbank)
    print()
 

def main():
    while True:
        print('Hauptmenü:')
        print('[*1]: Datenbank anlegen')
        print('[*0]: Programm beenden')
        print()

        try:
            eingabe = int(input())
        except ValueError:
            print('Bitte nur Zahlen eingeben!')
     
        if eingabe == 0:
            return
        elif eingabe == 1:
            server, benutzer, passwort, datenbank = anmeldedaten_erfragen()
            datenbank_anlegen(server, benutzer, passwort, datenbank)


if __name__ == '__main__':
    main()
Sirius3
User
Beiträge: 17710
Registriert: Sonntag 21. Oktober 2012, 17:20

@Schwarzer Wolf: ich kann mich allem was noisefloor schreibt (inklusive dem besten Datenbanksystem :wink:) nur anschließen. Zur Beruhigung: solange man noch lernt, kann man eigentlich nicht die falsche Wahl treffen. Es gibt ja auch nicht die eine Datenbank, die immer passt. Wenn Du mit einem Datenbanksystem anfängst und es kennen lernst und dann merkst, dass etwas nicht geht, dann hast Du viel gelernt: Du kennst die Grenzen und kannst das nächste mal besser abschätzen, ob die Datenbank geeignet ist; Du merkst, wie sehr Dein Code abhängig von einem externen System ist und wie Du das nächste mal solche Abhängigkeiten möglichst an einer Stelle bündelst.

Zum Code: Funktionen sind die Dinger, die man aufrufen kann. Das Wort funktion braucht nicht im Namen vorkommen, oder hast Du irgendeine Funktion aus der Standardbibliothek gefunden, die auf diese Art benannt wurde? Funktionen werden häufig nach Tätigkeiten benannt (weil sie ja auch was machen), sieht man schön an »datenbank_anlegen«. Der Name »serverlogin« ist dagegen falsch, da nur Login-Parameter abgefragt werden. Leerzeilen in Funktionen sind selten nötig, nur dann, wenn man in sich abgeschlossene Blöcke trennen will, und dann sollte man sich fragen, ob diese Blöcke nicht besser eigene Funktionen wären. Um die Gleichheitszeichen bei Keyword-Argumenten gehören keine Leerzeichen. Das Hauptprogramm (alles ab Zeile 51) gehört auch in eine Funktion (üblicherweise »main« genannt, bei Dir wohl »hauptprogramm« obwohl ich immer über deutsche Namen wegen der sonst englischen Schreibweise stolpere), das über »if __name__ == '__main__': main()« am Ende der Datei aufgerufen wird. Alle Importe gehören dagegen an den Anfang der Datei, so dass man alle Abhängigkeiten zu anderen Modulen sofort sehen kann. Nackte excepts dürfen nicht vorkommen, weil sie auch Programmierfehler (z.B. NameError) unterdrücken und so das finden von Fehlern unmöglich machen. Die Aussage, dass bei irgendeinem Fehler keine Serververbindung zustande kam ist ja auch falsch. »exit« sollte in einem Programm maximal einmal vorkommen (am Ende von »main«).
Benutzeravatar
Schwarzer Wolf
User
Beiträge: 56
Registriert: Donnerstag 5. Januar 2017, 05:24

Ich Grüße Euch
noisefloor hat geschrieben: * ìmport Statement gehören an den Anfang, nicht in die Funktion
Ok, also quasi genau wie bei c das #include?
noisefloor hat geschrieben:* Kommentare sollen den Quelltext _zusätzlich_ dokumentieren. Etliche Kommentare beschreiben einfach, was danach so wie so offensichtlich ist und sind damit überflüssig.
Ok, also ein wenig Zuviel des Ganzen. Ist es denn strategisch besser, sich anzugewöhnen längere Kommentare zu machen und kurze Bezeichner oder eher aussagekräftige Bezeichner und wenig Kommentar?
noisefloor hat geschrieben:* Ein nacktes `try... except` fängt stumpf alle Fehler ab, dass will man in der Regel nie. Du solltest im `except`nur die Ausnahme behandeln, die dich wirklich interessiert.
So wie in dem Beispiel von Blackjack?
noisefloor hat geschrieben:* SQL-Statement formatiert man nicht mit den String-Methoden, weil das dann anfällig gegen SQL-Injections ist. Die Python DB API 2.0 macht das über Platzhalter, die dann mit Werten aus einen Tupel befüllt werden und die Werte werden vorher escaped. Wie das genau bei dem von dir gewählten DB-Modul ist, müsstest du in der Doku nachlesen. Für SQLite ist es in der Python Doku beschrieben.
Ich werde wohl erst einmal zu SQLite gehen. Deine Argumente sind für mich einleuchtend. Zudem habe ich gestern festgestellt, dass Calibre auch eine SQLite benutzt. Wenn ich nun ein Programm auf einem Server (SSH) aufrufe, muss ich mir dann Gedanken um Injections machen? Der Serverbetreiber könnte doch eh meine Passwörter einsehen in den Datenbanken, oder?
noisefloor hat geschrieben: Und ob du wirklich MySQL nehmen möchtest... Auch, wenn man das in vielen Beispielen findet, muss man ja permanent ein Fragezeichen hinter die Zukunft von MySQL machen. IMHO ist PostgreSQL das "bessere" RDBMS - aber das ist ein andere Thema.
Du hast recht, ich kam nur darauf, weil es in den Lehrbüchern steht, die ich habe. Am liebsten sind mir generell GNU und Ähnliche.
noisefloor hat geschrieben:* `basis_menue = - 1; while basis_menue != 0:;`ist so zu umständlich, zumal du nie den Wert von Basismenü änderst. Schreib' einfach `while True:`
Ok, habe mir das mit dem "-1" angewöhnt, weil es so im Buch beschrieben wurde. True ist in diesem Fall alles außer 0?
noisefloor hat geschrieben:* statt Menüs selber zu basteln kann man das `cmd-Modul nutzen. Macht dann Sinn, wenn es mehr als eine handvoll Menüpunkte gibt.
Ok, werde ich mich mit befassen.
noisefloor hat geschrieben:* `exit(0)`ist so nicht der korrekte Weg (und AFAIK auch nicht dokumentiert). Du solltest das exit aus dem sys-Modul nehmen.
Über sys Stand auch im Buch. Ich kam auf das "normale" exit, da man in meiner IDE auch so ein exit angezeigt bekommt und das beim Probieren funktioniert hat.
BlackJack hat geschrieben:Das mit dem einsetzen von Werten in SQL-Anweisungen das noisefloor erwähnte gilt allerdings tatsächlich nur für SQL-Werte. Datenbank- und Tabellennamen gehören da nicht dazu, die muss man dann tatsächlich in die Zeichenkette hinein formatieren. Wobei das oft ein „code smell“ ist wenn man variable SQL-Namen hat. Ein allgemeines Werkzeug zum anlegen von Datenbanken wäre da eine Ausnahme.
Das mit Platzhaltern hatte ich probiert, es ging aber nicht. Also wäre der von mir gewählte Weg der einzig richtige?

Was bedeutet "code smell"?
BlackJack hat geschrieben:Zusätzlich zu der Behandlung von Ausnahmen die man *erwartet* würde ich auch keine Funktion die etwas macht was sich ausserhalb des Hauptprogramms befindet, das Programm komplett abbrechen lassen. Das nimmt dem Aufrufer die Möglichkeit *anders* mit Fehler- oder Ausnahmesituationen umzugehen und die Funktion einfach wieder zu verwenden und/oder zu testen.
Ok, try -> except also nur im def main()?
BlackJack hat geschrieben:Auf Modulebene gehört nur Code der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst und nur dann aufgerufen wird, wenn das Modul als Programm ausgeführt wird, aber *nicht* wenn es nur importiert wird.
Ich habe das ein paar Mal so gemacht, das Ich eine def Main() (naja bei mir def basis() :-P angelegt hat, da Ich davor schon ein wenig c geübt hatte. Habe das dann aber verworfen, da es mir komisch vorkam, diese Funktion dann mit basis() aufzurufen.

Also, sofern Ich das nicht übersprungen habe, wird in dem Hauptbuch was Ich lese, das mit Main() nicht erwähnt. Im Index des Buches steht auch nichts. Im Buch werden quasi die Funktionen immer direkt ohne extra main() aufgerufen.
BlackJack hat geschrieben:Der `basis_`-Präfix bei den Namen im Hauptprogramm erscheint mir auch überflüssig, weil der keinen Mehrwert an Information bietet.

Bei einer Funktion `serverlogin()` würde man vom Namen her erwarten, das die eine Anmeldung am Server vornimmt und nicht nur die Anmeldedaten vom Benutzer erfragt.
Ingesamt dachte ich mir, dass es einen längeren Code so übersichtlicher macht. War wie gesagt alles wohl etwas zu viel des Ganzen und wird geändert.
BlackJack hat geschrieben:Bei einer Funktion `serverlogin()` würde man vom Namen her erwarten, das die eine Anmeldung am Server vornimmt und nicht nur die Anmeldedaten vom Benutzer erfragt.
Auf den Namen kam ich, wegen der der Codestelle im Buch, dass keine Serververbindung möglich ist. Siehe Code am Ende.
BlackJack hat geschrieben:Der Style Guide for Python Code sagt, das um Zuweisungen bei Schlüsselwortargumenten keine Leerzeichen gesetzt werden.
Ok, damit meinst Du in diesem falle z. B.: "host=server"? Wenn ja, ist das in meinem Buch auch anders (Siehe Code am Ende). Meine IDE hat mir dort aber einen PEP8 Fehler angezeigt und die IDE wollte es zusammenschreiben.
BlackJack hat geschrieben:Datenbank-Cursor habe ich in deutsch noch nie als „Zeiger“ übersetzt gesehen. Zumal die Cursor-Objekte von der DB-API V2 auch nicht wirklich wie Cursor in SQL verwendet werden und selbst wenn das verwendete DBMS SQL-Cursor kennt, diese nicht verwenden müssen.
Ich würde sowieso keine deutschen Namen verwenden. Das bringt schnell Probleme mit der Konvention das Exemplare von Containerdatentypen mit der Mehrzahl des Namens eines Elementes aus dem Container benannt werden. Weil es im deutschen wesentlich mehr Worte gibt, bei denen Einzahl und Mehrzahl sich nicht unterscheiden, während das im englischen fast immer der Fall ist, und diese Unterscheiden fast immer das Mehrzahl-”s” ist
Ok, vollkommen logisch. Ich werde mir versuchen anzugewöhnen, nur noch Englisch zu benutzen.
BlackJack hat geschrieben:Ein `commit()` macht nur bei DML-SQL-Anweisungen Sinn, also bei solchen die Daten in Tabellen betreffen (inklusive Anweisungen die Daten nur abfragen und nicht verändern), aber nicht beim Anlegen von Datenbanken oder Tabellen. Das kann sowieso nicht zurück gerollt werden.
Das commit habe ich ebenfalls in diesem Beispiel aus dem Buch. Siehe Code am ende.

@BlackJack
Danke für den Code. Das ist für mich sehr aufschlussreich. Ich habe nun noch 2 Fragen zu dem Code:
BlackJack hat geschrieben: from contextlib import closing
from getpass import getpass
Warum das "from" vor dem import. Was ist der Vorteil, als wenn ich nur über import aufrufe?
BlackJack hat geschrieben:if __name__ == '__main__':
was genau macht diese zeile? Es wäre also falsch, main() ohne oben genannte Zeile aufzurufen?

@Sirius3

Ich antworte nur auf einiges an Dich, da ich das meiste von dem, was Du schreibst, schon beantwortet habe. Ich hoffe, Du nimmst es mir nicht böse und liest dort gegebenenfalls mit.
Sirius3 hat geschrieben:Zur Beruhigung: solange man noch lernt, kann man eigentlich nicht die falsche Wahl treffen. Es gibt ja auch nicht die eine Datenbank, die immer passt. Wenn Du mit einem Datenbanksystem anfängst und es kennen lernst und dann merkst, dass etwas nicht geht, dann hast Du viel gelernt: Du kennst die Grenzen und kannst das nächste mal besser abschätzen, ob die Datenbank geeignet ist; Du merkst, wie sehr Dein Code abhängig von einem externen System ist und wie Du das nächste mal solche Abhängigkeiten möglichst an einer Stelle bündelst.
Du hast recht. Wollte eben nur nicht mit SQLite anfangen, weil ich eben wegen dem Lite dachte ...
Sirius3 hat geschrieben:Funktionen sind die Dinger, die man aufrufen kann. Das Wort funktion braucht nicht im Namen vorkommen, oder hast Du irgendeine Funktion aus der Standardbibliothek gefunden, die auf diese Art benannt wurde? Funktionen werden häufig nach Tätigkeiten benannt (weil sie ja auch was machen), sieht man schön an »datenbank_anlegen«
Ich weiß, manchmal habe ich das Problem, das ich es mir viel umständlicher mache, als es sein müsste.
Sirius3 hat geschrieben:Der Name »serverlogin« ist dagegen falsch, da nur Login-Parameter abgefragt werden.
Das habe ich leider auch so aus dem Buch übernommen. Siehe Code am Ende.
Sirius3 hat geschrieben: Leerzeilen in Funktionen sind selten nötig, nur dann, wenn man in sich abgeschlossene Blöcke trennen will, und dann sollte man sich fragen, ob diese Blöcke nicht besser eigene Funktionen wären.
Werde ich mir merken.

@all
Ich Poste hier mal den Originalcode aus dem Buch:

Code: Alles auswählen

# Connector importieren
import sys, mysql.connector

# Verbindung zum Datenbankserver erstellen
try:
    connection = mysql.connector.connect \
        (host = "localhost", user = "root", passwd = "")
except:
    print("Keine Verbindung zum Server")
    sys.exit(0)

# Execution-Objekt erzeugen
cursor = connection.cursor()

# Datenbank erzeugen
cursor.execute("CREATE DATABASE IF NOT EXISTS firma")
connection.commit()

# Execution-Objekt schliessen
cursor.close();

# Verbindung schliessen
connection.close()
Noch mal ein Großes danke an Euch :D . Ich habe gemerkt, dass es wirklich wichtig ist, auch mit Leuten über seinen Anfängercode zu schreiben. Etliches scheint wohl nicht wie in Büchern zu sein. Ist das nun oftmals eine Optimierung des Codes oder wäre vieles einfach schlicht falsch (trotzdem ausführbar)? :?

Da ich es furchtbar schwer habe durch lesen zu lernen, sondern eher durchs Machen, ist es vermutlich das Beste, die Codes dann auch immer hier zu Posten um zu lesen, was verbessert werden sollte. Oder macht das zu viel Aufwand, und ich sollte nur bei Problemen schreiben?

Habe schon bei C das Problem, dass z. B. ein Autor schreibt, man soll das benutzen und das andere der schlechtere Weg sein und das andere Buch schreibt, genau das Gegenteil. :roll:

Zu Datenbanken:
Sagen wir mal, ich möchte einige Tausend Zitate abrufbar machen. Eventuell über Web (öffentlich ohne Login). Maximal 100 Leute (vermutlich eher selten), die zur gleichen Zeit darauf zugreifen. Reicht da SQLite (später gegebenfalls Postgre)?
Wer in der Wildnis lebt, muss zum Wolf werden, oder als Schaf sterben.
(Syrisches Sprichwort)
Benutzeravatar
noisefloor
User
Beiträge: 3843
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,
True ist in diesem Fall alles außer 0?
Fast. `None`ist auch nicht `True`(und auch nicht `False`). `True`und `False`sind ein eigener Datentyp in Python. Und wenn man auf Wahrheitswerte prüfen will, dann sollte man den auch nehmen (und nicht 0 und 1).
Ist das nun oftmals eine Optimierung des Codes oder wäre vieles einfach schlicht falsch (trotzdem ausführbar)?
Das hat nichts mit Optimierung zu tun, sondern ist nur schlicht stilistisch unschön bzw. nicht-idomatisches Python. Damit ist der Code in der Regel schlecht lesbar und schlecht wartbar. Lauffähig kann das trotzdem sein.
So wie in dem Beispiel von Blackjack?
Ja. Hinter das `except` gehört halt die Exception (man kann auch mehrere angeben), die man abfangen will.
Sagen wir mal, ich möchte einige Tausend Zitate abrufbar machen. Eventuell über Web (öffentlich ohne Login). Maximal 100 Leute (vermutlich eher selten), die zur gleichen Zeit darauf zugreifen. Reicht da SQLite (später gegebenfalls Postgre)?
Ja, locker. SQLite unterstützt auch parallele Lesezugriffe, nur halt nicht paralleles Schreiben. Und "einige Tausend" Datensätze sind auch kein Problem.

Gruß, noisefloor
Sirius3
User
Beiträge: 17710
Registriert: Sonntag 21. Oktober 2012, 17:20

@Schwarzer Wolf: ›#include‹ kann in C auch an jeder beliebigen Stelle stehen und dort wird es auch nicht so strikt gehandhabt, wie man das in Python tun sollte. Kommentare sollen beschreiben, warum man etwas macht und nicht wie etwas heißen sollte: daher aussagekräftige Bezeichner. Guter Code braucht fast nie Kommentare, sondern nur Doc-Strings.

Injections haben nicht in erster Linie damit zu tun, dass jemand etwas böswillig macht, sondern damit, dass nicht erwartete Eingaben unerwartete Fehler produzieren. Ein O'Connell kann nichts dafür, daß die Datenbank abstürzt. Dass man solche Fehler dann für böse Dinge ausnutzen kann, ist erst der zweite Schritt. Solange man also sicher ist, dass die Eingaben ok sind, alle Sonderzeichen escaped sind und alle Daten im richtigen Format vorliegen, kann beim Hineinformatieren in SQL-Anweisungen auch nichts passieren. Gestern habe ich wieder in einem neuen Pythonbuch genau das gefunden: »Um Parameter an SQL zu übergeben benutzt man format«. Mit Beispiel aber ohne die ganzen Bedingungen, die ich gerade aufgeführt habe. Da diese ganze Prüferei und Konvertiererei mühselig ist, benutzt man einfach die vorgesehenen Platzhalter und muß sich um den Rest nicht kümmern, das geschieht dann automatisch.
Der richtige Weg beim Erzeugen von Datenbanken und Tabellen ist es, gar keine variable Namen zu benutzen.

In Lehrbüchern steht leider viel Quatsch. Der Autor hat vermutlich vor 30 Jahren C gelernt, als -1 noch für true stand. Auch werden meist nur die Funktionen und Syntax erklärt, aber nicht wie man strukturiert Programmiert. Dass da kein main erwähnt wird, ist also nicht verwunderlich. Es ist ja auch nur eine Funktion wie jede andere auch, verhindert aber, dass man aus Versehen globale Variablen benutzt, macht Programme testbar und man kann sie ohne Seiteneffekte auch als Module benutzen. Das »if __name__ == '__main__':« sorgt nämlich dafür, dass main nur aufgerufen wird, wenn man das Programm auch als solches startet. Wird die selbe Datei als Modul importiert, wird main nicht ausgeführt.

try-except kann man überall dort verwenden, wo man etwas sinnvolles mit der Exception anfangen kann, also nicht nur in main. Wenn das Programm aber nach dem except-Block in keinem gültigen Zustand mehr ist, man also nicht mehr weiter arbeiten kann, dann sollte man diese Exception dort auch nicht behandeln.

Ein »from xy import z« importiert nur einzelne Namen in den eigenen Namensraum und nicht das ganze Modul. Bei »import contextlib« muß man per »contextlib.closing« auf die Funktion zugreifen, bei der from-Variante kann man direkt »closing« schreiben.

Nochmal zu Deinem Buch: Buchautor zu sein ist mit viel Arbeit und wenig Ruhm verbunden (kann selbst ein Lied singen). Gerade bei Einführungsbucher muß man vieles wissen, und dann bleibt halt meist nur, über vieles ziemlich wenig zu wissen. Sobald ich ein Buch sehe, das sich nicht an die Konventionen von PEP8 hält, bekommt es ein „nicht empfehlenswert“, da das Einhalten von Konventionen nichts kostet, nur, daß man mal PEP8 liest. Und wenn der Autor das nicht tut, dann hat er sich wahrscheinlich auch sonst nicht viel mit der Philosophie von Python auseinandergesetzt.
BlackJack

@Schwarzer Wolf: Faustregel bei Kommentaren: Nicht beschreiben *was* der Code macht, denn das sollte aus dem Code selber ablesbar sein, sondern *warum* er das so macht wie er es macht. Sofern das nicht offensichtlich ist. An der Stelle kann man sich nicht an Lehrbüchern orientieren, auch nicht an guten, wenn die in jeder Zeile das offensichtliche kommentieren. Diese Kommentare richten sich nur an den absoluten Anfänger, aber so sollte man eher keine realen Programme schreiben. Da sollte man schon einen Leser voraussetzen der programmieren allgemein, und die verwendete Programmiersprache im besonderen, beherrscht, und auch entweder die verwendeten Bibliotheken bereits kennt, oder sich das Wissen bei Bedarf anlesen kann.

Um SQL-Injections sollte man sich immer Gedanken machen, vor allem weil es keinen zusätzlichen Aufwand bedeutet das für Werte sicher zu gestalten, ist es müssig überhaupt darüber nachzudenken ob man es unsicher oder sicher macht. Man macht es einfach *immer* auf die sichere Weise und gut ist.

MySQL steht in vielen Büchern weil es ein sehr weit verbreitetes DBMS ist, welches man üblicherweise bei 08/15-Webhostern angeboten bekommt.

Das es `exit()` einfach so gibt ist nicht dokumentiert. Das muss also nicht einfach so vorhanden sein, auch wenn es das bei CPython (leider) ist.

„Code smell“ bedeutet der Code ”riecht komisch”, also etwas das nur in Ausnahmefällen wirklich so gelöst werden sollte und in allen anderen Fällen eher keine gute Idee ist. Wie in diesem Fall das zusammensetzen von SQL-Anweisungen per Zeichenkettenoperationen mit Daten die vom Benutzer kommen, was SQL-Injections ermöglicht. Und selbst wenn man das sicher gestaltet, oder guten Grund hat zu glauben das man dem Benutzer hier vertrauen kann das er weiss was er tut und verantwortlich handelt, kann zum Beispiel der Wunsch variable Tabellennamen zu verwenden trotzdem ein „code smell“ sein und auf einen falschen Datenbankentwurf hindeuten.

Wenn man Namen mit Präfixen wie `basis_` versehen muss um die Übersicht zu behalten, dann hat man eindeutig zu viele Namen auf einmal in einem Namensraum oder Sichtbarkeitsbereich. Wenn das Hauptprogramm in einer Funktion steckt, dann sind alle Namen dort ja lokal und gelten nur dort.

`__name__` enthält den Modulnamen als Zeichenkette *ausser* wenn man das Modul als Programm ausführt, dann ist der Namen an die Zeichenkette '__main__' gebunden. Das steht vielleicht nicht in Deinem Buch, aber in der Python-Dokumentation. Oben rechts auf fast jeder Seite in der HTML-Variante der Python-Dokumentation ist ein Link zum Index, worüber man solche Namen und auch Schlüsselworte und die ganzen Namen (Konstanten, Funktionen, Klassen, Methoden) aus der Standardbibliothek finden kann.

Bei SQLite ist die Hauptschwachstelle nebenläufiger Schreibzugriff. Wobei in aktuellen Versionen mit dem optinalen „Write Ahead Log“ einige Szenarien besser geworden sind. Sowohl der Datenumfang als auch viele nebenläufige Lesezugriffe sind grundsätzlich erst einmal kein Problem. Einige Tausend Datensätze sind ja keine wirkliche Datenmenge. Und 100 lesende Nutzer sollten auch kein Problem darstellen.

@Sirius3: Bei C steht eher 1 für `true`. Die -1 ist in vielen BASIC-Dialekten der kanonische Wert für ”wahr” weil die vorzeichenbehaftete Ganze Zahlen verwenden und ``NOT`` eine bitweise Negation aller Bits ist. Wenn der Autor also -1 für Wahr nimmt, dann ist der noch viel gefährlicher. ;-)
Benutzeravatar
Schwarzer Wolf
User
Beiträge: 56
Registriert: Donnerstag 5. Januar 2017, 05:24

Sirius3 hat geschrieben: Ein »from xy import z« importiert nur einzelne Namen in den eigenen Namensraum und nicht das ganze Modul. Bei »import contextlib« muß man per »contextlib.closing« auf die Funktion zugreifen, bei der from-Variante kann man direkt »closing« schreiben.
Heißt das, dass ich dadurch Arbeitsspeicher und Rechenleistung spare und am besten immer nur das vom Modul importiere, was ich brauche?

@all

Danke. Das war alles sehr aufschlussreich und nun weiß ich erst mal, wo ich weiter ansetzen kann beim Lernen.

Schönes Wochenende :D
Wer in der Wildnis lebt, muss zum Wolf werden, oder als Schaf sterben.
(Syrisches Sprichwort)
BlackJack

@Schwarzer Wolf: ``from … import …`` spart weder Arbeitsspeicher noch Rechenleistung, es muss in jedem Fall das Modul importiert werden und das heisst auch in jedem Fall das der Code auf Modulebene komplett ausgeführt werden muss, denn nur dann hat man ja auch sicher den Wert zu dem oder den Namen den/die man importiert.
Benutzeravatar
snafu
User
Beiträge: 6731
Registriert: Donnerstag 21. Februar 2008, 17:31
Wohnort: Gelsenkirchen

Schwarzer Wolf hat geschrieben:Heißt das, dass ich dadurch Arbeitsspeicher und Rechenleistung spare und am besten immer nur das vom Modul importiere, was ich brauche?
Würde man im ersten Moment vielleicht denken, aber eine Klasse oder Funktion kann ja auch Abhängigkeiten außerhalb des Codes haben (Importe, andere Klassen oder Funktionen, auf Modulebene definierte Konstanten, ...). Und theoretisch kann der Name bis zum Schluss des Modulcodes noch durch etwas anderes überschrieben werden. Daher wird das Modul immer komplett durchlaufen. import ... from sorgt nur dafür, dass der Name direkt verfügbar ist und nicht mittels modul.name angesprochen werden muss. Bei längeren Modulnamen oder wenn noch Untermodule angesprochen werden müssen, ist es eine Vereinfachung. Der Nachteil ist, dass man nicht mehr auf einem Blick sehen kann, aus welchem Modul der Name kommt (man muss ihn dann halt oben bei den Imports raussuchen, wenn man an seinem Ursprung interessiert ist).
Antworten