OOP in Python - kleines Beispiel

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
Jochen1980
User
Beiträge: 40
Registriert: Montag 15. August 2011, 18:44

Servus, in meinem Buch wird ein kleines Spiel entwickelt und ich wundere mich über einige Bestandteile des Codes. Erstens: ich bin überrascht, dass im Tutorial Instanzattribute nicht explizit irgendwo formuliert werden. Die Klassen stehen hier in einer Datei, ich las aber, dass es Module und gar Packages gibt. Module sollen knapp beschrieben werden, Klassen mit CamelCase. Ist es in Python verbreitet, dass jede Klasse in einer eigenen Datei steht. Wie werden umfassendere mit OOP umgesetzte Bibliotheken realisiert ... nutzt man da diese Packages oder schreibt man mehrere Klassen in eine Datei. Ich habe es noch nicht probiert, aber wie löst Python die Sache mit der Bekanntheit in Klassen, es ist ja interpretiert und wie macht man es wenn die eine Klasse die andere braucht, gibts da irgendwie Probleme mit Mehrfacheinbindung wie in C++, ein Sprachkonstrukt fand ich in Python da nicht ... scheint also irgendwie anders gelöst worden zu sein. Danke vorab für einige Anmerkungen ... ich setze nun mal eine kleine Gui an das Snippet da unten.

Code: Alles auswählen

#!/usr/bin/python3

import random

# MathTask #####################################################################
class MathTask:
	def __init__(self, i, anzahl):
		self.nr = i
		self.gesamt = anzahl
		
	def __str__(self):
		a = random.randint(10,30)
		b = random.randint(10,30)
		self.ergebnis = a + b
		return "Aufgabe " + str(self.nr) + " von " + str(self.gesamt) + " : " + str(a) + " + " + str(b)

	def beantworten(self):
		try:
			if self.ergebnis == int(input()):
				print(self.nr, ": *** Richtig ***")
				return 1
			else:
				raise
		except:
			print(self.nr, ": *** Falsch ***" )
			return 0

# Game #########################################################################
class Game:
	def __init__(self):
		random.seed()
		self.richtig = 0
		self.anzahl = -1
		while self.anzahl<0 or self.anzahl>10:
			try:
				print("Wie viele Aufgaben (1 bis 10):")
				self.anzahl = int(input())
			except:
				continue
				
	def spielen(self):
		for i in range(1,self.anzahl+1):
			a = MathTask(i,self.anzahl)
			print(a)
			self.richtig += a.beantworten()
			
	def __str__(self):
		return "Richtig: " + str(self.richtig) + " von " + str(self.anzahl)


# main #########################################################################

print( "Zahlenspiel nach Thomas Theis, Python-Tutorial" )

s = Game()
s.spielen()
print(s)

BlackJack

@Jochen1980: Falls Du mit „Instanzattribute nicht explizit irgendwo formuliert werden” meinst, das man sie nicht deklarieren muss, dann stimmt das. Explizit setzen muss man sie natürlich, sonst sind sie nicht vorhanden und Zugriffe führen zu der Ausnahme `AttributeError`. Üblicherweise sollte man alle Attribute in der `__init__()`-Methode einführen und nicht beliebig in irgendwelchen Methoden. Nach der Initialisierung sollte das Objekt in einem benutzbaren Zustand sein. Aus der Sicht ist `MathTask` eine ziemliche Katastrophe. Nicht nur das nicht alle Attribute aus der `__init__()`-Methode ersichtlich werden, man muss so ein Objekt auch mindestens einmal in eine Zeichenkette umgewandelt haben, bevor man `beantworten()` aufrufen kann. Jedes mal wenn man so ein Objekt in eine Zeichenkette umwandelt, ändert sich die Aufgabe. Statt `True` oder `False` zurück zu geben, gibt die Funktion Zahlen zurück, und was das ``raise`` und das ``try``/``except`` da zu suchen haben, ist mir auch nicht klar. Das ist einfach nur unnötig umständlich, ausserdem ist mir gerade nicht so ganz klar welche Ausnahme bei der ``raise``-Anweisung eigentlich ausgelöst wird? Spontan hätte ich gesagt ausserhalb eines ``except``-Blocks ist ein nacktes ``raise`` syntaktisch gar nicht erlaubt.

`random.seed()` sollte man nicht aufrufen. Unter bestimmten Umständen ist das sogar dem „Zufall” abträglich.

