Erstes eigene Klasse Wuerfelbecher

Stellt hier eure Projekte vor.
Internetseiten, Skripte, und alles andere bzgl. Python.
Antworten
Erik1986
User
Beiträge: 3
Registriert: Freitag 4. Juli 2014, 13:09

Hallo, Ich habe nun nach einigem Studium der Python Lehrbücher begonnen aus dem Internet ein paar Übungsaufgaben zusammenzusuchen wobei ich auf eine Aufgabe stiess die forderte dass man ein Programm entwickelt dass aus einer bestimmten Anzahl würfen mit einem x seitigen Würfel durch einen Zufallsgenerator herausfindet welche Zahl am meisten geworfen wurde.

Da bei mehreren Versuchen immer wieder feststellte dass sich die einzelnen Würfe kaum unterschieden entschied ich mich statdessen alle möglichen Würfe auszugeben, deren Anzahl und die Prozentzahl. Da dies nun mein erstes eigenes Programm ist sind Verbesserungsvorschläge wilkommen und mich würde interessieren ob ich die Klasse Statisch machen kann so dass ich nicht bei jedem Aufruf eine neue Instanz erzeugen muss.

Code: Alles auswählen


import random

class Wuerfelbecher(object):
    
    def __init__(self):
        self.__anzahlSeiten = 0
        self.__anzahlWuerfe = 0
        self.__dictErgebnis = {}
        
    def __wuerfeAbfragen(self):
        """ Benutzereingabe Anzahl der Wuerfe
        """
        while self.__anzahlWuerfe < 1 or self.__anzahlWuerfe > 1000:
            try:
                self.__anzahlWuerfe = int(input("Anzahl der Wuerfe (1 - 1000): "))
            except ValueError:
                print("Bitte nur Ganzzahlen eingeben!")
                
    def __seitenAbfragen(self):
        """ Benutzereingabe Anzahl der Seiten
        """
        while self.__anzahlSeiten < 1 or self.__anzahlSeiten > 24:
            try:
                self.__anzahlSeiten = int(input("Anzahl der Seiten (1 -   24): "))
            except ValueError:
                print("Bitte nur Ganzzahlen eingeben!")
                
    def __wuerfelsequenz(self):
        """ Durch Zufallsgenerator erzeugte Wuerfe
        """
        random.seed()
        zaehler = 0
        while zaehler < self.__anzahlWuerfe:
            wurf = random.randint(1, self.__anzahlSeiten)
            try:
                self.__dictErgebnis[wurf] += 1
            except KeyError:
                self.__dictErgebnis[wurf] = 1
            zaehler += 1
                
    def __dictAusgeben(self):
        """ Formatierte Ausgabe des Dictionarys
        """
        zaehler = 1
        while zaehler <= self.__anzahlSeiten:
            prozent = self.__dictErgebnis[zaehler] / self.__anzahlWuerfe * 100
            try:
                print("%2d wurde %3d mal gewuerfelt (%3.2f%%)." % (zaehler, self.__dictErgebnis[zaehler], prozent))
            except KeyError:
                print("%2d wurde   0 mal gewuerfelt (%3.2f%%)." % (zaehler, prozent))
            zaehler += 1
        del self.__dictErgebnis
        del self.__anzahlSeiten
        del self.__anzahlWuerfe
            


    def programmBeginnen(self):
        """ Oeffentliche Methode zur Programmausfuehrung
        """
        self.__wuerfeAbfragen()
        self.__seitenAbfragen()
        self.__wuerfelsequenz()
        self.__dictAusgeben()

BlackJack

@Erik1986: Das ist kein OOP sondern missbrauch einer Klasse als globaler Namensraum für ein paar Funktionen, die nicht einmal ordentliche Funktionen sind, weil sie keine Argumente entgegen nehmen und nichts zurück geben. Was genau ist der Sinn das so kompliziert zu machen mit einer Klasse die man nur erstellen und die *eine* ”Methode” aufrufen kann?

