richtig gedacht? objektorientierter Taschenrechner

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
Benutzeravatar
Dr.Miles
User
Beiträge: 38
Registriert: Montag 15. Dezember 2008, 08:33
Wohnort: Mannheim
Kontaktdaten:

Hallo,
ich versuche gerade mir objektorientierte Programmierung anzueignen. Habe ein wenig dazu gelesen und bisschen probiert und versuche nun als weitere Übung einen objektorientierten taschenrechner zu schreiben. Bisher funktioniert das nicht wirklich, allerdings liegt das IMO auch an der zu funktionsorientierten Denkweise (nur ein Gefühl). Ist meine Vorgehensweise korrekt?:
Bild


Mein bisheriger Code(funktioniert nicht)

Code: Alles auswählen

class taschenrechner :
	def hauptmenue(self):
		print "Taschenrechner:\n(1)Addition\n(2)Subtraktion\n(3)Multiplikation\n(4)Division"
		if int(raw_input("Auswahl:")) == 1 :
			i = taschenrechner()
			i.berechnung("Addition")
	class berechnung :
		def __init__(self,rechenart):
			self.berechnungs_ui(rechenart)
		def berechnungs_ui(self,rechenart):
			print rechenart , ":"
			operand1 = raw_input("Operand 1:")
			operand2 = raw_input("Operand 2:")
			if rechenart == "Addition":
				be = taschenrechner.berechnung("Addition")
				print be.addition(operand1,operand2)
		def addition(self,operand1,operand2):
			return operand1+operand2

inst = taschenrechner()
inst.hauptmenue()
Wie müsste ich es aus objektorientierter Sicht besser aufteilen, oder ist das nicht das Problem?
Gruß
Dr.Miles[/img]
www.i2p2.de <--- sehr interressantes Anonymisierungsprojekt.
www.schaeuble-wegtreten.de <--- Petition gegen Schäuble
derdon
User
Beiträge: 1316
Registriert: Freitag 24. Oktober 2008, 14:32

http://www.python-forum.de/topic-12791.html lesen und verstehen. Ein Taschenrechner kann keine Methode hauptmenue haben (du musst dich fragen: "Was kann ein Taschenrechner machen und welche Eigenschaften besitzt er?"). Ein Taschenrechner könnte also schon eher eine Methode "calculate" haben (schließlich rechnet er ja und gibt keine Menüs aus). Und warum heißt eine Klasse bei dir "Berechnung"? Eine Berechnung kann nichts machen, die ist einfach da (bzw. wird vom TR ausgegeben). Klassennamen werden großgeschrieben. Was soll die Variable i? Ziemlich nichtssagend meiner Meinung nach.

PS: Hast du das Bild mit MS Paint erstellt? :D
Benutzeravatar
C4S3
User
Beiträge: 292
Registriert: Donnerstag 21. September 2006, 10:07
Wohnort: Oberösterreich

Das Menü hat mit dem Rechner nix zu tun, raus damit und in eine eigene Klasse.

Ich habe in "Objektorientierte Programmierung mit Python" von Weigend erst kürzlich ein für mich recht anschauliches Beispiel eines "Wörterbuches" gelesen das mir das ein wenig verständlicher gemacht hat.
Aber da ich das Prinzip OO selber noch nicht verinnerlicht habe, ist meine Aussage eher mein Empfinden - verlassen würde ich mich darauf auch nicht 100%ig.
Hier gibt es so viele schlaue Leute, die werden sich schon zu Wort melden.
Gruß!
Benutzeravatar
BlackVivi
User
Beiträge: 762
Registriert: Samstag 9. Dezember 2006, 14:29
Kontaktdaten:

Erstmal'ne Taschenrechner-Klasse erstellen die autonom rechnet... Also bestimmte Events empfängt und aus ihnen was macht, je nachdem was. Anschließend eine GUI entwerfen, die diese Events sendet.

Das sind 2 Hauptklassen, Taschenrechner könnte man vielleicht noch mit anderen Klassen unterstützen.. Je nachdem wie umfangreich man es mag =D
Benutzeravatar
HerrHagen
User
Beiträge: 430
Registriert: Freitag 6. Juni 2008, 19:07

Zum Thema Trennung von Oberfläche und Rest: vieleicht kannst du dich ja von ein wenig dem hier inspirieren lassen:
http://de.wikipedia.org/wiki/Model_View_Controller

