My Rock, Paper, Stone

Stellt hier eure Projekte vor.
Internetseiten, Skripte, und alles andere bzgl. Python.
Benutzeravatar
andie39
User
Beiträge: 152
Registriert: Dienstag 7. Dezember 2021, 16:32

Hallo.

Wie hier schon geschrieben:
viewtopic.php?f=1&t=53650

Schreibe ich mein erstes Programm.
Rock, Paper, Stone.
Also nichts besonderes.

Ich habe es nur aus dem Kopf ohne spicken geschrieben um zu sehen ob ich das mit dem bisherigen Wissen schaffe.

Das hat auch geklappt, mehrere Fehlermeldungen aber ausser der if/else Sache konnte ich alles debuggen.

Hier die erste Version:

Kommentare sind nicht drin und der Code ist vermutlich auch nicht gut aber es läuft:

Code: Alles auswählen

import random

print("Welcome to Rock, Paper Scissors")
print()
print()
print("Please choose")
print()
print("Press r for Rock")
print("Press p for Paper")
print("Press s for Scissors")


choice = input("You have choosen ")
computer=["s", "p", "r"]
print()

print("Computer's turn........")


computer_choice = random.choice(computer)
print("The computer has choosen " + computer_choice)

def win():
	print("You win")
def lost():
	print("You lost")
 
if choice in computer:
	if choice == computer_choice:
		print("Draw, try again")
	elif choice =="r" and computer_choice =="p":
		win()
	elif choice == "r" and computer_choice == "s":
		win()
	elif choice == "p" and computer_choice == "r":
	  lost()
	elif choice == "p" and computer_choice == "s":
	  lost()
	elif choice == "s" and computer_choice == "r":
	  lost()
	elif choice == "s" and computer_choice == "p":
		win()
else:
		print("Wrong input")

Als nächstes will ich aus der Grundstruktur Verbesserungen einbauen:

Zum Beispiel soll die Eingabe und Abgleich funktionieren wenn der Buchstabe der Eingabe als Großbuchstabe geschrieben wird.
Wie weiss ich aber noch nicht.

Auch würde ich gerne einen Timer einbauen, der ein paar Sekunden verstreichen lässt also quasi:

Computer‘s turn…..
*timer X Sekunden*

Computer has choosen..
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Eingerückt wird immer mit 4 Leerzeichen pro Ebene, man mischt niemals Tabs mit Leerzeichen. So ist der Code viel zu unstrukturiert.
Man mischt nicht Code der sofort ausgeführt wird und Funktionsdefinitionen durcheinander. Alles gehört in Funktionen.
Die Variable `computer` hat den falschen Namen. `if choice in computer` liest sie so als völlig unsinniger Satz.
Man sollte immer Rock auswählen, denn so ist die Chance, zu gewinnen, am größten.
Benutzeravatar
andie39
User
Beiträge: 152
Registriert: Dienstag 7. Dezember 2021, 16:32

Danke für das Feedback.

1) Tab und 4 Leerzeichen sind doch gleich oder nicht?
Es ist doch strukturiert untereinander oder?

2) Ja das stimmt Computer kommt noch aus einer vorherigen angefangen Version.
Es funktioniert natürlich aber ist unschön das stimmt.

3) „ Man mischt nicht Code der sofort ausgeführt wird und Funktionsdefinitionen durcheinander. Alles gehört in Funktionen.“
Was meinst du genau? Wenn ich Fragen darf.
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@andie39: Ad 1) Ein Tabulatorzeichen sagt „bis zum nächsten Tabstop“ und was das bedeutet ist nicht fest definiert. Oft sind die alle 8 Zeichen, manchmal aber auch 4, 5, oder 10, oder 14, oder sogar gar nicht regelmäßig. Kommt halt darauf an wo das interpretiert wird und was dort dann entweder die Voreinstellung oder die Benutzereinstellung ist. Firefox geht von einem Tabstop alle 8 Zeichen aus und man kann das soweit ich weiss auch nirgends ändern. Jedenfalls nicht über eine öffentlich angebotene Schnittstelle.

Linux-Terminals gehen in der Regel auch von einem Tabstop alle 8 Zeichen aus. Das kann man mit dem ``tabs``-Programm ändern. Auch unregelmässig, in dem man eine Liste mit den Positionen der Tabstops angibt.

Strukturiert untereinander sieht das bei mir auch nicht unbedingt aus. Die `lost()`-Aufrufe sind anders eingerückt als die `win()`-Aufrufe, und die Einrückung des letzten `print()`-Aufrufs passt auch nicht so ganz dazu, dass der eigentlich auf der gleichen Ebene angesiedelt ist wie die ``if``-Anweisungen im ``if``-Zweig davor und nicht wie die `win()`-Aufrufe. Optisch sieht das jetzt aber so aus. Also zumindest wenn man Tabstops alle 8 Zeichen annimmt, wie das bei mir im Firefox ist.