Die doppelten führenden Unterstriche sollten, wenn man sie überhaupt unbedingt braucht, jeweils nur *ein* führender Unterstrich sein.

Geschäftslogik und Benutzerinteraktion stecken im selben Objekt.

`__wuerfeAbfragen()` und `__seitenAbfragen()` sind nahezu identisch und eigentlich ein Fall für eine Funktion die eine ganze Zahl vom Benutzer abfragt.

`random.seed()` aufzurufen ist nicht nur überflüssig, sondern unter umständen sogar schädlich weil es unter bestimmten Umständen für *weniger* zufällige Werte sorgen kann!

Wenn man eine Zählschleife hat, bei der man die Anzahl der Durchläufe vorher kennt, nimmt man keine ``while`` sondern eine ``for``-Schleife. Das ist weniger Code, der zudem nicht so weiträumig verteilt ist, sondern sich tatsächlich nur auf die ``for``-Zeile beschränkt.

Statt eines normalen Wörterbuchs, bietet sich `collections.Counter` an.

Bei der Ausgabe der Daten wäre die `get()`-Methode bei einem normalen Wörterbuch einfacher als den `KeyError` zu behandeln und dort den Ausgabetext fast noch mal identisch zu haben.

Das entfernen der Attribute macht die Klasse am Ende noch einmal einen Tick nutzloser.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Hallo und willkommen im Forum!

Ich fange einfach mal von oben nach unten an. Als erstes ist auffällig, dass die Würfelbecherklasse überflüssig ist. Sie hält im Prinzip nur zwei Parameter und das Ergebnis der berechnung. Einfache Funktionen wären hier die bessere Lösung gewesen. Deine Nachfrage nach statischen Klassen ist dafür schon ein schöner Indikator.

Dann fällt sofort auf, dass du überall doppelte führende Unterstriche verwendest. Die sind *nicht* dazu da etwas als privat zu deklarieren, doppelte Unterstriche dienen der Vermeidung der Namenskollision. Doppelte Unterstriche verhindern auch *nicht*, dass man auf die Attribute noch zugreifen kann. Es ist eben nur etwas mehr Schreibarbeit. Um "private" Attribute, oder besser "Implementierungsdetails", zu kennzeichnen, wird ein führender Unterstrich verwendet.

Python ist auch nicht Java, daher solltest du dich auch an die Namenskonventionen halten. Variablen und Namen von Funktionen werden klein_mit_unterstrich geschrieben. Schau dir dazu mal PEP 8 an, daran solltest du dich halten. Das macht dein Programm für alle verständlicher.

Die Funktionen zum Abfragen der Würfe und der Anzahl der Seiten sind quasi identisch. Das solltest du zu einer einzigen Funktion zusammenfassen. "wuerfe_abfragen" und "seiten_abfragen" rufen die neuen Funktion dann nur noch mit den entsprechenden Parametern auf (Text und Grenzwerte).

Der seed-Aufruf in Zeile 32 ist überflüssig und die while-Schleife sollte eine for-Schleife sein. Das ist immer dann der Fall, wenn du vorher die Anzahl der Durchläufe kennst. Ein Zähler, welcher bei jedem Durchlauf um eins inkrementiert wird, ist ein ganz deutliches Signal für diesen Fall ;-) Selbiges gilt für die Schleife in "dictAusgeben".

Dann stellt sich die Frage, was du mit den 53 biss 55 beabsichtigst. Vielleicht versuchst du zu erklären, warum du diese Aufrufe machst. Wahrscheinlich stecken da einige falsche Annahmen hinter.
Das Leben ist wie ein Tennisball.
BlackJack

Mal mit Funktionen (für Python 2):

Code: Alles auswählen

# coding: utf8
from __future__ import division, print_function
from collections import Counter
from random import randint


def roll_dices(roll_count, side_count):
    return Counter(randint(1, side_count) for _ in xrange(roll_count))


