Warum int als Returnwert?

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
mogli
User
Beiträge: 2
Registriert: Freitag 15. Juni 2018, 13:02

Mahlzeit!

Python ist neu für mich, und ich spiele grade ein wenig damit herum.
Konkret sitze ich ein kleiner Datenbank-Klasse, die die Verbindung herstellen und Abfragen ausführen und loggen soll.

Ich nutze dazu MySQLdb.
Innerhalb der Klasse habe ich als Eigenschaften u.a.:

Code: Alles auswählen

self.connection = MySQLdb.connect(self.hostname, self.username, self.password, self.database)
self.cursor = self.connection.cursor()
Nun hätte ich gerne eine Methode, die als Parameter eine SQL-Abfrage entgegennimmt, die bestehende Connection nutzt und dann die Abfrage ausführt.
Das klappt auch, aber der Returnwert ist mir schleierhaft.

Code: Alles auswählen

	def execute(self, query):
		if(self.cursor):
			
			try:
				self.queries.append(query)
				return self.cursor.execute(query)
				

			except Exception as e:
				logging.error('DB-Execution Error: %s', str(e))
				return False
			
			
		else:
			logging.warning('Trying to execute query without connection (%s)', query)
			return False
Warum ist der Returnwert von meiner execute() Methode "int"? Ich hätte ein Object erwartet....

Was ich gerne tun würde:

Code: Alles auswählen

db = meineDBKlasse()
result = db.execute("SELECT * FROM tabelle")
rows = result.fetchall()
Bei result.fetchall() bekomme ich einen AttributeError (int object has no attribute fetchall).

Warum klappt das nicht, das hier aber schon:

Code: Alles auswählen

	def execute(self, query):
		if(self.cursor):
			
			try:
				self.queries.append(query)
				result = self.cursor.execute(query)
				return result.fetchall()
				

			except Exception as e:
				logging.error('DB-Execution Error: %s', str(e))
				return False
			
			
		else:
			logging.warning('Trying to execute query without connection (%s)', query)
			return False
Ich kann mir das Verhalten weder erklären, noch weiß ich was ich ändern muss, damit ich außerhalb der Klasse mit "self.cursor.execute" weiterarbeiten kann.

Hat jemand eine Idee?
Sirius3
User
Beiträge: 17710
Registriert: Sonntag 21. Oktober 2012, 17:20

Wenn Dir etwas unklar ist, dann schau am besten in der Dokumentation zu `execute` nach. Cursor sind kurzlebige Objekte, die man am besten pro Transaktion erstellt. Sie speichern z.B. Informationen über die aktuelle Abfrage. Die beim Erzeugen der Connection gleich mit zu erzeugen ist nicht gut. Eine Funktion sollte eine Art Rückgabewert haben, hier hast Du ein Objekt, auf dem man fetchall aufrufen kann (wenn Du Deinen Fehler korrigiert hast) oder False im Fehlerfall. Das ist schlecht, denn dann muß man immer prüfen, war der Rückgabewert False, dann ist es ein Fehler sonst mach was; aber für den Fehlerfall gibt es schon Exceptions, die Du dann auch nutzen solltest.

Bleibt also:

Code: Alles auswählen

def execute(self, query, *args):
    self.queries.append(query)
    cursor = self.connection.cursor()
    cursor.execute(query, *args)
    return cursor
Das ist aber auch nicht so gut, weil man will ja nicht für jede Query einen neuen Cursor erstellen, den gibt man am besten von außen vor, also besser:

Code: Alles auswählen

    def cursor(self):
        return self.connection.cursor()

    def execute(self, cursor, query, *args):
        cursor.execute(query, *args)

db = meineDBKlasse()
cursor = db.cursor()
db.execute(cursor, "SELECT feldA, feldB FROM tabelle")
rows = cursor.fetchall()
was dann die ganze `execute`-Methode ziemlich überflüssig macht:

Code: Alles auswählen

    def cursor(self):
        return self.connection.cursor()

db = meineDBKlasse()
cursor = db.cursor()
cursor.execute(cursor, "SELECT feldA, feldB FROM tabelle")
rows = cursor.fetchall()
Benutzeravatar
__blackjack__
User
Beiträge: 13003
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Mir ist an der Stelle nicht so ganz klar was die `meineDBKlasse` eigentlich leisten soll. Selbst für's nur protokollieren würde ich wahrscheinlich einfach SQLAlchemy verwenden statt da selbst etwas zu schreiben.

Bei der Ausnahmebehandlung einfach nur eine Fehlerzeile zu protokollieren macht die Fehlersuche unnötig schwer. Wenn man Ausnahmen protokolliert mit denen man nicht rechnet, also die man nicht sinnvoll behandelt, sollte man sicherstellen, dass der Traceback für die Fehlersuche zur Verfügung steht. Das `logging`-Modul hat da auch eine Funktion für.

Die Namensschreibweise entspricht nicht der üblichen Konvention. Klassen werden in MixedCase geschrieben, daran erkennt man dann auch, dass es eine Klasse ist, und muss das nicht noch mal im Namen schreiben. `meineDbKlasse` wäre dann also `MeineDb`. Und da hat `Meine` irgendwie Null Nährwert, bleibt `Db` übrig. Das ist so kurz, das ich es wohl ausschreiben würde — `Database`.

Eingerückt ist das alles auch ein wenig zu tief für den Style Guide for Python Code.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
mogli
User
Beiträge: 2
Registriert: Freitag 15. Juni 2018, 13:02

@Sirius3: Danke Dir! Fehler gefunden und Hinweis bezüglich unklarem Rückgabewert verstanden.

@_blackjack_: "meineDBKlasse" soll in erster Linie leisten, dass ich mich an Python herantasten kann. ;)
Ich habe noch nichts konkretes im Kopf. Mein Gedanke war, dass es nicht verkehrt sein kann, eine Klasse zu haben, die sich um eine DB-Verbindung kümmert, die ich dann verschiedenen anderen Klassen übergeben kann.
Danke auch für den Hinweis auf "SQLAlchemy". Kannte ich nicht, gucke ich mir an!
Antworten