Ad 3) Du hast da ja offensichtlich Code der ausserhalb von Funktionen steht. Sollte nicht sein. Denn der wird ausgeführt wenn man das Modul importiert. Das sollte man machen können ohne das mehr passiert als Konstanten, Klassen, und Funktionen zu definieren. Zum Beispiel um einzelne Funktionen mal interaktiv ausprobieren zu können. Oder Werkzeuge verwenden zu können die Module importieren um Dokumentation daraus zu erstellen. Oder automatisierte Tests. Und alles was auf Modulebene definiert ist, ist überall im Modul sichtbar, was auf globale Variablen hinaus läuft, was auch Böse™ ist.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Benutzeravatar
andie39
User
Beiträge: 152
Registriert: Dienstag 7. Dezember 2021, 16:32

Ah ok.

Also immer 4 Leerzeichen machen wie schon von Sirius3 erwähnt.

Jetzt sehe ich es auch das es doch nicht so sauber dasteht.

Aber welcher Code ist außerhalb von Funktionen?
Das ist mir jetzt als letztes noch nicht klar.
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Na fast aller. Die ganzen Print statements. Die ganze Logik. Du hast doch nur zwei kleine Funktionen, die kaum was machen.
Benutzeravatar
andie39
User
Beiträge: 152
Registriert: Dienstag 7. Dezember 2021, 16:32

Ach die def Funktionen hättren ganz nach oben gepackt werden sollen?quasi nach import random?
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@andie39: Code ausserhalb von Funktionen steht nicht *in einer Funktion*. Davon hast Du zwei Stück. `win()` und `loose()`. Alles andere ist ausserhalb davon. Du kannst dafür keine Funktion aufrufen, das läuft einfach so ab.

Ungetestet:

Code: Alles auswählen

#!/usr/bin/env python3
import random


def win():
    print("You win.")


def lose():
    print("You lose.")


def main():
    print("Welcome to Rock, Paper, Scissors")
    print()
    print()
    print("Please choose")
    print()
    print("Press r for Rock")
    print("Press p for Paper")
    print("Press s for Scissors")

    choice = input("You have choosen ")
    choices = ["s", "p", "r"]
    print()

    print("Computer's turn...")

    computer_choice = random.choice(choices)
    print("The computer has choosen", computer_choice)
    if choice in choices:
        if choice == computer_choice:
            print("Draw, try again")
        elif choice == "r" and computer_choice == "p":
            win()
        elif choice == "r" and computer_choice == "s":
            win()
        elif choice == "p" and computer_choice == "r":
            lose()
        elif choice == "p" and computer_choice == "s":
            lose()
        elif choice == "s" and computer_choice == "r":
            lose()
        elif choice == "s" and computer_choice == "p":
            win()
        else:
            assert (
                False
            ), f"unexpected choices {choice!r} and {computer_choice!r}"
    else:
        print("Wrong input")


if __name__ == "__main__":
    main()
Wenn etwas mit einem ``elif`` endet, macht es oft Sinn sich zu überlegen was da passieren müsste wenn es ein ``else`` gäbe. Oft ist das ein Zustand der eigentlich nie eintreten dürfte, wenn man denn keine Fehler gemacht hat. Das ist eine gute Gelegenheit für ``assert`` um sicherzustellen, dass das nicht einfach geräuschlos passieren kann falls man doch einen Fehler gemacht hat.

Die Ausgaben waren uneinheitlich. Entweder "win"/"lose" oder "won"/"lost". Ich habe das "lost" mal in "lose" geändert, auch bei der Funktion, denn die werden üblicherweise nach der Tätigkeit benannt, die sie durchführen, und `lost()` ist keine Tätigkeit.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Benutzeravatar
andie39
User
Beiträge: 152
Registriert: Dienstag 7. Dezember 2021, 16:32

Danke für die Hilfe.

Ich muss sagen, dass ich in meinem Onlinekurs noch gar nicht bei Funktionen war.
Ich hatte nur bei anderen Codes das gesehen und mir gedacht weil es oft vorkommt ist es sehr wichtig und dann per Google nachgesehen was es ist.

Ich schaue mir aktuell die ganzen Grundlagen nochmal an und vertiefe diese. Man sieht ja hier das manches mir noch unklar sind. Und ohne feste Grundlagen macht das kaum Sinn.
Dann kam mir der Sinn die immer wiederkehrende Ausgabe Sieg oder Niederlage dahingehend zu definieren.

Du hast hier jetzt den Hauptteil selber als Funktion definiert „main“
Erstmal ist das für dieses Programm ja nicht nötig damit es läuft.