def ask_integer(prompt, default_value, max_value, min_value=1):
    prompt = (
        '{0} ({1} - {2}) [{3}]: '.format(
            prompt, min_value, max_value, default_value
        )
    )
    while True:
        response = raw_input(prompt)
        if not response.strip():
            response = default_value
        try:
            result = int(response)
        except ValueError:
            print('Bitte nur Ganzzahlen eingeben!')
        else:
            if min_value <= result <= max_value:
                return result
            else:
                print(
                    'Bitte Werte zwischen {0} und {1} eingeben (inklusive)!'
                        .format(min_value, max_value)
                )


def print_result(histogram, roll_count, side_count):
    for side_number in xrange(1, side_count + 1):
        count = histogram[side_number]
        print(
            '{0:2d} wurde {1:3d} mal gewürfelt ({2:.2%})'.format(
                side_number, count, count / roll_count
            )
        )


def main():
    roll_count = ask_integer('Anzahl der Würfe', 1000, 1000)
    side_count = ask_integer('Anzahl der Seiten', 6, 24)
    print_result(roll_dices(roll_count, side_count), roll_count, side_count)


if __name__ == '__main__':
    main()
Erik1986
User
Beiträge: 3
Registriert: Freitag 4. Juli 2014, 13:09

Hmm warum als Anfänger in Python Python 2 verweden wenn die Python 2 Programmierer angehalten sind auf Python 3 umzusteigen? Das ist als würde ich C programmieren obwohl C++ die fortschrittlichere Lösung ist.

Eine Klasse habe ich deshalb verwendet da ich hauptsächlich mit C++ / C# programmiere, jedoch Python für pre Builds Etc. verwenden möchte. Dort ist es nuneinmal der Fall dass man die Daten in (statische) Klassen verpackt und sie in Asemblys speichert um sie vom Hauptprogramm zu trennen. Auch schliesst eine Struktur die Kapselung nicht ein.

Laut der Turorials und Bücher die ich verwendete solle man __ statt _ verwenden da so sichergestellt wird dass wenn mehrere Kassen in einem Programm eingebunden werden keine Namenskollisionen entstehen.

Den Seed Operator habe ich verwendet da er meines Wissens den Random Generator mit der Systemzeit initialisiert von daher wüsste ich nicht wieso dies Fehler erzeugen würde besonders da ich wenn ich ihn nicht verwende bei jeder neuen Instanz die Selben ergebnisse bekomme.

die del Anweisungen dienen dem Freigeben des Speichers nach Programmende da die beiden Integer Variablen und das Dictionary nichtmehr benötigt werden und so der Speicher freigegeben wird auch wenn die Instanz nicht beendet wird.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Erik1986 hat geschrieben:Hmm warum als Anfänger in Python Python 2 verweden wenn die Python 2 Programmierer angehalten sind auf Python 3 umzusteigen? Das ist als würde ich C programmieren obwohl C++ die fortschrittlichere Lösung ist.
Niemand ist angehalten Python 3 zu benutzen und nicht Python 2. Die zweier Version ist genau so aktuelle wie die dreier Reihe. Python 3 ist auch nicht deutlich besser als 2, sonst hätten längst alle den Umstieg gemacht.
Erik1986 hat geschrieben:Eine Klasse habe ich deshalb verwendet da ich hauptsächlich mit C++ / C# programmiere, jedoch Python für pre Builds Etc. verwenden möchte. Dort ist es nuneinmal der Fall dass man die Daten in (statische) Klassen verpackt und sie in Asemblys speichert um sie vom Hauptprogramm zu trennen. Auch schliesst eine Struktur die Kapselung nicht ein.
Willst du nun Python programmieren lernen oder bei C++ bleiben? Wenn du eine neue Sprache lernst, dann solltest du sie auch so verwenden wie sie gedacht ist. Mit dem selben Argument könntest du auch in C++ so programmieren wie in C.
Erik1986 hat geschrieben:Laut der Turorials und Bücher die ich verwendete solle man __ statt _ verwenden da so sichergestellt wird dass wenn mehrere Kassen in einem Programm eingebunden werden keine Namenskollisionen entstehen.
Nein, das wird nur zur Namenskollisionsvermeidung bei Vererbung und Mixins verwendet. Der Fall ist aber extrem selten. Wenn du dir nicht sicher bist, dass du doppelte führende Unterstriche brauchst, dann brauchst du sie nicht.
Erik1986 hat geschrieben:Den Seed Operator habe ich verwendet da er meines Wissens den Random Generator mit der Systemzeit initialisiert von daher wüsste ich nicht wieso dies Fehler erzeugen würde besonders da ich wenn ich ihn nicht verwende bei jeder neuen Instanz die Selben ergebnisse bekomme.
Nein bekommst du nicht, das random-Modul initialisiert sich selbst.
Erik1986 hat geschrieben:die del Anweisungen dienen dem Freigeben des Speichers nach Programmende da die beiden Integer Variablen und das Dictionary nichtmehr benötigt werden und so der Speicher freigegeben wird auch wenn die Instanz nicht beendet wird.
Du musst in Python nichts per Hand freigeben, das macht der Garbage Collector schon ganz prima für dich.
Das Leben ist wie ein Tennisball.
Erik1986
User
Beiträge: 3
Registriert: Freitag 4. Juli 2014, 13:09