Dein Code verwendet an einer Stelle noch eine sehr prozedurale Herangehensweise:

Code: Alles auswählen

            if rechenart == "Addition":
                be = taschenrechner.berechnung("Addition")
                print be.addition(operand1,operand2)
        def addition(self,operand1,operand2):
            return operand1+operand2 
In Python sind auch Funktionen erstklassische Objekte. Wenn das, was ausgeführt werden soll, variabel ist, lohnt es sich darüber nachzudenken, ob du eine Funktion in einer Variable speicherst.
Du machst ja auch nicht folgendes:

Code: Alles auswählen

def plus_vier(a):
    if a==1: return 5
    if a==2: return 6
    if a==3: return 7
         ...
Statt abzufragen ob eine Addition gewünscht war, und dann eine Addition auszuführen auszuführen und damit eine zusätzliche Verzweigung zu haben, wäre folgendes sinnvoller:

Code: Alles auswählen

def addition(a, b):
    return a+b
def subtraktion(a, b):
    return a-b

operationen = {"+": addition, "-": subtraktion}
operationen[auswahl]()
PS: Übertreibs bitte nicht. Objektorientiert Arbeiten heißt nicht alles mit Klassen nur so vollzustopfen (es sei den du willst in Java programmieren..), sondern dort wo es passt.

MFG HerrHagen
DasIch
User
Beiträge: 2718
Registriert: Montag 19. Mai 2008, 04:21
Wohnort: Berlin

Es gibt übrigens ein operator Modul.
BlackJack

@Dr.Miles: Du müsstest Dir erst einmal über den Funktionsumfang Deines Taschenrechners klar werden. Wie wird er bedient ("use case") und was muss er leisten?

Wenn Du zum Beispiel eine Oberfläche hast mit zwei Eingabefeldern, einem Ausgabefeld und vier Buttons für die Grundrechenarten, dann brauchst Du keine Klasse für die Logik, da reicht es einfach nach den Knöpfen die passende Funktion auszusuchen und aufzurufen.

Wenn Du einen einfachen "echten" Taschenrecher simulieren willst, also nur ein Textfeld für Ein- und Ausgabe, vier Knöpfen für die Grundrechenarten und eine '='-Taste, dann wird es schon interessanter und eventuell komplizierter, als man sich das so auf den ersten Blick denkt. Dann muss man sich nämlich Gedanken darüber machen, was der Taschenrechner sich alles merken muss und welche Zustände er kennt und wie die Übergänge dazwischen sind.

Es ist auf jeden Fall einfacher eine Rechnerlogik für einen RPN-Rechner zu implementieren. Also einen, der einen Stapel besitzt und wo man erst die Operanden auf den Stapel eingibt und dann die Operation auswählt, die sich die Operanden vom Stapel holt und das Ergebnis dort wieder ablegt.
Benutzeravatar
Dr.Miles
User
Beiträge: 38
Registriert: Montag 15. Dezember 2008, 08:33
Wohnort: Mannheim
Kontaktdaten:

So habe mal versucht das was mir gesagt wurde umzusetzen, soweit ich es verstanden habe:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: utf-8 -*- 
#++++++++++++++++++++++++++++#
# Application: OOPCalcer.py   
# Author:   Dr.Miles
# Date:     2009/01/23
# My Python:   Python 2.5.2 (r252:60911, Jul 31 2008, 17:31:22) [GCC 4.2.3 (Ubuntu 4.2.3-2ubuntu7)] on linux2
version = "OOPCalcer Version 0.1 - "
#
# Description: 
# 
#++++++++++++++++++++++++++++#
#
#++++++++++++++++++++++++++++#
#---imports---#
import easygui #http://easygui.sourceforge.net
from sys import exit
from string import replace


class Calculator :
	def addition(self,operand1,operand2) :
		return operand1 + operand2
	def subtraction(self,operand1,operand2) :
		return operand1 - operand2
	def multiplication(self,operand1,operand2) :
		return operand1 * operand2
	def division(self,operand1,operand2) :
		return operand1 / operand2
Calcer = Calculator()

