Frage eines Anfängers

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.
gulash
User
Beiträge: 1
Registriert: Donnerstag 27. April 2017, 18:15

Hallo, ich bin sowohl in diesem Forum als auch in der Python-Programmierung neu. Darum soll es jetzt aber garnicht gehen, nämlich um folgendes: Wie gesagt bin ich noch ziemlich bei den Anfängen, habe ein Buch welches eig. gut und ausführlich erklärt. Aufgaben sind auch enthalten, nur folgende machen mir sehr zu schaffen:

1. Man soll eine Funktion zeichne(figur, laenge) definieren, die entweder ein Dreieck oder ein Quadrat mit gewünschter Seitenlänge zeichnet. Ein möglicher Aufruf soll so aussehen: figur("quadrat", 150) (Oder anstelle von "quadrat" eben "dreieck")

2. Man soll ein Programm schreiben, dei dem der User eine Zahl zwischen 1 und 100 eingibt und diese dann als Zahlwort ausgegen wird. (Also z.B. der Nutzer gibt 43 ein und es wird "dreiundvierzig" ausgegeben.

Ich frag mich dabei ob ich einen gewaltigen Logikfehler habe und fürs Programmieren einfach nicht gemacht wurde oder ich mit meinem bisherigen Wissen diese Aufgaben einfach noch nicht lösen kann. Die ganzen "Standardanweisungen" wie eben print() input(), if - else, def(), .format() wurden schon erklärt, aber ich komme damit einfach nicht weiter.

Wäre euch sehr dankbar wenn mir jemand erklären könnte wie auch auch von mir aus nur eine der Aufgaben lösen kann.

Mfg,
Gulash
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@gulash: fang einfach mal an, aufzuschreiben, wie Du jemandem erklären würdest, das Problem mit einem Blatt Papier Schritt für Schritt zu lösen.
Zizibee
User
Beiträge: 229
Registriert: Donnerstag 12. April 2007, 08:36

Hallo gulash willkommen im Forum,

hast du schon mal hier nach Lösungen für deine Aufgaben geschaut? Erst letztens gab es einen Beitrag, der alles für deine Aufgabe 2 behandelt hat.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

gulash hat geschrieben:1. Man soll eine Funktion zeichne(figur, laenge) definieren, die entweder ein Dreieck oder ein Quadrat mit gewünschter Seitenlänge zeichnet. Ein möglicher Aufruf soll so aussehen: figur("quadrat", 150) (Oder anstelle von "quadrat" eben "dreieck")
Da fehlt eine Angabe, nämlich womit, also mit welcher Grafikbibliothek, soll man zeichnen?

Hier wäre eine Lösung für tkinter mit python3

Code: Alles auswählen

# -- python 3 ------
import tkinter as tk
from threading import Thread


class Application(tk.Tk):

    def __init__(self,**kwargs):
        tk.Tk.__init__(self,**kwargs)
        self.canvas = tk.Canvas(self,bg = 'white',width=400,height=400)
        self.canvas.pack()

        self.parameters = None
        self.after(1000,self.call_function)

    def zeichne(self,figur,laenge):

        if figur == "quadrat":

            x0 = 10
            y0 = 10

            x1 = x0 + laenge
            y1 = y0
            x2 = x0 + laenge
            y2 = y0 + laenge
            x3 = x0
            y3 = y0 + laenge
            
            self.canvas.create_line(x0,y0,x1,y1,x2,y2,x3,y3,x0,y0)
            # einfacher ginge es mit
            # self.canvas.create_rectangle(x0,y0,x0+laenge,y0+laenge)
            # aber mit dem Dreieck muss man es mit create_line machen

        elif figur == "dreieck":
            # selber machen
            pass

    def call_function(self):

        if self.parameters != None:

            self.canvas.delete(tk.ALL)
            self.zeichne(self.parameters[0],self.parameters[1])
            self.parameters = None

        self.after(100,self.call_function)

root = Application()

def eingabe():

    figuren = { 'q' : 'quadrat', 'd' : 'dreieck' }

    while True:

        figur_buchstabe = input("quadrat = q, dreieck = d : ")

        if figur_buchstabe == '':
            break

        if figur_buchstabe in figuren:
            figur = figuren[figur_buchstabe]
        else:
            print('\n? Fehler: falsche Eingabe\n')
            continue


        while True:

            abbruch = False

            laenge_string = input("laenge: ")

            if laenge_string == '':
                abbruch = True
                break

            try:
                laenge = int(laenge_string)
                break

            except ValueError:
                print('\n? Fehler: das war keine Zahl\n')

        if abbruch:
            break
        
        print()
        root.parameters = (figur,laenge) # zeichne
        

    root.quit()

Thread(target=eingabe).start()
root.mainloop()
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@Alfons Mittelmeyer: ein GUI-Programm mit Kommandozeileneingaben zu kombinieren ist seltsam. Eingaben von einem Thread aus zu machen, riskant. Die eingabe-Funktion enthält viel zu viele Leerzeilen und zu viel Code. Ein paar mehr Funktionen könnten nicht schaden, dann braucht es auch keine Flags oder continue. Parameter aus anderen Threads heraus zu setzen ist ein Fehler. Dafür gibt es Queues.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Sirius3 hat geschrieben:Parameter aus anderen Threads heraus zu setzen ist ein Fehler. Dafür gibt es Queues
In diesem Falle braucht es aber keine Queue, denn der Benutzer kann gewiß nicht so schnell eine neue Eingabe machen, wie die Figur gezeichnet wird. Ganz gewiß macht der Benutzer keine neue Eingabe in einer Zehntelsekunde!

Außerdem ist die Eingabe nur zum Testen gedacht
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@Sirius3: so übersichtlicher?

Code: Alles auswählen

# -- python 3 ------
import tkinter as tk
from threading import Thread

class Application(tk.Tk):

    def __init__(self,**kwargs):
        tk.Tk.__init__(self,**kwargs)
        self.canvas = tk.Canvas(self,bg = 'white',width=400,height=400)
        self.canvas.pack()

        self.parameters = None
        self.after(1000,self.call_function)

    def zeichne(self,figur,laenge):

        if figur == "quadrat":

            x0 = 10
            y0 = 10

            x1 = x0 + laenge
            y1 = y0
            x2 = x0 + laenge
            y2 = y0 + laenge
            x3 = x0
            y3 = y0 + laenge
            
            self.canvas.create_line(x0,y0,x1,y1,x2,y2,x3,y3,x0,y0)
            # einfacher ginge es mit
            # self.canvas.create_rectangle(x0,y0,x0+laenge,y0+laenge)
            # aber mit dem Dreieck muss man es mit create_line machen

        elif figur == "dreieck":
            # selbermachen
            pass

    def call_function(self):

        if self.parameters != None:
            self.canvas.delete(tk.ALL)
            self.zeichne(self.parameters[0],self.parameters[1])
            self.parameters = None

        self.after(100,self.call_function)

root = Application()

def eingabeschleife():
    figuren = { 'q' : 'quadrat', 'd' : 'dreieck' }

    while True:

        while True:
            figur_buchstabe = input("quadrat = q, dreieck = d : ")
            if figur_buchstabe == '':
                return
            if figur_buchstabe in figuren:
                figur = figuren[figur_buchstabe]
                break
            else:
                print('\n? Fehler: falsche Eingabe\n')

        while True:
            laenge_string = input("laenge: ")
            if laenge_string == '':
                return
            try:
                laenge = int(laenge_string)
                break
            except ValueError:
                print('\n? Fehler: das war keine Zahl\n')

        print()
        root.parameters = (figur,laenge) # zeichne

def eingabe():
    eingabeschleife()
    root.quit()

Thread(target=eingabe).start()
root.mainloop()
Durch die Funktion eingabeschleife kann man sich das Flag ersparen, weil man auch aus der inneren Schleife heraus durch return abbrechen kann. Die erste innere Schleife ist nur wegen der Symmetrie zur zweiten, weil das dann übersichtlicher ist.
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

@gulash: der hier präsentierte Code ist wieder einmal mit Vorsicht zu genießen. Zum einen sind Threads nichts für Anfänger, denn ein threadsicheres Programm zu schreiben ist nicht leicht. Und wenn es dennoch um Threads geht, dann sollte als erstes gezeigt werden, warum der Zugriff mehrerer Threads auf einen gemeinsamen Namensraum ohne Locks schlecht ist, wo die Vorzüge aber auch Probleme von Locks liegen, und welche Lösungen es zur Kommunikation gibt, z.B. Queues. Auf die Nutzung von Threads solltest Du zunächst besser verzichten.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@Alfons Mittelmeyer: es geht hier doch nicht darum, wie schnell jemand tippen kann, sondern dass generell keine gemeinsamen Zustände zwischen verschiedenen Threads existieren dürfen. Ähnlich, wie dass man bei globalen Variablen keinen Überblick hat, wer was wann ändert, ist bei Threads mit gemeinsamen Zuständen nicht nachzuvollziehen wer wann was ändert. Zudem ist root eine globale Variable, siehe anderen Thread. root.quit darf auch aus dem Eingabethread heraus nicht aufgerufen werden!
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Sirius3 hat geschrieben:@Alfons Mittelmeyer: es geht hier doch nicht darum, wie schnell jemand tippen kann, sondern dass generell keine gemeinsamen Zustände zwischen verschiedenen Threads existieren dürfen.
Warum alles als generell ansehen und daraus ein Dogma machen? Es ging nur darum, dass der User gulash es ausprobieren kann. Und ihn auch noch mit Queues und events zu konfrontieren sprengt den Rahmen für einen Anfänger.

Wichtig ist jedenfalls:

Man darf von einem Thread keine Funktionen eines anderen Threads aufrufen. Und normalerweise auch auf keine Variablen des anderen Threads zugreifen. Im vorliegenden Fall macht es nichts aus, weil man nicht so schnell neue Eingabe machen kann, und wenn es vielleicht hier in diesem Fall doch crashen würde? Na und? Ist eh nur zum Ausprobieren. Jedenfalls besser, als das Programm jedes mal neu zu starten.

@gulash, wenn Du mittels Programm mehr in den Canvas hineinschreiben wolltest, dann bitte nicht mit dem Verfahren, das für die Eingabe benutzt wurde, sondern in der __init__ Methode von class Application!!!
Sirius3 hat geschrieben:root.quit darf auch aus dem Eingabethread heraus nicht aufgerufen werden!
Besser als zweimal Strg C zu drücken.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Alfons Mittelmeyer hat geschrieben:Es ging nur darum, dass der User gulash es ausprobieren kann. Und ihn auch noch mit Queues und events zu konfrontieren sprengt den Rahmen für einen Anfänger.
Warum soll man dem Fahranfänger zeigen, wo die Bremse ist? Das macht das Fahren für den Anfang nur kompliziert. Solange er weiß, wo das Gaspedal ist, reicht das ja, weil so kann er wenigstens mal losfahren. Wird schon kein Auto an der nächsten Kreuzung kommen.
BlackJack

@Alfons Mittelmeyer: GUI-Rahmenwerke sind in der Regel nicht thread-safe. Man darf also den Zustand der GUI nur aus dem Thread ändern, in dem auch die GUI-Hauptschleife läuft. Anfängern keine kaputten Programme mit der Bemerkungen wie „Na und? Ist eh nur zum ausprobieren…“ zu zeigen ist kein Dogma. Insbesondere wenn es nicht deterministisch zu Problemen führt, und der Anfänger das somit nicht gleich merkt das das Murks ist.

`root.quit()` ist nicht besser als zweimal Strg+C zu drücken (warum eigentlich 2×?) weil beim Ausführen der `quit()`-Funktion potentiell zwei Threads gleichzeitig auf die Interna von Tk/Tcl zugreifen was zu Fehlern führen kann und somit falsch ist.

Wenn Queues und Ereignisse für Anfänger zu viel sind, dann sind es auch Threads. Denn mit Threads kann man nicht sinnvoll etwas anfangen ohne die Probleme und deren Lösungen zu kennen die man sich mit nebenläufiger Programmierung einfangen kann. Eben insbesondere weil das alles nicht mehr deterministisch ist und man Fehler erst viel später bemerkt wo es dann sehr viel Arbeit machen kann alles so umzuschreiben das es tatsächlich funktioniert.
Benutzeravatar
noisefloor
User
Beiträge: 3854
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

eine Dreieck als ASCII-Art:

[codebox=pycon file=Unbenannt.txt]>>> def draw_triangle(edge_length):
... for i in range(edge_length):
... current_line = '/{}\\'.format(' '*i*2)
... print('{:^{edge_length}}'.format(current_line, edge_length=edge_length*2))
... print('-'*edge_length*2)
...
>>> draw_triangle(10)
/\
/ \
/ \
/ \
/ \
/ \
/ \
/ \
/ \
/ \
--------------------
>>>
[/code]

Das Quadrat ist ja ein bisschen einfacher, weil man da nicht auf die Zentrierung achten braucht.

Gruß, noisefloor
BlackJack

@noisefloor: Wobei das nicht gleichschenklig ist, oder nur unter gewissen Rahmenbedingungen die eher unwahrscheinlich sind. ;-)
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

