String wird plötzlich zur Funktion

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
Bumblebee38
User
Beiträge: 15
Registriert: Mittwoch 23. Mai 2018, 20:26

Hallo,

ich arbeite zurzeit daran, Tic-Tac-Toe in Python zu programmieren. Nach einiger Zeit komme ich allerdings nicht weiter, da irgendwann ein String als Funktion angesehen wird. Ich möchte, dass mehrere Strings kombiniert werden, was ja mit einer Funktion dazwischen nicht funktioniert. Ich finde jedoch den Fehler dahinter nicht.


Code: Alles auswählen

import os

######################################################################################Brett(Anfang)
def generateBoard():
    reihe = [] ##leere reihe erstellen
    for i in range(0,5): ##von 0 bis 5 wiederholend leere Spalten erzeugen
        feld = []
        for j in range(0,6): ##von 0 bis 6 wiederholend Zeilen erzeugen
            feld.append(" - ")
        reihe.append(feld) ##freie Felder generieren
    return reihe ##Funktion schließen und freie Felder ausgeben

def boardtostring(reihe): ##board mit dem Wert von den freien Feldern erstellen
    oberste_reihe = "  1  2  3  4  5  6 \n" ##leeren Inhalt erstellen
    for feld in reihe:
        oberste_reihe += "|" ##leerer Inhalt + definierter Anfang
        for element in feld:
            oberste_reihe += element ##leerer Inhalt + definierte Mitte
        oberste_reihe += "| \n" ##leerer Inhalt + definiertes Ende + Absatz
    print(oberste_reihe) ##gesamten Inhalt Ausgeben
    return oberste_reihe ##Funktion schließen und Inhalt mitnehmen

board = generateBoard() ##freie Felder als var board abspeichern
boardtostring(board) ##Funktion für Inhalt mit freien Feldern aufrufen

######################################################################################Brett(Ende)/Setzen(Anfang)

#Vorgehensweise: Input bestimmt, wo das jeweilige Zeichen eingesetzt werden soll. Je nach Input wird der Stein gelegt (Schwerkraft nicht vergessen!). Steine sollen abwechselnd gelegt werden.

def such(board, spalte_abfrage): ##Funktion zum Suchen von " - " (leeren Stellen)
    zeile = len(board) - 1
    while board[zeile][spalte_abfrage] != " - ":
        zeile = zeile - 1
        if zeile < 0:
            return -1
    return zeile

def spieler_num(spieler_nummer): #sorgt für die Abwechslung der Spielernummern nach jedem Zug
    spieler_nummer / 2
    if spieler_nummer == 1:
        return 1
    else:
        return 2

def spalte_zeich(spieler_nummer, spalte_zeichen):
    spalte_zeichen = ""
    if spieler_nummer == 1:
        spalte_zeichen = " X "
    else:
        spalte_zeichen = " O "

def setz(ÜbergabeBoard, spieler_nummer, spalte_zeichen):
    spieler_nummer = 1
    spalte_abfrage = [] ##leerer Wert
    spalte_abfrage = int(input("Spieler " + str(spieler_nummer) + ": \n In welche Spalte soll dein Stein hin? \n")) - 1 #Abfrage des Wertes als int

    if spalte_abfrage >= 0 and spalte_abfrage <= 5:
        frei = such(ÜbergabeBoard, spalte_abfrage) ##Überprüfung, ob es die Spalte überhaupt gibt
        if frei >= 0: ##bestimmte Spalte im Brett auf Verfügbarkeit überprüfen
            ÜbergabeBoard[frei][spalte_abfrage] = spalte_zeichen
            spieler_nummer = spieler_nummer + 1
            
        else:
            open("notify.txt", "a").write("*belegt*")
            #print("*belegt*")
    elif spalte_abfrage == "":
        open("notify.txt", "w+").write("///////\n *Der Wert der Spalte kann nicht leer sein.* \n/////// \n") ####es kommt erst gar nicht zur Meldung
        return 0
    else:
        open("notify.txt", "w+").write("///////\n *404: Spalte nicht vorhanden* \n/////// \n")
        #print("*404: Spalte nicht vorhanden*") 