Warum wird der Teil als Funktion definiert zumal die Win und Lose ja auch voher angesprochen werden?

Folgende Punkte sind mir auch nicht klar weil ich diese auch noch nicht hatte:

else:
assert (
False
), f"unexpected choices {choice!r} and {computer_choice!r}"

Welchen Zweck hat das?

Ebenso das hier:

if __name__ == "__main__":
main()

Das scheint ja die Funktion main auszulösen aber mit welcher Bedingung verstehe ich nicht.
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das assert hat __blackjack__ doch erklaert: er faengt damit ab, ob ein eigentlich nicht zu erreichender Fall trotzdem eintritt, und dadurch erzeugt das eine aussagekraeftige Fehlermeldung.

Und auch warum man das alles in Funktionen packt hat __blackjack__ schon ausgefuehrt. Punkt 3 oben. Bitte nochmal lesen.

Der letzte Teil mit __name__ etc ist der "__main__-Guard", der dafuer sorgt, dass die main-Funktion ausgefuehrt wird, wenn man das Skript "normal" startet. Statt es eben zu importieren etc.
Benutzeravatar
andie39
User
Beiträge: 152
Registriert: Dienstag 7. Dezember 2021, 16:32

Ja sorry, für mich ist das nicht immer gleich zu verstehen.

Das assert ist dafür da, falls etwas passiert, dass eigentlich nicht passieren kann und gibt einen Fehler aus.
War für mich hier einfach schwer nachvollziehbar, da die else Funktion ja nicht ausgeführt werden kann.
Man kann ja nichts falsches eingeben.
Für mich nicht immer "logisch".

Punkt 3 habe ich gelesen, ich gebe aber zu: Für mich aktuell nicht nachvollziehbar.
Ich verstehe es an diesem Punkt leider einfach nicht.

ICh verstehe auch ehrlich:
"Der letzte Teil mit __name__ etc ist der "__main__-Guard", der dafuer sorgt, dass die main-Funktion ausgefuehrt wird, wenn man das Skript "normal" startet. Statt es eben zu importieren etc." nicht

Normal ausführen ist für mich ausführen und dann wird das Programm doch auch ohne die Main Funktion gestartet.
Ich weiß nicht was hier mit importieren gemeint ist.
Aber das wird mangelndes Vorwissen sein.

Sprich ich werde den Kurs und das Buch weiter langsam abarbeiten, dann wird es hoffentlich entsprechend erklärt werden.
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@andie39: Wieso kann man nichts falsches Eingeben? Dabei gehst Du ja schon davon aus, dass der Code fehlerfrei ist. Und das auch in Zukunft niemand, beispielsweise aus dem Schere, Stein, Papier ein Schere, Stein, Papier, Spock, Echse macht und dabei Fehler macht. Das beim Programmieren keine Fehler gemacht werden, wird von der Realität immer wieder anders gesehen. 😉 Und genau dafür gibt es ``assert``, um im Programm einerseits Invarianten und Sachen die niemals passieren dürften zu ”dokumentieren”, und auch gleichzeitig zur Laufzeit überprüfen zu können.

Das ``else`` kann dann erreicht werden wenn irgend etwas in den Bedingungen davor fehlerhaft ist. Zum Beispiel wenn man sich bei irgendeiner Bedingung vertippt hat, oder bei Werten die dort benutzt werden, nicht an alle Möglichkeiten gedacht hat. Klassisches Beispiel

Code: Alles auswählen

In [249]: def test(x): 
     ...:     if x == 0: 
     ...:         print("Null") 
     ...:     elif x > 0: 
     ...:         print("grösser") 
     ...:     elif x < 0: 
     ...:         print("kleiner") 
     ...:     else: 
     ...:         assert False, f"unerwarteter Wert: {x!r}" 
     ...:                                                                       

In [250]: test(0)                                                               
Null

In [251]: test(-42)                                                             
kleiner

In [252]: test(47.11)                                                              
grösser
Jetzt sollte man ja meinen man könnte da keine Zahl übergeben die bis zum ``else`` kommt, weil ja jede Zahl entweder 0 oder grösser oder kleiner als 0 ist. Tja, ausser der Gleitkommawert „not a number“:

Code: Alles auswählen

In [253]: test(math.nan)                                                    
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
<ipython-input-253-3698fae13965> in <module>
----> 1 test(math.nan)

<ipython-input-249-5f99d12e49aa> in test(x)
      7         print("kleiner")
      8     else:
----> 9         assert False, f"unerwarteter Wert {x!r}"
     10 