k dann setz ich mich nochmal drüber^^

Also wärs im Prinzip das sinvollste statt das Problem objektorientiert einfach Prozedural mit 3 Funktionen zu lösen richtig?^^
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Erik1986 hat geschrieben:Hmm warum als Anfänger in Python Python 2 verweden wenn die Python 2 Programmierer angehalten sind auf Python 3 umzusteigen?
Öh... wer sagt denn beides? Erstens hat Dich dazu doch niemand aufgefordert und zweitens fordert auch niemand Entwickler auf, Python 2 nicht mehr zu verwenden - zumindest keine relevante Stelle.
Erik1986 hat geschrieben: Das ist als würde ich C programmieren obwohl C++ die fortschrittlichere Lösung ist.
Äh...nö. C und C++ sind zwei unterschiedliche Sprachen, die sich deutlich voneinander unterscheiden und die nicht in einer Vorgänger-Nachfolger-Beziehung stehen :!:
Erik1986 hat geschrieben: Eine Klasse habe ich deshalb verwendet da ich hauptsächlich mit C++ / C# programmiere, jedoch Python für pre Builds Etc. verwenden möchte. Dort ist es nuneinmal der Fall dass man die Daten in (statische) Klassen verpackt und sie in Asemblys speichert um sie vom Hauptprogramm zu trennen. Auch schliesst eine Struktur die Kapselung nicht ein.
Auch in C++ würde man (ich) das nicht als Klasse implementieren. In C# müsste man das vermutlich auch nicht... falls man mit einem ``Func``-Typ hinkommt. Den Rest habe ich leider nicht verstanden.
Erik1986 hat geschrieben: Laut der Turorials und Bücher die ich verwendete solle man __ statt _ verwenden da so sichergestellt wird dass wenn mehrere Kassen in einem Programm eingebunden werden keine Namenskollisionen entstehen.
Welches Tutorial behauptet das? Und falls dem so ist, vergiss beides; sowohl das Tutorial, welches damit vollkommen unbrauchbar ist, als auch die Behauptung :-)
Erik1986 hat geschrieben: Den Seed Operator habe ich verwendet da er meines Wissens den Random Generator mit der Systemzeit initialisiert von daher wüsste ich nicht wieso dies Fehler erzeugen würde besonders da ich wenn ich ihn nicht verwende bei jeder neuen Instanz die Selben ergebnisse bekomme.
Du brauchst ``seed`` nicht separat aufrufen; die Initialisierung erfolgt iirc beim Importieren des Moduls. Du kannst das aber einfach testen, indem Du das ``seed`` weglässt und vergleichst, ob bei jedem Ablauf die selben Zahlen kommen ;-)