Insgesamt ist das ein ziemlich schlechtes Beispiel für OOP, weil OOP kein Selbstzweck ist. Das hätte man mit Funktionen viel einfacher haben können. Die zusätzliche Komplexität bringt keinen Nutzen und es ist noch nicht einmal sauber gelöst. Wenn das tatsächlich ein Beispiel aus dem Buch ist, würde ich das in die Tonne werfen.

Eine Datei pro Klasse ist in Python nicht üblich. Eine Datei beschreibt ein Modul und in einem Modul sollte man Klassen und Funktionen zusammen fassen, die thematisch zusammen gehören. Wenn es zu viel für ein Modul wird, kann man es auf mehrere Module und Module kann man in Packages zusammen fassen.

Was Du mit „Bekanntheit von Klassen” ist mir nicht so ganz klar. Klassen sind ja auch nur Objekte die an den Klassennamen gebunden sind. Genau wie bei Objekten die man per Zuweisung an Namen bindet, oder Funktionen. Wenn der Block der ``class``-Anweisung abgearbeitet ist, dann gibt es den Namen und man kann ihn verwenden. Genau wie Funktionen erst verwendet werden können, wenn die ``def``-Anweisung abgearbeitet wurde.

Was ist „Mehrfacheinbindung wie in C++”? So etwas wie ``#include`` gibt es in Python nicht. Lies in der Dokumentation mal nach was bei einem ``import`` passiert.
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

Jochen1980 hat geschrieben:... ich wundere mich über einige Bestandteile des Codes.
Ich mich auch... :)

Was ich anderst machen würde:
- MathTask verwendet zum Instanziieren 4 Namen, die noch nicht einmal sonderlich aussagekräftig sind. Statt 'nr' würde ich etwas wie 'zaehler' oder so verwenden, also

Code: Alles auswählen

def __init__(self, zaehler, gesamt):
    self.zaehler = zaehler
    self.gesamt = gesamt
- Die __str__-Methode wird hier IMHO für falsche Zwecke missbraucht. Das gehört in eine eigene Methode. Dass 'self.ergebnis' z. B. beim Aufruf von '__str__' erzeugt wird, ist mehr als unglücklich. Das gehört in '__init__', denn dort möchte man sehen, mit welchen Attributen die Klasse arbeitet.
- 'MathTask.beantworten' ist der Wahnsinn! Steht das echt so in dem Buch?? Warum 'try:... except:' an dieser Stelle? Da wird alles, das irgendwie irgendwas prüft miteinander durchgemischt, geschüttelt und hin- und hergeworfen... :wink: Das geht auch mit 'if-else'!
- Eine while-Schleife wie in 'Game.__init__' ist echt komisch und gehört zudem auch nicht in '__init__'.
- Eigentlich ist die ganze Game-Klasse unsinnig und lässt sich mit einer einfachen Schleife viel klarer lösen.

mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
Benutzeravatar
gkuhl
User
Beiträge: 600
Registriert: Dienstag 25. November 2008, 18:03
Wohnort: Hong Kong

Ich denke, dass try-except in MathTask dient dazu den NameError abzufangen, sollte man es wagen ``beantworten`` vor ``__str__`` aufzurufen. Das ist wirklich extrem gruselig!!!
BlackJack

@gkuhl: Und den `ValueError` falls der Benutzer etwas eingibt, was nicht in ein `int` wandelbar ist. Statt darauf explizit zu reagieren.
mutetella
User
Beiträge: 1695
Registriert: Donnerstag 5. März 2009, 17:10
Kontaktdaten:

Ich weiß natürlich nicht, inwieweit das Spielbeispiel bereits verändert wurde, jedoch stammt dieser

Code: Alles auswählen

class Spiel:
    def __init__(self):
        # Start des Spiels
        random.seed()
        self.richtig = 0

        # Anzahl bestimmen
        self.anzahl = -1
        while self.anzahl<0 or self.anzahl>10:
            try:
                print("Wie viele Aufgaben(1 bis 10):")
                self.anzahl = int(input())
            except:
                continue
sehr eigentümlich anmutende Codeausschnitt aus der Leseprobe zum Galileobuch "Einstieg in Python" von Thomas Theis.

Schade nur, dass dieses Buch unter anderen auch auf der Pythonseite genannt (und damit ja irgendwie auch empfohlen) wird.

mutetella
Entspanne dich und wisse, dass es Zeit für alles gibt. (YogiTea Teebeutel Weisheit ;-) )
Antworten