AssertionError: unerwarteter Wert: nan
Selbst wenn man das nicht um Spock und Echse erweitert, würden mir zwei Änderungen einfallen die man am momentanen Code machen könnte und die beide natürlich die Möglichkeit bieten, dass man dabei Fehler macht: Statt zwei Vergleiche mit ``and`` zu verknüpfen, könnte man ein Tupel mit den Eingaben mit einem Tupel mit Werten vergleichen. Und man könnte das Tupel mit den Eingaben vor dem ``if`` erstellen und an einen Namen binden, beispielsweise `moves`:

Code: Alles auswählen

        elif choice == "r" and computer_choice == "p":
            win()
        
        # =>
        
        elif (choice, computer_choice) == ("r", "p"):
        
        # =>
        
        elif moves == ("r", "p"):
Die Bedingungen würden dadurch deutlich kürzer.

Und dann kann man auch die Anzahl der ``elif``-Zweige verkürzen, denn letztlich gibt es ja nur drei Fälle, in dem man die Fälle jeweils zusammenfasst. Mal für's Gewinnen:

Code: Alles auswählen

        elif moves == ("r", "p") or moves == ("r", "s") or moves == ("s", "p"):
            win()
Und *diese* Bedingung kann man mit einer Liste und dem ``in``-Operator kompakter ausdrücken:

Code: Alles auswählen

        elif moves in [("r", "p"), ("r", "s"), ("s", "p")]:
            win()
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Benutzeravatar
andie39
User
Beiträge: 152
Registriert: Dienstag 7. Dezember 2021, 16:32

Hmm so habe ich das noch nicht gesehen, dass macht Sinn.
Die Lernekurve ist bei mir leider noch etwas flach ;)

Es ist doch etwas schwieriger als gedacht.
Teilweise bin ich mir nur nich sicher ob es grundsätzliche Verständnisprobleme sind oder weil die Grundlagen fehlen...

Aber zu dieser Sache:

assert False habe ich jetzt verstanden.

Aber eigentlich reicht doch: assert False?
Wenn man das schreibt und der Fehler auftaucht, kommt doch die Tracebackmeldung und wo der Fehler auftritt?

f"unerwarteter Wert {x!r}"
ist ja nur eine weitere Information.
Die kann man doch als String schreiben oder nicht?

Weshalb das f am Anfang und wofür das {x!r}

?
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Wenn du tief in deinem Code bist, zu dem Zeitpunkt als er geschrieben wurde, und versuchst diesen Fall zu verhindern, dann hilft es deinem späteren selbst (oder einem anderen Programmierer) enorm, wenn da steht *warum* da eine Ausnahme ausgelöst wurde. Und welcher Wert dafür verantwortlich war. Denn das ist ja wichtig.

Mit deinem Argument bräuchte man auch nur eine Art Warnschild. Klebt ja da, wo’s gefährlich ist. Dauert dann aber länger (und ist ggf zu spät) rauszufinden, was genau denn nun gefährlich ist.

Das f”..” nennt sich Formatstring, kannst du mal recherchieren.
Benutzeravatar
andie39
User
Beiträge: 152
Registriert: Dienstag 7. Dezember 2021, 16:32

Ah das lässt Strings kürzer und einfacher einbauen als mit str und +

https://ichi.pro/de/3-tipps-zur-verbess ... 7966454574

Dann wäre der F-String auch der, den man am häufigsten einsetzen würde.
Bzw macht die ältere Variante dann überhaupt noch Sinn?
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Noe, ich nutze nach Moeglichkeit nur f-Strings.
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Die ältere Variante ist str.format, die ganz alte Variante ist %. str und + ist gar keine Variante, sondern einfach nur schlechter Programmierstil.
Benutzeravatar
andie39
User
Beiträge: 152
Registriert: Dienstag 7. Dezember 2021, 16:32

Gut das hatte ich mir gedacht.
str und + war bei mir ganz am Anfang im Kurs.
Dann ist das etwas, was ich in zukunft wieder vergessen kann und auch zu F-String übergehen kann.
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

`str.format()` macht noch Sinn, wenn man die Vorlage getrennt irgendwo definiert hat und nicht hart kodiert an der Stelle wo sie verwendet wird. Und der ``%``-Operator kann noch Sinn machen wenn die Vorlage geschweifte Klammern enthält und es mühselig wird die alle zu ”escapen”, man sich aber keine Template-Engine als Abhängigkeit dazu holen möchte.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Benutzeravatar
andie39
User
Beiträge: 152
Registriert: Dienstag 7. Dezember 2021, 16:32

Ich habe heute per Buch noch einmal die Grundlagen vertieft. Dort kam am Ende Fehlerbehandlung.

Und zwar über die Funktionen:
try, except, finally

Das wäre doch auch eine Möglichkeit teilweise anstatt assert zu verwenden oder?
Antworten