class GUI :
	def __init__(self) :
		while True :
			self.choice = easygui.buttonbox(msg='Welche Art der Berechnung möchtest du durchführen?', title=version, choices=\
			('Addition', 'Subtraktion', 'Division','Multiplikation','Programm beenden'), image=None)
			if self.choice == "Addition" :
				self.addition()
			elif self.choice == "Subtraktion" :
				self.subtraction()
			elif self.choice == "Division" :
				self.division()
			elif self.choice == "Multiplikation" :
				self.multiplication()
			else :
				exit(0)
	def addition(self):
		self.version = version + "Addition" #version = version + "Addition"
		self.msg = "Bitte untenstehende Felder ausfüllen"
		numbers = easygui.multenterbox(self.msg,self.version, ("1.Summand","2.Summand"))		
		self.summary = Calcer.addition(float(replace(numbers[0],",",".")), float(replace(numbers[1],",",".")))
		easygui.msgbox(msg='Das Ergebnis von %s und %s ist %10.2f'%(numbers[0], numbers[1]\
		,self.summary), title=version , ok_button='OK', image=None)
	def subtraction(self):
		self.version = version + "Subtraktion"
		self.msg = "Bitte untenstehende Felder ausfüllen"
		numbers = easygui.multenterbox(self.msg,self.version, ("Minuend","Subtrahend"))		
		self.summary = Calcer.subtraction(float(replace(numbers[0],",",".")), float(replace(numbers[1],",",".")))
		easygui.msgbox(msg='Die Differenz von %s weniger %s ist %10.2f'%(numbers[0], numbers[1]\
		,self.summary), title=version , ok_button='OK', image=None)
	def division(self):
		self.version = version + "Division"
		self.msg = "Bitte untenstehende Felder ausfüllen"
		numbers = easygui.multenterbox(self.msg,self.version, ("Dividend","Divisor"))		
		self.summary = Calcer.division(float(replace(numbers[0],",",".")), float(replace(numbers[1],",",".")))
		easygui.msgbox(msg='Der Quotient von %s durch %s ist %10.2f'%(numbers[0], numbers[1]\
		,self.summary), title=version , ok_button='OK', image=None)
	def multiplication(self):
		self.version = version + "Multiplikation"
		self.msg = "Bitte untenstehende Felder ausfüllen"
		numbers = easygui.multenterbox(self.msg,self.version, ("1.Faktor","2.Faktor"))		
		self.summary = Calcer.multiplication(float(replace(numbers[0],",",".")), float(replace(numbers[1],",",".")))
		easygui.msgbox(msg='Der Produkt von %s mal %s ist %10.2f'%(numbers[0], numbers[1]\
		,self.summary), title=version , ok_button='OK', image=None)

GUI = GUI()
GUI()
Ich habe zuerst version = version + "Addition" versucht, bekam aber eine
UnboundLocalError: local variable 'version' referenced before assignment-Exception.
Hat bestimmt was mit namespaces zu tuen, aber was genau?
Ist version in dem moment wo ich sie anlege lokal und er sucht nicht mehr nach der globalen?
PS: Den redundaten teil vom Code kann ich bestimmt mit Vererbung entfernen oder?
Dacht ich mir, aber hab nur kurz was zu dem Thema gelesen...

Gruß
Dr.Miles
www.i2p2.de <--- sehr interressantes Anonymisierungsprojekt.
www.schaeuble-wegtreten.de <--- Petition gegen Schäuble
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Man würde in ``__init__`` eigentlich keine Endlosschleife machen, denn jeder der die Klasse instanziiert, selsbt um einfach mal nachzuschauen was diese Klasse so kan ist dann angeschmiert. Da würde man eher eine separate ``run()`` Methode machen.
Dauerbaustelle
User
Beiträge: 996
Registriert: Mittwoch 9. Januar 2008, 13:48

Warum addierst du etwas zu deinem `version`-String? O_________o
Falls du eine Art Fenstertitle ausgeben willst, dann mach das mit

Code: Alles auswählen

funktion_zum_setzen_des_titels("%s - %s" % (programtitle, operation))
Außerdem musst du in den Berechnungsklassen Strings, die du eh nur temporär benötigst, nicht in eine Variable speichern, das ist sinnlos und kostet nur Platz/Speicher. Falls du es der Übersichtlichkeit Willen aber trotzdem tun möchtest, dann speichere es nicht in eine Instanzvariable (`self.foo`) sondern in eine nur-Funktions-interne (`foo`).

Zur Frage mit der Vererbung: Du könntest eine Base-Klasse machen, und diese für jeden Operator erben.

Etwa so:

Code: Alles auswählen