def notify(): ##funktioniert
    if os.path.exists("notify.txt"): ##überprüfe ob notify.txt existiert
        print(open("notify.txt").read()) ##gebe Inhalt von notify.txt aus
        os.remove("notify.txt") ##lösche notify.txt, damit beim nächsten Lesen die letzte Nachricht nicht noch zusätzlich angezeigt wird

while True: #Endlosschleife zum Spielen
    #notify()
    spieler_nummer = spieler_num
    spalte_zeichen = spalte_zeich
    boardtostring(board)
    notify()
    setz(board, spieler_nummer, spalte_zeichen) ###! Variablen der Parameter sind nur lokal, dürfen aber nicht global sein !
    #notify()
    ##noch zu tun: Systemmeldungen zum Schluss ausgeben, wenn welche vorhanden sind; Siegesbedingung einbauen

Es kann zwar ausgeführt werden, aber irgendwann heißt es:

Code: Alles auswählen

Traceback (most recent call last):
  File "/Users/name/Documents/Programmierung/Tic-Tac-Toe.py", line 93, in <module>
    boardtostring(board)
  File "/Users/name/Documents/Programmierung/Tic-Tac-Toe.py", line 25, in boardtostring
    oberste_reihe += element ##leerer Inhalt + definierte Mitte
TypeError: can only concatenate str (not "function") to str


Weiß jemand weiter?
Benutzeravatar
/me
User
Beiträge: 3561
Registriert: Donnerstag 25. Juni 2009, 14:40
Wohnort: Bonn

Was glaubst du was diese Zeile in deinem Code tut?

Code: Alles auswählen

ÜbergabeBoard[frei][spalte_abfrage] = spalte_zeichen
Bumblebee38
User
Beiträge: 15
Registriert: Mittwoch 23. Mai 2018, 20:26

Dieser Teil soll in die freie und ausgewählte Spalte das jeweilige Zeichen setzen. Das Zeichen wird in der Funktion dadrüber definiert.
Benutzeravatar
__blackjack__
User
Beiträge: 14031
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Bumblebee38: Du hast eine Funktion `spalte_zeich` die wird in der Endlosschleife am Ende des Quelltextes an den Namen `spalte_zeichen` gebunden und dann etwas danach als drittes Argument an die Funktion `setz` übergeben und die trägt dann die Funktion in das Board ein (``ÜbergabeBoard[frei][spalte_abfrage] = spalte_zeichen``). So kommt die Funktion da rein.

Das Programm enthält zu viele Kommentare und so einige schlechte, weil inhaltlich falsche Namen. Der Namen sollte beschreiben was der Wert bedeutet. `oberste_reihe` sollte beispielsweise an etwas gebunden werden was die oberste Reihe beschreibt, nicht an eine Darstellung des *gesamten* Spielfelds. `feld` ist eine Reihe des Spielfelds und kein einzelnes Feld. Gute Namen sind wichtig, weil man dann im Code lesen kann was passiert, und keine Kommentare dafür braucht. Insbesondere wenn Namen inhaltlich falsch sind, macht das Code deutlich unverständlicher, und birgt auch die Gefahr das man denkt man hat den Code verstanden, hat aber wegen der falschen Namen den Code falsch verstanden.

Namen sollten keine Abkürzungen sein oder kryptische Pre- und/oder Postfixe enthalten. Funktionen beschreiben üblicherweise die Tätigkeit die von der Funktion ausgeführt werden. `spieler_num()`, `spalte_zeich()` sind definitiv keine Tätigkeiten.

Faustregel für Kommentare: Nicht kommentieren *was* der Code macht, denn das steht da bereits als Code, sondern warum er das *so* macht, sofern das nicht offensichtlich ist. So exzessiv jede Zeile zu kommentieren ist falsch. Kommentare sollten wenn sie nicht sehr kurz sind, auch eher als Block vor dem Code stehen, den sie kommentieren. Schmuckkommentare wie Trennlinien sind nicht gut.