@Blackjack: OK, dann mit sicherer Queue und keine globalen Variablen

Code: Alles auswählen

# -- python 3 ------
import tkinter as tk
from threading import Thread
import queue
from math import sqrt


class Application(tk.Tk):

    def __init__(self,app_queue,**kwargs):
        tk.Tk.__init__(self,**kwargs)

        self.queue = app_queue
        self.canvas = tk.Canvas(self,bg = 'white',width=400,height=400)
        self.canvas.pack()
        self.execute_queue()

    def execute_queue(self):
        while not self.queue.empty():
            self.queue.get()()
        self.after(100,self.execute_queue)

    def zeichne(self,figur,laenge):

        if figur == "quadrat":

            x0 = 10
            y0 = 10

            x1 = x0 + laenge
            y1 = y0
            x2 = x0 + laenge
            y2 = y0 + laenge
            x3 = x0
            y3 = y0 + laenge
            
            self.canvas.create_line(x0,y0,x1,y1,x2,y2,x3,y3,x0,y0)
            # einfacher ginge es mit
            # self.canvas.create_rectangle(x0,y0,x0+laenge,y0+laenge)
            # aber mit dem Dreieck muss man es mit create_line machen

        elif figur == "dreieck":
            # eigentlich selbermachen
            x0 = 10
            ybase = 10

            y0 = ybase + laenge*sqrt(3)/2
            x1 = x0 + laenge
            y1 = y0
            x2 = x0 + laenge/2
            y2 = ybase
            self.canvas.create_line(x0,y0,x1,y1,x2,y2,x0,y0)

