Liste hat kein Attribut len()

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
Friedericus
User
Beiträge: 25
Registriert: Dienstag 21. September 2010, 14:59

Hi Leuts. Könnt ihr mir bei folgendem Problem helfen?

Code: Alles auswählen

from random import choice

class puzzle(object):

    def __init__(self):
        self.numbers = [0,1,2,3,4,5,6,7,'#']
        self.cords= [[0,0],[0,1],[0,2],[1,0],[1,1],[1,2],[2,0],[2,1],[2,2]]

    def randomize(self):

        print len(self.numbers)
        a = 0
        while(len(self.numbers) > 0):
            rand = choice(self.numbers)
            self.cords[a].append(rand)
            a = a+1
            self.numbers = self.numbers.remove(rand)

    def printModule(self):
        i = 2
        for a in range (len(self.cords)):
            print self.cords[a],
            if (a == i):
                print "\r"
                i = i+3
puz = puzzle()



puz.randomize()            
puz.printModule()
gibt mir

Code: Alles auswählen

9

Traceback (most recent call last):
  File "D:/Python27/8-erspiel.py", line 30, in <module>
    puz.randomize()
  File "D:/Python27/8-erspiel.py", line 14, in randomize
    rand = choice(self.numbers)
  File "D:\Python27\lib\random.py", line 274, in choice
    return seq[int(self.random() * len(seq))]  # raises IndexError if seq is empty
TypeError: object of type 'NoneType' has no len()
aus. Warum hat self.numbers kein len(), eine Zeile drüber wird sie aber ausgegeben?
Zuletzt geändert von Anonymous am Freitag 21. Oktober 2011, 00:51, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Code-Tags gesetzt.
ms4py
User
Beiträge: 1178
Registriert: Montag 19. Januar 2009, 09:37

list.remove ist eine "in-place" Operation und gibt None zurück...
„Lieber von den Richtigen kritisiert als von den Falschen gelobt werden.“
Gerhard Kocher

http://ms4py.org/
BlackJack

@Friedericus: Sonstige Anmerkungen zum Quelltext:

Klassennamen sollten nicht mit einem Kleinbuchstaben anfangen. Während man bei `printModule()`, was eigentlich von der Konvention her `print_module()` heissen sollte, noch „künstlerische Freiheit“ anführen kann, sollte man sich an die Namenskonvention für Klassennamen unbedingt halten. Dann hat man zum Beispiel auch kein Problem mehr einen generischen Namen für ein Exemplar zu finden: einfach den Klassennamen in der Konvention für nicht-Klassen. Also zum Beispiel ``puzzle = Puzzle()``. Womit wir eine Überleitung zum nächsten Punkt haben: Keine ungebräuchlichen Abkürzungen verwenden. `coords` wäre noch im Rahmen (Achtung: *zwei* 'o's!), aber `puz` und `rand` gehen IMHO nicht mehr.

Die Beziehung von `randomize()` und `__init__()` ist komisch. Man kann `randomize()` nur einmal aufrufen, weil `self.numbers` danach leer ist. Kann man ein `Puzzle`-Exemplar sinnvoll verwenden wenn nicht als allererstes `randomize()` darauf aufgerufen wird? Falls nicht, sollte das keine Methode sein, die man von aussen aufruft, sondern die von `__init__()` aufgerufen wird und dann vielleicht auch durch einen führenden Unterstrich als Interna gekennzeichnet wird.

Macht `numbers` auf dem Objekt überhaupt Sinn? Die Liste ist nach `randomize()` leer — wird die danach jemals wieder verwendet?

So eine Liste wie `self.coords` hin zu schreiben ist fehleranfällig. Die würde ich eher programmatisch erstellen. `itertools.product()` ist das passende Werkzeug dafür.

`randomize()` ist ziemlich umständlich und ineffizient geschrieben. Die ``while``-Schleife könnte man durch eine ``for``-Schleife ersetzen. Wenn bei jedem Schleifendurchlauf ein Element aus `self.numbers` entfernt wird, dann ist ja klar, dass die Schleife ``len(self.numbers)`` mal (vor Schleifeneintritt ermittelt) durchlaufen werden muss, bis die Liste leer ist:

Code: Alles auswählen

    def randomize(self):
        for i in xrange(len(self.numbers)):
            random_pick = choice(self.numbers)
            self.coords[i].append(random_pick)
            self.numbers.remove(random_pick)
Das ist aber wegen der Laufzeit von `remove()` noch suboptimal. Es wird etwas aus den Zahlen ausgesucht und am Ende der Schleife dann noch einmal nach Wert von vorne beginnend in der Liste gesucht und entfernt — was bedeutet alle Listenelemente danach müssen eine Position nach vorne verschoben werden.

Effizienter wäre es `self.numbers` mit `random.shuffle()` zu mischen und dann die Elemente in einer ``for``-Schleife auf die Koordinaten zu verteilen:

Code: Alles auswählen

    def randomize(self):
        shuffle(self.numbers)
        for number in self.numbers:
            self.coords[i].append(number)
        self.numbers = list()   # Falls das überhaupt nötig ist.
Sollte wie von mir vermutet die Trennung zwischen `__init__()` und `randomize()` unsinnig sein, kann man die `self.coords` auch gleich passend erzeugen, statt die Zahlen nachträglich hinzu zu fügen:

Code: Alles auswählen

    def __init__(self):
        numbers = range(8) + ['#']
        shuffle(numbers)
        coords = product(xrange(3), xrange(3))
        self.coords = [[x, y, n] for (x, y), n in izip(coords, numbers)]
        self.numbers = list()   # Falls das überhaupt nötig ist.
Klammern bei Bedingungen bei ``if``/``while`` sind überflüssig solange sie nicht dazu dienen Operatorvorrang zu verändern, oder dem Leser deutlicher zu machen.
Antworten