Falsche Platzierung liste[x][y]

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
misterjosu
User
Beiträge: 44
Registriert: Samstag 29. Dezember 2012, 21:40

Hey,
Ich versuche mich gerade am Conway's Game of life. Das was ich also geschrieben hab will ich nicht so benutzen, aber ich brauche es um später zu wissen welche Zelle Tot und welche Lebendig ist.
Das ganze versuche ich mit einer row_example liste an die ich dann X viele "D" anhänge, diese liste wird dann X mal an die area liste angehängt. dann müsste ich doch eine 2D liste haben, was auch klappt, allerdings ist es so als würde einfach an jeder untergeordneten liste alles gemacht wird. Ich wäre euch sehr dankbar wenn ihr mir sagt woran es liegt, denn das macht echt irgendwie spaß.

Hier der Quellcode der area.py datei:

Code: Alles auswählen

area = []

def create_area(area_len):
    row_example = []

    for i in range(area_len+1):
        row_example.append("D")
    for i in range(area_len+1):
        area.append(row_example)
        
create_area(10)
print(area)
area[1][1] = "X"
print("---")
print(area)
Hier das ergebnis:

Code: Alles auswählen

[['D', 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D'], ['D', 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D'], ['D', 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D'], ['D', 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D'], ['D', 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D'], ['D', 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D'], ['D', 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D'], ['D', 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D'], ['D', 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D'], ['D', 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D'], ['D', 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D']]
---
[['D', 'X', 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D'], ['D', 'X', 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D'], ['D', 'X', 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D'], ['D', 'X', 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D'], ['D', 'X', 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D'], ['D', 'X', 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D'], ['D', 'X', 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D'], ['D', 'X', 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D'], ['D', 'X', 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D'], ['D', 'X', 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D'], ['D', 'X', 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D', 'D']]
PS: Mit der datei die dafür sorgt, dass das ganze mit Tkinter ausgegeben wird habe ich schonangefengen:

Code: Alles auswählen

# area_len = Feldlänge z.B. 20
# area     = 2 Dimensionale Liste (repräsentiert die zellen)

from tkinter import *
root = Tk()

def create_window(area_len): # Erzeugt ein Fenster mit Toten Zellen
    
    liste = []

    for x in range(0, area_len+1):
        for y in range(0, area_len+1):
            Button(root, width = 3, bg="Black", relief="raised").grid(row=x, column=y)

def refresh(area, area_len):

    for x in range(0, area_len): # area wird nach lebenden zellen durchsucht
        for y in range(0, area_len):
            if area[x][y] == "X":
                Button(root, width = 3, bg = "Gold",relief="raised").grid(row=x, column=y)
            elif area[x][y] == "D":
                Button(root, width = 3, bg = "Black", relief="raised").grid(row=x, column=y)
            else:
                print("NOT DEFINED LIST CHARAKTER")
                
BlackJack

@misterjosu: Du erstellst eine Liste und steckst dann diese *eine* Liste mehrfach in die Liste die an `area` gebunden ist. Du musst für jede Zeile der Datenstruktur eine *neue* Liste erstellen und nicht immer die selbe hinzufügen.

Globale Datenstrukturen sind nicht schön, die führen zu undurchsichtigen Programmen mit schwer nachvollziehbaren Abhängigkeiten. Deine `create_area()`-Funktion sollte ihrem Namen gerecht werden und die Datenstruktur tatsächlich komplett neu erzeugen und nicht auf „magische” Weise eine Liste füllen die eigentlich gar nichts mit der Funktion zu tun hat. Werte ausser Konstanten sollten Funktionen als Argumente betreten und wenn man in der Funktion etwas erzeugt was der Aufrufer verwenden soll, dann gibt man das mit ``return`` an den Aufrufer zurück.

Du möchtest vielleicht auch mal über das ``+ 1`` bei `range()` nachdenken und die Ds in einer Reihe zählen. Ich vermute mal das ist nicht die Anzahl die Du haben wolltest.
misterjosu
User
Beiträge: 44
Registriert: Samstag 29. Dezember 2012, 21:40

Ok, danke.

wie kann ich dann ein beliebig großes 2d listen objekt erstellen ? ( ohne dass ich für jedenteilder liste eine variable definiere ?

danke im vorraus
BlackJack

@misterjosu: Na mit einer Schleife. Du erstellst erst eine Liste und fügst die in der zweiten Schleife x mal dem Ergebnis hinzu. Stattdessen muss Du in einer Schleife auch s mal eine neue Liste erstellen, die dann dem Ergebnis hinzugefügt wird.

Wenn die inneren Listen unveränderliche Objekte enthhalten, wie beispielsweise Zahlen oder Zeichenketten, dann brauchst Du dafür übrigens keine Schleife, sondern kannst die Multiplikation auf Listen verwenden. Beispiel: ``row = [0] * width``. Bei veränderlichen Elementen geht das nicht, weil keine Kopien erstellt werden, sondern nur entsprechend viele Verweise auf das *eine* Objekt in der Liste stehen. Also genau das Problem entsteht, welches Du jetzt gerade hast.
Sirius3
User
Beiträge: 17737
Registriert: Sonntag 21. Oktober 2012, 17:20

Hallo misterjosu,

Du mußt keine neue Variable definieren, sonder jeweils ein neues Listenobjekt erzeugen. Dieses kannst Du, wenn es sein muß, immer an die selbe Variable binden.

Code: Alles auswählen

def create_area(area_len):
    return [['D']*area_len for _ in range(area_len)]
        
area = create_area(10)
misterjosu
User
Beiträge: 44
Registriert: Samstag 29. Dezember 2012, 21:40

okdanke wenns wieder probleme gibt melde ich mich
misterjosu
User
Beiträge: 44
Registriert: Samstag 29. Dezember 2012, 21:40

Was bewirkt der unterstrich zwischen for _ in ?

Code: Alles auswählen

return [['D']*self.area_len for _ in range(self.area_len)]
dass das was davor steht x oft ausgeführt wird ?
BlackJack

@misterjosu: Der bewirkt gar nichts, das ist einfach nur ein Name. Da könntest Du auch `i` oder `foo` schreiben. Da der Name aber nicht verwendet wird, hat Sirius3 `_` gewählt was konventionell ein Name ist der dem Leser vermittelt, dass der Name und damit der Wert absichtlich nicht benutzt wird. Für das gesamte Konstrukt kannst Du in der Dokumentation „list comprehension” nachschlagen.
misterjosu
User
Beiträge: 44
Registriert: Samstag 29. Dezember 2012, 21:40

Ok danke.

würdet ihr das alles in einzelnen dateien unterbringen oder eine große erstelle mit klassen ( mache ich im momnet)
BlackJack

@misterjosu: Was ist „das alles”? Eine Datei entspricht einem Modul. Und in einem Modul fasst man Funktionen und Klassen zusammen, die thematisch zusammen gehören. Wenn es zu viel wird, kann man sich Gedanken machen wie man den Inhalt von einem Modul sinnvoll auf zwei oder mehr Module aufteilen kann.
misterjosu
User
Beiträge: 44
Registriert: Samstag 29. Dezember 2012, 21:40

Bin jetzt glaube ich relativ weit, allerdings bekomme ich kein fenster mehr angezeigt was ich mir nicht erklären kann.Hier der quellcode:

Code: Alles auswählen

from tkinter import *
from time import *

root = None
global root
root = Tk()

class Area(object):
    def __init__(self, area_len):
        self.area_len = area_len

    def create_area(self):
        self.area = [['D']*self.area_len for _ in range(self.area_len)]
        return self.area

    def check_zell(self, area):
        self.area = area
        self.life_zells = []
        arr = 0
        
        for x in range(0, self.area_len):
            for y in range(0, self.area_len):
                for column in [x-1, x, x+1]:
                    
                    for row in[y+1, y-1]:
                        try:
                            if self.area[column][row] == "X":
                                arr += 1
                        except:
                            arr += 0
                          
                    for column in[x-1, x+1]:
                        try:
                            if self.area[column][y] == "X":
                                arr += 1
                        except:
                            arr += 0

                if arr == 2:
                    if self.area[x][y] == "X":
                        self.life_zells.append([x, y, "X"])
                        
                if arr == 3:
                    if self.area[x][y] == "X":
                        self.life_zells.append([x, y, "X"])
                        
                else:
                    self.life_zells.append([x, y, "D"])
        
        return self.life_zells

            
    def refresh(self):
        for i in self.life_zells:
            
            x = i[0]
            y = i[1]

            area_list[x][y] = i[2]

    def set_zell(self, x, y):
        area_list[x][y] = "X"

    def kill_zell(self, x, y):
        area_list[x][y] = "D"            
    
class Gui(object):
    def __init__(self):
        self.liste = []
        self.ent_x = IntVar()
        self.ent_y = IntVar()

    def create_window(self, area_len):
        for x in range(0, area_len):
            for y in range(0, area_len):
                Button(root, width = 3, bg="Black", relief="raised").grid(row=x, column=y)
        label_x = Label(root, text = "X Coordinaten").grid(row= 2, column = area_len+1, padx = 10)
        label_y = Label(root, text = "Y Coordinaten").grid(row= 3, column = area_len+1, padx = 10)
                
        entry_x = Entry(root, width = 3, textvariable = self.ent_x).grid(row = 2, column = area_len+2, padx= 10)
        entry_y = Entry(root, width = 3, textvariable = self.ent_y).grid(row = 3, column = area_len+2, padx= 10)

        button_1 = Button(root, width = 10, text = "Set ", command= Area(area_len).set_zell(x, y)).grid(row = 5, column = area_len+1)
        button_2 = Button(root, width = 10, text = "Kill", command= Area(area_len).kill_zell(x, y)).grid(row = 5, column = area_len+2)

        root.resizable(0, 0)
        
    def refresh(self, area_len, area):
        for x in range(0, area_len): # area wird nach lebenden zellen durchsucht
            for y in range(0, area_len):
                if area[x][y] == "X":
                    Button(root, width = 3, bg = "Gold",relief="raised").grid(row=x, column=y)
                elif area[x][y] == "D":
                    Button(root, width = 3, bg = "Black", relief="raised").grid(row=x, column=y)
                else:
                    print("NOT DEFINED LIST CHARAKTER")

def main(area_len):
    
    a = Area(area_len) # class Abkürzung      
    area_list = a.create_area()
    print(area_list)
    global area_list

    g = Gui()
    g.create_window(area_len)
    print("teat")

    run = 1
    while True:
        while run == True:
            print("refresh")
            a.check_zell(area_list)
            a.refresh()
            g.refresh(area_len, area_list)
            sleep(1)


main(30)
kritik am code ist natürlich auch wilkommen
BlackJack

@misterjosu: So wie Du das versuchst umzusetzen funktionieren GUI-Toolkits in der Regel nicht. Da musst Du Dich von dem Gedanken verabschieden, dass *Du* den Ablauf des Programms zum Beispiel in einer Schleife steuerst. Stattdessen ruft man die Hauptschleife des GUI-Toolkits auf und die kümmert sich darum, dass bei bestimmten Ereignissen kleine Codeabschnitte mit möglichst geringer Laufzeit ausgeführt werden, die der Programmierer für die Ereignisse registriert hat. Ein Ereignis wäre beispielsweise wenn der Anwender auf eine Schaltfläche klickt, oder wenn eine gewisse Zeit verstrichen ist, seit der der Programmierer einen Rückruf und eine Verzögerung angegeben hat. Letzteres würde man zum Beispiel verwenden um das Game of Life am laufen zu halten. Immer wieder eine Rückruffunktion registrieren, die nach einer kurzen Verzögerung einen Schritt in der Simulation berechnet und sich dann selbst wieder zum Rückruf registriert. Die Methode dafür bei `tkinter` heisst `after()` und sie existiert auf jedem Widget-Objekt.

Sonstige Anmerkungen: Du verwendest Klassen, damit sollte ``global`` unnötig sein. Das hat in einem sauber strukturiertem Programm normalerweise keinen Platz. Auf Modulebene hat diese Anweisung zudem keinerlei Effekt. Von den drei Zeilen am Anfang die sich auf `root` beziehen, sind die ersten beiden sinnfrei. Letztendlich sollte man auf Modulebene aber auch nur Konstante Werte und Klassen und Funktionen definieren und keine „globalen” Datenstrukturen.

Nach dem die `__init__()`-Methode einer Klasse abgelaufen ist, sollte das Objekt vollständig initialisiert sein und sich in einem konsistenten Zustand befinden. Insbesondere sollte man keine neuen Attribute in anderen Methoden einführen. Das macht es schwieriger alle Attribute einer Klasse zu erfassen und welche Attribute wann existieren und welche Methoden man in welcher Reihenfolge aufrufen kann, ohne `AttributeError`\s zu bekommen ist schwieriger nachzuvollziehen. Wenn man ein Exemplar von `Area` erstellt, dann erwartet man doch das man ein Objekt bekommt, das so ein `Area` darstellt und nicht das man noch eine Methode aufrufen muss, die man *immer* aufrufen muss, damit es tatsächlich vollständig ist.

Die Klasse `Area` ist im Grunde keine Klasse. Da sitzen Funktionen drauf, die Daten von aussen entgegennehmen, die eigentlich der innere Zustand sein sollten — die Zellenmatrix. Auf der anderen Seite werden Daten an das Objekt gebunden, die nichts mit dem Zustand zu tun haben, beziehungsweise redundant sind, wie beispielsweise `life_zells`. Und zusätzlich werden diese Daten am Ende von der Funktion, denn eine Methode ist das nur syntaktisch, auch noch an den Aufrufer zurück gegeben.

Bei `refresh()` und den folgenden beiden „Methoden” sind es noch nicht einmal Funktionen, denn eine saubere Funktion verändert keine globale Datenstruktur die hier auf magische Weise einfach so in der Umgebung existieren muss.

Zur Namensgebung: Man sollte Abkürzungen vermeiden die nicht allgemein bekannt sind. Namen sollen Klarheit verschaffen und den Leser nicht zum Raten animieren.

Nackte ``except``-Anweisungen ohne konkrete Ausnahmen die man dort auch erwartet führen zu Problemen wenn dort mal etwas passiert, was man nicht erwartet. Zum Beispiel ein `NameError` oder `AttributeError` weil man sich vertippt hat, oder irgendeine andere Ausnahme für die die Behandlung im ``except`` nicht geeignet ist. Zumal Du Dir noch mal überlegen solltest bei welchen Werten in der `check_zell()` Ausnahmen ausgelöst werden und welche Werte zwar keine Ausnahme auslösen, aber nicht das Ergebnis haben was Du Dir offenbar erhoffst. (Tipp: Negative Indexzugriffe.)

In der GUI-Klasse sind auch eine ganze Menge Fehler drin, die nahelegen, dass Du nicht überblickst was da eigentlich passiert, wie Klassen und Methoden richtig verwendet werden. Ich würde das Thema GUI erst einmal zurück stellen, bis OOP halbwegs sitzt.
misterjosu
User
Beiträge: 44
Registriert: Samstag 29. Dezember 2012, 21:40

Danke erstmal für die Kritik, allerdings fällt es mir schwer das mit den Klassen zu verstehen, also warum mandass eben so aufbaut wie manes aufbaut ( Habe das buch " objektorientierte programmierung mit python 3 von michael weigend"). Und ich habe viel spaßdamit mir den kopf zubrechebn warum es wieder nicht klappt...
BlackJack

@misterjosu: Klassen sind dazu da um Zusammengehörige Daten und Funktionen zu einem Objekt zusammen zu fassen. Die `__init__()`-Methode sollte diese Daten initialisieren und die Methoden darauf operieren. In der Regel werden die Daten nicht herausgegeben um ausserhalb der Klasse Veränderungen daran vorzunehmen. Dafür schreibt man Methoden. Das nennt sich Datenkapselung. Denn so kann man die Datenstruktur intern ändern, ohne dass Code der das Objekt benutzt, etwas davon mitbekommt.

Die Daten die an ein Objekt gebunden werden, sollten den Zustand dieses Objekts beschreiben. Nicht mehr, aber auch nicht weniger. Mehr, und damit falsch, wäre es beispielsweise irgendwelche temporären Daten an das Objekt zu binden und so, quasi verdeckt, an andere Methoden weiter zu geben. Bei `area_len` könnte man auch Argumentieren, dass es redundant ist, denn man könnte ja einfach die Länge von `self.area` abfragen — wenn das denn in der `__init__()` schon initialisiert worden wäre.

Hier wäre mal ein Beispiel wie man die Zellen als eigenen Datentyp beschreiben könnte:

Code: Alles auswählen

#!/usr/bin/env python
# coding: utf-8
from random import random
from time import sleep


class Cells(object):
    DIRECTIONS = [
        (i, j) for i in xrange(-1, 2) for j in xrange(-1, 2) if i or j
    ]

    def __init__(self, size):
        self.size = size
        self.cells = [[False] * size for _ in xrange(size)]

    def __iter__(self):
        return iter(self.cells)

    def __str__(self):
        return '\n'.join(
            ''.join(('X' if cell else '.') for cell in row) for row in self
        )

    def _check_coordinate(self, coordinate):
        if any(not (0 <= n < self.size) for n in coordinate):
            raise IndexError('coordinate outside cells')

    def __getitem__(self, (x, y)):
        self._check_coordinate((x, y))
        return self.cells[y][x]

    def __setitem__(self, (x, y), cell_value):
        self._check_coordinate((x, y))
        self.cells[y][x] = cell_value

    def count_alive_neighours(self, x, y):
        result = 0
        for d_x, d_y in self.DIRECTIONS:
            try:
                if self[x + d_x, y + d_y]:
                    result += 1
            except IndexError:
                pass  # Ignore coordinates outside the cells.
        return result

    def step(self):
        new_cells = list()
        for j, row in enumerate(self):
            new_row = list()
            for i, cell in enumerate(row):
                alive_neighours = self.count_alive_neighours(i, j)
                # 
                # BUG: That is not the original formular to decide the
                #   fate of a cell.  This one leads to over population.
                # 
                new_row.append(cell or alive_neighours not in [2, 3])
            new_cells.append(new_row)
        self.cells = new_cells

    def spawn_random_cells(self, probability=0.5):
        for j in xrange(self.size):
            for i in xrange(self.size):
                self[i, j] = random() < probability


def main():
    size = 20
    cells = Cells(size)
    cells.spawn_random_cells()
    while True:
        print '-' * size
        print cells
        cells.step()
        sleep(1)


if __name__ == '__main__':
    main()
Antworten