Namen schreibt man in Python klein_mit_unterstrichen. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (MixedCase). Siehe auch den Style Guide for Python Code.

Auf Modulebene sollte nur Code stehen der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst. Auf keinen Fall sollte man das Hauptprogramm auf Modulebene zwischen Funktionsdefinitionen verteilen. Unübersichtlicher geht's kaum.

In `generateBoard()` sind `reihe` und `feld` falsch. Die Kommentare sind teilweise inhaltlich falsch. Funktionen werden durch ``return`` nicht ”geschlossen” und das Spielfeld wird da auch nicht ”ausgegeben”, und auch nicht ”mitgenommen” wie an anderer Stelle beschrieben. `range()` liefert Zahlen bis zum Endpunkt *exklusive* des Endpunktes. Eigentlich sollten die ganzen Kommentare weg. Nicht nur weil sie grösstenteils falsch sind, sondern weil man sie nicht braucht.

`boardtostring()` sollte `print_board()` heissen und keinen Rückgabewert haben. Entweder gibt man etwas aus oder gibt es zurück, aber nicht beides. Der Rückgabewert wird ja auch nirgends verwendet.

Auch hier sind die Kommentare schlecht. Die werfen mehr Fragen auf als sie helfen, weil sie teilweise nicht wirklich zu dem Code passen. Der ist auch zu umständlich und ineffizient. Wenn die Funktion sowieso die Ausgabe macht, braucht man nicht erst die komplette Ausgabe aufbauen, sondern kann die auch Zeilenweise per `print()` ausgeben. Und wiederholtes ``+``/``+=`` von Zeichenketten ist ineffizient, weil Zeichenketten unveränderbar sind, da also jedes mal eine neue erstellt wird. Das macht man in Python in dem man Teilzeichenketten in Listen (oder allgemein iterierbaren Objekten) sammelt/erstellt, und die dann mit der `join()`-Methode auf Zeichenketten zusammen setzt. Die einzelnen Zeilen sind ja bereits in Listen als Zeichenketten, da drängt sich `join()` geradezu auf.

Bei `such()` würde man eine einfache ``for``-Schleife über den Zeilenindex statt der ``while``-Schleife verwenden. Die `reversed()`-Funktion ist hier noch interessant, wenn man Rückwärts über ein `range`-Objekt iterieren möchte (oder generell über eine Sequenz/ein umkehrbar iterierbares Objekt). Statt spezieller Rückgabewerte/spezieller Fehlerwerte vom gleichen Typ wie ein reguläres Ergebnis wäre eine Ausnahme deutlich besser. Dazu sind die da.

In `setz()` wird das Argument `spieler_nummer` nicht verwendet, weil der Name gleich als erstes an die 1 gebunden wird, egal was da übergeben wurde.

`spalte_abfrage` an eine leere Liste zu binden um dann gleich in der nächsten Zeile den Namen an eine ganze Zahl zu binden ist auch sinnfrei. Weg damit.

Die Funktion gibt eigentlich nichts zurück, nur in einem Zweig steht ein ``return 0``. Was soll das?

Es zieht ja sowieso immer nur Spieler 1, aber selbst wenn man das nicht machen würde, bekäme der Aufrufer nicht mit ob der Spieler denn nun tatsächlich einen validen Zug gemacht hat oder nicht. Spieler könnten auf diese Weise also einfach aussetzen wenn sie wollen oder sich vertippen. Soll das so sein?

Dateien die man öffnet sollte man auch wieder schliessen. Wenn möglich sollte man die ``with``-Anweisung dazu verwenden. Irgendwas mit "+" ist bei Textdateien beim Dateimodus *sehr* sicher falsch!

`spalte_abfrage` kann niemals gleich der leeren Zeichenkette sein, weil das an eine ganze Zahl gebunden wird.
„A life is like a garden. Perfect moments can be had, but not preserved, except in memory. LLAP” — Leonard Nimoy's last tweet.
Antworten