Im übrigen ist ``seed`` eine Funktion und kein Operator.
Erik1986 hat geschrieben: die del Anweisungen dienen dem Freigeben des Speichers nach Programmende da die beiden Integer Variablen und das Dictionary nichtmehr benötigt werden und so der Speicher freigegeben wird auch wenn die Instanz nicht beendet wird.
Python hat einen Garbage-Collector - der kümmert sich um das Freigeben von Speicher und auf den hast Du keinen Einfluss. Mittels ``del`` löschst Du nur den Namen, nicht das Objekt selber!

EyDu war schneller - ich poste es dennoch als "Verstärkung" :-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
pillmuncher
User
Beiträge: 1484
Registriert: Samstag 21. März 2009, 22:59
Wohnort: Pfaffenwinkel

Erik1986 hat geschrieben:Hmm warum als Anfänger in Python Python 2 verweden wenn die Python 2 Programmierer angehalten sind auf Python 3 umzusteigen? Das ist als würde ich C programmieren obwohl C++ die fortschrittlichere Lösung ist.
Python 2 ist nicht veraltet. Di Entscheidung, Python 2 oder Python 3 zu verwenden, hängt u.a. davon ab, welche Bibliotheken du verwenden möchtest/musst und ob diese für die jeweilige Python Version zur Verfügung stehen, oder ob dein Programm in einer gegebenen Python 2/3 Umgebung lauffähig sein soll, und so weiter. Python 2 wird noch einige Jahre mantained werden, und ist deswegen eben noch nicht veraltet.

Deswegen passe der Vergleich zu C vs. C++ auch nicht recht. diese sind eigenständige Programmiersprachen, wobei die eine auf der anderen basiert. Python 2 und 3 sind dieselbe Programmiersprache, nur dass die eine ggü. der anderen einige Modifikationen enthält, die z.T. sogar rückportiert wurden.
Erik1986 hat geschrieben:Eine Klasse habe ich deshalb verwendet da ich hauptsächlich mit C++ / C# programmiere, jedoch Python für pre Builds Etc. verwenden möchte. Dort ist es nuneinmal der Fall dass man die Daten in (statische) Klassen verpackt und sie in Asemblys speichert um sie vom Hauptprogramm zu trennen. Auch schliesst eine Struktur die Kapselung nicht ein.
Es gibt in Python keine statischen Klassen. Es gibt zwar statische Methoden, aber die bieten praktisch keinen Vorteil ggü. einfachen Funktionen in einem Modul.
Erik1986 hat geschrieben:Laut der Turorials und Bücher die ich verwendete solle man __ statt _ verwenden da so sichergestellt wird dass wenn mehrere Kassen in einem Programm eingebunden werden keine Namenskollisionen entstehen.
Das ist nur halbwahr. Die doppelten führenden Unterstriche bei Attributnamen führen zu Private Name Mangling. Die Idee dahinter ist, dass bei Mehrfachvererbung manchmal Namenskollisionen auftreten können:

Code: Alles auswählen

class Foo(object):
    def blubb(self):
        print 'Foo instance blubbed!'

class Bar(object):
    def blubb(self):
        print 'Bar instance blubbed!'
    def snarf(self):
        self.blubb()

class Baz(Foo, Bar):
    pass

b = Baz()
b.snarf()
Ergebnis:

Code: Alles auswählen

Foo instance blubbed!
Wenn man es dagegen so macht:

Code: Alles auswählen

class Foo(object):
    def __blubb(self):
        print 'Foo instance blubbed!'

class Bar(object):
    def __blubb(self):
        print 'Bar instance blubbed!'
    def snarf(self):
        self.__blubb()

class Baz(Foo, Bar):
    pass

b = Baz()
b.snarf()
ist das Ergebnis:

Code: Alles auswählen

