@steveroch_rs: Ein paar Anmerkungen zum Code:
Sternchenimporte sollte man vermeiden. Es wird dann schnell unübersichtlich welcher Name wo definiert wurde. Bei `Tkinter` holt man sich weit über 100 Namen in das Modul, von denen nur ein Bruchteil wirklich benötigt wird. Ausserdem besteht die Gefahr von Namenskollisionen.
Auf Modulebene gehört nur Code der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht normalerweise in einer Funktion die `main()` heisst. Damit kann man dann nicht mehr einfach so auf globale Variablen zugreifen → Werte betreten Funktionen und Methoden als Argumente und verlassen sie als Rückgabwerte.
Das bedeutet für `draw_dot()` das dort nicht einfach auf magische Weise von der Existenz von `canvas`, `x_for_guidots`, und `x_for_guidots` ausgegangen werden kann/darf, sondern das diese Werte als Argumente übergeben werden müssen.
Der Name `dot` wird in der Funktion zwar an einen Wert gebunden, dann aber nicht weiter benutzt, genau wie `arc` im Hauptprogramm. Das ``return`` am Ende der Funktion ist überflüssig.
Die eigentliche Programmlogik und die GUI sind vermischt — das sollten sie nicht sein. Zum Beispiel sollte die Programmlogik nicht mit GUI-Koordinaten arbeiten die dann in Werte für die Programmlogik umgerechnet werden sondern umgekehrt sollten die eigentlich interessanten Werte für die GUI umgerechnet werden. Denn wenn man das sauber trennt, dann weiss die Programmlogik gar nichts von der GUI und damit wäre es komisch wenn die Programmlogik GUI-Koordinaten erzeugt. Das Umrechnen kann und sollte man in einer GUI-Funktion erledigen und `draw_dot()` bietet sich da geradezu an.
Die äussere ``while True``-Schleife ist fehlerhaft weil das Programm nach schliessen des Fensters weiter in dieser Schleife läuft, dann aber Probleme bekommt, zum Beispiel weil das Hauptfenster nicht mehr vorhanden ist und somit auch kein neues `Canvas`-Objekt in dem nicht mehr vorhandenen Hauptfenster erzeugt werden kann. Die Schleife sollte wohl eigentlich nur die Benutzereinngabe am Anfang umfassen um sicherzustellen das der Benutzer eine sinnvolle Iterationanzahl eingibt.
Die ``continue``-Anweisungen haben allesamt keinen sichtbaren Effekt, weil die Schleife auch ohne diese Anweisungen von vorne beginnt ohne das vorher noch etwas passiert. ``continue`` verwende ich persönlich auch äusserst ungern weil das ein unbedingter Sprung an den Schleifenanfang ist den man nicht an der Struktur/Einrückung des Quelltextes sehen kann und es wird schwieriger Codeteile die ein ``continue`` enthalten in eigene Funktionen heraus zu ziehen. Ausserdem kann man ans Ende der Schleife keinen Code schreiben (jedenfalls nicht so leicht) der in jedem Fall vor dem nächsten Schleifendurchlauf ausgeführt wird, weil das von einem ``continue`` umgangen wird.
`loops` und `counter` sind irgendwie redundant und statt einer ``while``-Schleife würde sich hier eine ``for``-Schleife eher aufdrängen.
Einbuchstabige Namen sind selten sprechend genug. Ausnahmen sind ganzzahlige Schleifenzähler (`i`, `j`, `k`) oder Koordinaten (`x`, `y`) weil das aus der Mathematik bekannt ist, aber `h` für die Anzahl der Treffer ist nicht selbsterklärend.
Das initialisieren des Zufallsgenerators ist nicht nur überflüssig sondern unter bestimmten Umständen sogar kontraproduktiv. `random.seed()` ist ausserhalb des `random()`-Moduls eher für das Gegenteil von total (pseudo)zufälligen Werten gedacht, nämlich wenn man für jeden Lauf die selbe Folge von Zufallswerten braucht, kann man mit einem konkreten Wert als Argument genau das sicherstellen.
Etwas wie die `Canvas`-Grösse und davon abhängige Werte sollten nicht als ”magische” Zahlen überall im Programm verstreut stehen. Das definiert man an einer Stelle im Programm und bezieht sich dann im Rest des Quelltextes darauf. So kann man die grösse bei Bedarf an einer einzigen Stelle ändern ohne das gesamte Programm absuchen zu müssen, immer mit der Gefahr das man etwas übersieht oder inkonsistent ändert.
Ich lande dann als Zwischenergebnis ungefähr bei so etwas:
Code: Alles auswählen
# !/usr/bin/python
# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, print_function
import time
import Tkinter as tk
from math import sqrt
from random import random
def draw_dot(canvas, factor, x, y):
x, y = x * factor, y * factor
canvas.create_line(x, y, x, y + 1, fill='red')
def main():
while True:
print(
'Pi will be calculated more precisely the more iterations you'
' select.\nIt will also take longer!'
)
sample_count = raw_input(
'Please select number of iterations (1 - 1,000,000): '
)
print()
if not sample_count.isdigit():
print('You must enter an integer value!')
sample_count = int(sample_count)
if sample_count < 1:
print('Please select at least one iteration!')
elif sample_count > 1000000:
print('The maximum amount of iterations is 1 million!')
else:
break
print('calculating...')
print()
start_time = time.clock()
root = tk.Tk()
root.title('Monte-Carlo Simulation for Pi')
size = 500
canvas = tk.Canvas(root, height=size, width=size, bg='grey')
hit_count = 0
for _ in xrange(sample_count):
x, y = random(), random()
draw_dot(canvas, size, x, y)
hit_count += int(sqrt(x**2 + y**2) <= 1.0)
pi = 4 * (hit_count / sample_count)
canvas.create_arc(-size, 3, size - 3, size * 2, outline='green')
canvas.pack()
print()
print('Pi:', pi)
print()
print('Time to calculate result: ', time.clock() - start_time, 'seconds')
print()
root.mainloop()
if __name__ == '__main__':
main()
Was hier jetzt noch problematisch ist, ist die Vermischung zwischen der Simulation und der GUI. Die Simulation sollte ohne die GUI funktionieren. Dann könnte man sie auch in einen Thread auslagern oder so gestalten das die GUI sie schritt- oder ”batch”-weise mittels `after()`/`after_idle()`-Methode ausführt. Dazu wäre für eine saubere Lösung dann auch objektorientierte Programmierung nötig.