def eingabeschleife(app_queue,root):
    figuren = { 'q' : 'quadrat', 'd' : 'dreieck', 'e' : 'erase' }

    while True:

        while True:
            figur_buchstabe = input("quadrat = q, dreieck = d, e = erase : ")
            if figur_buchstabe == '':
                return
            if figur_buchstabe in figuren:
                figur = figuren[figur_buchstabe]
                break
            else:
                print('\n? Fehler: falsche Eingabe\n')

        if figur == 'erase':
            app_queue.put(lambda : root.canvas.delete(tk.ALL))
            print()
            continue

        while True:
            laenge_string = input("laenge: ")
            if laenge_string == '':
                return
            try:
                laenge = int(laenge_string)
                break
            except ValueError:
                print('\n? Fehler: das war keine Zahl\n')

        print()
        app_queue.put(lambda figur = figur, laenge = laenge: root.zeichne(figur,laenge))
        

def eingabe(app_queue,root):
    eingabeschleife(app_queue,root)
    app_queue.put(lambda : root.quit())

def main():
    app_queue = queue.Queue()
    root = Application(app_queue)
    Thread(target=eingabe,args=(app_queue,root)).start()
    root.mainloop()

main()
Benutzeravatar
noisefloor
User
Beiträge: 3854
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,
BlackJack hat geschrieben:@noisefloor: Wobei das nicht gleichschenklig ist, oder nur unter gewissen Rahmenbedingungen die eher unwahrscheinlich sind. ;-)
Du meinst "gleichseitig", oder? Gleichschenklig ist es. Gleichseitig ist mit ASCII-Zeichensatz in der Tat ein wenig schwierig.

