anon
anon

Reputation:

instance has no attribute '__len__'

this will be a simple question, so it won't take you much time to explain it to me, but it would help me (and possibly other people too) understand something about classes and methods and Python.

My code wants to represent a board game, at this level just on a board with N holes in a row, which can accommodate marbles numbered from 0 to N-1 (in a random order).

class MarblesBoard:
    '''define the board game'''
    def __init__(self, Board):
        self._Board = Board
        self._n = len(Board)

    '''first type of move available: switch the first two marbles'''
    def switch(Board):
        Board[0], Board[1] = Board[1], Board[0]
        return Board

    '''second type of move available: move all the marbles by 1 space to the left'''
    def rotate():
        temp = Board[0]
        for i in range (0, n-1):
            Board[i] = Board[i+1]
        Board[n-1] = temp
        return Board

    '''print the state'''
    def __repr__(Board):
        for i in range(0, len(Board)):
            print self[i],

    '''rest to be developed when this works'''

now, I test displaying the board and moving the marbles. So I let

First = MarblesBoard([1,2,3,4])
First.rotate

and only get

<repr(<instancemethod at 0x104230410>) failed: AttributeError: MarblesBoard instance has no attribute '__len__'>

I know there has already been a similar question about this error message. AttributeError: Entry instance has no attribute '__len__'

I still somehow don't see how does the same error arise in my code. Would you mind explaining (in general) when is the attribute "len" established, and maybe suggest how could it be done in this code?

Upvotes: 1

Views: 8840

Answers (2)

Aaron Hall
Aaron Hall

Reputation: 395633

When you do:

First = MarblesBoard([1,2,3,4])
First.rotate

__repr__ is called, so the representation of the object can be echoed on your terminal. Here is yours (note that printing in it is wrong, you should just return the string, but correcting that is out of scope for this.):

def __repr__(Board):
    for i in range(0, len(Board)):
        print self[i],

You call len(Board). This argument, Board is actually the instance of the object. It is Pythonic to use self instead. You have not defined a __len__ function for len to call for this object, and thus you get the error you see.


Here's an example of some edits I would make. First inherit from object:

class MarblesBoard(object):
    '''define the board game'''

Next, we only capitalize class names:

    def __init__(self, board):
        self._board = board
        self._n = len(board)

Finally, docstrings go beneath the function name and arguments:

    def switch(self):
        '''first type of move available: switch the first two marbles'''
        self.board[0], self.board[1] = self.board[1], self.board[0]
        return self.board

Upvotes: 1

lvc
lvc

Reputation: 35089

def __init__(self, Board):
    self._Board = Board
    self._n = len(Board)

'''first type of move available: switch the first two marbles'''
def switch(Board):
    Board[0], Board[1] = Board[1], Board[0]
    return Board

You seem to expect that Board in the switch method will be the same Board as in __init__ - it isn't. The first argument to any method is the instance you are working with (so, an instance of MarblesBoard, when you are expecting it to be a list) - the name you give it doesn't affect that, but it is conventional to (as you have correctly done in __init__) call it self.

The Board in __init__ is the argument you actually passed in - a list. To get it back in other methods, you need to keep it around attached to self - you've done this already, its called self._Board. Like any variable name or function argument, the name you choose won't affect the functionality, but - again - there are conventions and calling it self._board (all lowercase) instead would be more usual. So, you can rewrite the above two methods as:

def __init__(self, board):
    self._board = board
    self._n = len(Board)

'''first type of move available: switch the first two marbles'''
def switch(self):
    self._board[0], self._board[1] = self._board[1], self._board[0]

(note you probably don't want to return anything from switch now that it changes the state of self).

This brings us to the heart of the problem. Python is implicitly calling the __repr__ method, which you have defined like this:

def __repr__(Board):
    for i in range(0, len(Board)):
        print self[i],

The same thing is going on: you expect Board to be a list, when it is a MarblesBoard. Calling len(Board) then tries to call MarblesBoard.__len__, which doesn't exist. You've also tried calling self[i] which would also be an error if there hadn't been one just before it (because it calls a different magic method, MarblesBoard.__getitem__, which you also haven't defined). The way to fix both problems at once is to do exactly what we did above and get to the list through the MarblesBoard instance:

def __repr__(self):
    for i in range(0, len(self._board)):
        print self._board[i]

Will work. However, note that this formulation still isn't very Pythonic - once your code works, you may want to consider posting it on codereview.SE.

Upvotes: 3

Related Questions