class Operation(object):
    output_string = ""
    name = ""
    input_names = None

    def calc(self):
        op1, op2 = eingabe_der_zahlen(input_names)
        result = self.operation(op1, op2)
        print self.output_string % {'op1' : op1, 'op2' : op2, 'result' : result)

    def operation(self, op1, op2):
        raise NotImplementedError()
        # Funktion MUSS nach Vererbung überschrieben werden

class Addition(Operation):
    output_string = "Das Ergebnis von %(op1)s und %(op2)s ist %(result)s"
    name = "Addition"
    input_names = ("1. Summand", "2. Summand")

    def operation(self, op1, op2):
        return op1+op2

class Multiplication(Operation):
    output_string = "Der Produkt von %(op1)s und %(op2)s ist %(result)s"
    name = "Multiplikation"
    input_names = ("1. Faktor", "2.Faktor")

# für beide anderen operationen kongruent
Jetzt instanzierst du in deiner Main-Klasse einfach die ganzen Operationen und rufst diese bei Bedarf auf:

Code: Alles auswählen

class Foo(object):
    def __init__(self):
        self.add = Addition()
        self.div = Division()
        # ...
    def mainloop(self):
        while True:
            # blubb
            self.div.do_calc()

foo = Foo()
foo.mainloop()
Hoffe, das ein wenig erklärt zu haben. Natürlich ginge es noch schöner, will dich ja aber nicht überfordern ;)

Edit: Anstatt `replace(foo, 'bar', 'blubb') verwende foo.replace('bar', 'blubb').
Zuletzt geändert von Dauerbaustelle am Freitag 23. Januar 2009, 23:38, insgesamt 1-mal geändert.
BlackJack

@Dr.Miles: Da ist kein OOP, das ist wieder nur unnötig kompliziert. Alle "Methoden" in `Calculator` sind eigentlich Funktionen, die schon im `operator`-Modul vorhanden sind.

Und auch in `GUI` gibt es keine einzige echte Methode, das sind auch alles Funktionen. Die tun sogar von der Struktur her alle fast dasselbe, da sollte man Striktur und Daten trennen. Wiederholungen von Struktur und Daten sollte man minimieren.

Mit Klassen modelliert man Objekte die einen (inneren) Zustand haben, den die Methoden manipulieren. Das ist bei Dir in beiden Fällen nicht gegeben.

Das die letzte Zeile quatsch ist, fällt nur deswegen nicht auf, weil sie nie ausgeführt wird.

@Dauerbaustelle: Das ginge in der Tat schöner, nämlich ohne Klassen und Vererbung. Das ist hier absolut unnötig mit Kanonen auf Spatzen geschossen. Wir sind hier doch nicht bei Java. Funktionen sind auch Objekte.

Code: Alles auswählen

import  sys
from operator import add, sub, mul, div
import easygui


def str2float(string):
    return float(string.replace(',', '.'))


def main():
    operations = {'Addition': (add,
                               ('1. Summand', '2. Summand'),
                               'und',
                               'Die Summe'),
                  'Subtraktion': (sub,
                                  ('Minuend', 'Subtrahend'),
                                  'weniger',
                                  'Die Differenz'),
                  'Division': (div,
                               ('Dividend', 'Divisor'),
                               'durch',
                               'Der Quotient'),
                  'Multiplikation': (mul,
                                     ('1. Faktor', '2. Faktor'),
                                     'mal',
                                     'Das Produkt')}
    
    while True:
        version = 'FuncCalculator Version 0.0.1 - '
        message = u'Welche Art der Berechnung möchtest Du Durchführen?'
        choice = easygui.buttonbox(message,
                                   version,
                                   operations.keys() + ['Programm beenden'])
        try:
            (func,
             operand_names,
             operation_name,
             result_name) = operations[choice]
        except KeyError:
            return
        
        message = u'Bitte untenstehende Felder ausfüllen'
        (operand_1,
         operand_2) = easygui.multenterbox(message,
                                           version + choice,
                                           operand_names)

        try:
            result = func(str2float(operand_1), str2float(operand_2))
        except ValueError:
            message = 'Bitte nur Zahlen eingeben!'
        except ZeroDivisionError:
            message = 'Division durch Null ist nicht definiert.'
        else:
            message = '%s von %s %s %s ist %10.2f' % (result_name,
                                                      operand_1,
                                                      operation_name,
                                                      operand_2,
                                                      result)
        easygui.msgbox(message, version, 'OK')


if __name__ == "__main__":
    main()
Antworten