Gruß, noisefloor
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@Alfons Mittelmeyer: Du schaffst es tatsächlich eine Queue zu benutzen und trotzdem root als threaduberschreitende Variable zu benutzen. In die Queue gehören die Variablen und nicht die Methoden, die Auflösung gehört in execute_queue.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Sirius3 hat geschrieben:@Alfons Mittelmeyer: Du schaffst es tatsächlich eine Queue zu benutzen und trotzdem root als threaduberschreitende Variable zu benutzen. In die Queue gehören die Variablen und nicht die Methoden, die Auflösung gehört in execute_queue.
Dann wohl besser so?

Code: Alles auswählen

# -- python 3 ------
import tkinter as tk
from threading import Thread
import queue
from math import sqrt


class Application(tk.Tk):

    def __init__(self,app_queue,**kwargs):
        tk.Tk.__init__(self,**kwargs)

        self.queue = app_queue
        self.canvas = tk.Canvas(self,bg = 'white',width=400,height=400)
        self.canvas.pack()
        self.execute_queue()

    def execute_queue(self):
        while not self.queue.empty():
            exec(self.queue.get())
        self.after(100,self.execute_queue)

    def zeichne(self,figur,laenge):

        if figur == "quadrat":

            x0 = 10
            y0 = 10

            x1 = x0 + laenge
            y1 = y0
            x2 = x0 + laenge
            y2 = y0 + laenge
            x3 = x0
            y3 = y0 + laenge
            
            self.canvas.create_line(x0,y0,x1,y1,x2,y2,x3,y3,x0,y0)
            # einfacher ginge es mit
            # self.canvas.create_rectangle(x0,y0,x0+laenge,y0+laenge)
            # aber mit dem Dreieck muss man es mit create_line machen

        elif figur == "dreieck":
            # eigentlich selbermachen
            x0 = 10
            ybase = 10

            y0 = ybase + laenge*sqrt(3)/2
            x1 = x0 + laenge
            y1 = y0
            x2 = x0 + laenge/2
            y2 = ybase
            self.canvas.create_line(x0,y0,x1,y1,x2,y2,x0,y0)