Bar instance blubbed!
Erik1986 hat geschrieben:Den Seed Operator habe ich verwendet da er meines Wissens den Random Generator mit der Systemzeit initialisiert von daher wüsste ich nicht wieso dies Fehler erzeugen würde besonders da ich wenn ich ihn nicht verwende bei jeder neuen Instanz die Selben ergebnisse bekomme.
Die random.seed() Funktion ist dazu da, bei verschiedenen Aufrufen desselben Programms zu unterschiedlichen Zeiten immer dieselbe Zufallszahlenfolge zu erzeugen, indem man seed() immer mit derselben Konstante aufruft. Manchmal braucht man sowas. Wenn man irgendeine "zufällige" Zufallszahlenfolge nöchte, braucht man seed() nicht aufzurufen, da das das normale Verhalten des random Moduls ist.
Erik1986 hat geschrieben:die del Anweisungen dienen dem Freigeben des Speichers nach Programmende da die beiden Integer Variablen und das Dictionary nichtmehr benötigt werden und so der Speicher freigegeben wird auch wenn die Instanz nicht beendet wird.
Python ist eine Dynamische Programmiersprache mit Garbage Collector, da braucht man keinen Speicher freizugeben. del x löscht auch nur den Namen x aus dem aktuellen Namespace:

Code: Alles auswählen

>>> x = object()
>>> x
<object object at 0x7ff804c0>
>>> y = x
>>> y
<object object at 0x7ff804c0>
>>> del x
>>> y
<object object at 0x7ff804c0>
>>> x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'x' is not defined
In specifications, Murphy's Law supersedes Ohm's.
BlackJack

@Erik1986: Ich verwende halt Python 2 weil ich den Schritt auf 3 aus diversen praktischen Gründen nicht machen kann. Und da bin ich nicht der einzige. In der realen Anwendung wird einem also auch immer noch Python 2 über den Weg laufen. Und das wird auch noch ein paar Jahre so sein. Python 2.7 wird noch bis 2020 offiziell unterstützt.

Der C und C++ Vergleich ist ganz böse daneben weil die Sprachen im Gegensatz zu Python 2 vs. Python 3 fast nichts miteinander gemein haben und es gute Gründe gibt C zu lernen, IMHO aber wenige um sich C++ anzutun.

”Statische Klassen” gibt es in Python nicht. Das was dem am nächsten kommt sind Module und Funktionen.

`random.seed()` kann in der Tat den Zufallsgenerator mit der Systemzeit initialisieren. Die Frage ist mit welcher Genauigkeit das passiert, und wenn man das mehrfach innerhalb dieser Genauigkeit aufruft und Zufallszahlen erzeugt, *dann* erhält man dabei tatsächlich immer die selbe Zahlenfolge. Wenn man den Aufruf weg lässt dann sind es dagegen verschiedene Zahlenfolgen. Die Implementierung bei CPython ist mittlerweile etwas ”schlauer” und versucht erst ob sie Daten per `os.urandom()` zur Initialisierung bekommen kann bevor sie auf die Systemzeit mit einer Auflösung von 256tel Sekunden zurückfällt, beziehungsweise auch eine gröbere Auflösung wenn das System weniger bei `time.time()` liefert. Im schlechtesten Fall sollte man also davon ausgehen, das jeder `seed()`-Aufruf der innerhalb einer Sekunde passiert, zur gleichen Zahlenfolge führt.

Du hast das Problem bereits prozedural gelöst, bloss syntaktisch umständlicher als es sein müsste. Funktionen in eine Klasse zu stecken ergibt nicht automatisch eine objektorientierte Lösung, dazu muss man das schon tatsächlich objektorientiert strukturieren. OOP ist keine Spracheigenschaft, sondern eine Gedankenmodell wie man Daten, Operationen, und den Programmfluss strukturiert. Das geht mit Sprachen die keine spezielle Unterstützung für OOP haben nicht so gut, auf der anderen Seite halten einen Sprachen mit spezieller Unterstützung nicht davon ab nicht-OOP-Programme zu schreiben obwohl man Sachen wie Syntax für Klassen verwendet.
Antworten