def eingabeschleife(app_queue):
    figuren = { 'q' : 'quadrat', 'd' : 'dreieck', 'e' : 'erase' }

    while True:

        while True:
            figur_buchstabe = input("quadrat = q, dreieck = d, e = erase : ")
            if figur_buchstabe == '':
                return
            if figur_buchstabe in figuren:
                figur = figuren[figur_buchstabe]
                break
            else:
                print('\n? Fehler: falsche Eingabe\n')

        if figur == 'erase':
            app_queue.put("self.canvas.delete(tk.ALL)")
            print()
            continue

        while True:
            laenge_string = input("laenge: ")
            if laenge_string == '':
                return
            try:
                laenge = int(laenge_string)
                break
            except ValueError:
                print('\n? Fehler: das war keine Zahl\n')

        print()
        format_string = "self.zeichne('{0}',{1})".format(figur,laenge)
        print(format_string)
        app_queue.put(format_string)
        

def eingabe(app_queue):
    eingabeschleife(app_queue)
    app_queue.put('self.quit()')

def main():
    app_queue = queue.Queue()
    Thread(target=eingabe,args=(app_queue,)).start()
    Application(app_queue).mainloop()

main()
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

@Alfons Mittelmeyer: Du schaffst es immer noch, eins draufzusetzen. Dann doch lieber die vorherige Version.
Benutzeravatar
kbr
User
Beiträge: 1487
Registriert: Mittwoch 15. Oktober 2008, 09:27

Sirius3 hat geschrieben:@Alfons Mittelmeyer: Du schaffst es immer noch, eins draufzusetzen. Dann doch lieber die vorherige Version.
In gewisser Weise ist dies ja schon kreativ ...
@gulash, sofern Du noch nicht erfolgreich vertrieben bist: don't do this at home and especially not at work. Dieser Code ist mal wieder ... Mist.
Antworten