Confused about __str__ on list in Python

Question:

Coming from a Java background, I understand that __str__ is something like a Python version of toString (while I do realize that Python is the older language).

So, I have defined a little class along with an __str__ method as follows:

class Node:

    def __init__(self, id):
        self.id = id
        self.neighbours = []
        self.distance = 0


    def __str__(self):
        return str(self.id)

I then create a few instances of it:

uno = Node(1)    
due = Node(2)    
tri = Node(3)    
qua = Node(4)

Now, the expected behaviour when trying to print one of these objects is that it’s associated value gets printed. This also happens.

print uno

yields

1

But when I do the following:

uno.neighbours.append([[due, 4], [tri, 5]])

and then

print uno.neighbours

I get

[[[<__main__.Node instance at 0x00000000023A6C48>, 4], [<__main__.Node instance at 0x00000000023A6D08>, 5]]]

Where I expected

[[2, 4], [3, 5]]

What am I missing? And what otherwise cringe-worthy stuff am I doing? 🙂

Asked By: Christofer Ohlsson

||

Answers:

Well, container objects’ __str__ methods will use repr on their contents, not str. So you could use __repr__ instead of __str__, seeing as you’re using an ID as the result.

Answered By: user1675187

Python has two different ways to convert an object to a string: str() and repr(). Printing an object uses str(); printing a list containing an object uses str() for the list itself, but the implementation of list.__str__() calls repr() for the individual items.

So you should also overwrite __repr__(). A simple

__repr__ = __str__

at the end of the class body will do the trick.

Answered By: Sven Marnach

__str__ is only called when a string representation is required of an object.

For example str(uno), print "%s" % uno or print uno

However, there is another magic method called __repr__ this is the representation of an object. When you don’t explicitly convert the object to a string, then the representation is used.

If you do this uno.neighbors.append([[str(due),4],[str(tri),5]]) it will do what you expect.

Answered By: Burhan Khalid

Because of the infinite superiority of Python over Java, Python has not one, but two toString operations.

One is __str__, the other is __repr__

__str__ will return a human readable string.
__repr__ will return an internal representation.

__repr__ can be invoked on an object by calling repr(obj) or by using backticks `obj`.

When printing lists as well as other container classes, the contained elements will be printed using __repr__.

Answered By: Hans Then

print self.id.__str__() would work for you, although not that useful for you.

Your __str__ method will be more useful when you say want to print out a grid or struct representation as your program develops.

print self._grid.__str__()

def __str__(self):
    """
    Return a string representation of the grid for debugging.
    """
    grid_str = ""
    for row in range(self._rows):
        grid_str += str( self._grid[row] )
        grid_str += 'n'
    return grid_str
Answered By: user3295570

It provides human readable version of output rather “Object”: Example:

class Pet(object):

    def __init__(self, name, species):
        self.name = name
        self.species = species

    def getName(self):
        return self.name

    def getSpecies(self):
        return self.species

    def Norm(self):
        return "%s is a %s" % (self.name, self.species)

if __name__=='__main__':
    a = Pet("jax", "human")
    print a 

returns

<__main__.Pet object at 0x029E2F90>

while code with “str” return something different

class Pet(object):

    def __init__(self, name, species):
        self.name = name
        self.species = species

    def getName(self):
        return self.name

    def getSpecies(self):
        return self.species

    def __str__(self):
        return "%s is a %s" % (self.name, self.species)

if __name__=='__main__':
    a = Pet("jax", "human")
    print a 

returns:

jax is a human
Answered By: jax

The thing about classes, and setting unencumbered global variables equal to some value within the class, is that what your global variable stores is actually the reference to the memory location the value is actually stored.

What you’re seeing in your output is indicative of this.

Where you might be able to see the value and use print without issue on the initial global variables you used because of the str method and how print works, you won’t be able to do this with lists, because what is stored in the elements within that list is just a reference to the memory location of the value — read up on aliases, if you’d like to know more.

Additionally, when using lists and losing track of what is an alias and what is not, you might find you’re changing the value of the original list element, if you change it in an alias list — because again, when you set a list element equal to a list or element within a list, the new list only stores the reference to the memory location (it doesn’t actually create new memory space specific to that new variable). This is where deepcopy comes in handy!

Answered By: Tommy W.

Answer to the question

As pointed out in another answer and as you can read in PEP 3140, str on a list calls for each item __repr__. There is not much you can do about that part.

If you implement __repr__, you will get something more descriptive, but if implemented correctly, not exactly what you expected.

Proper implementation

The fast, but wrong solution is to alias __repr__ to __str__.

__repr__ should not be set to __str__ unconditionally. __repr__ should create a representation, that should look like a valid Python expression that could be used to recreate an object with the same value. In this case, this would rather be Node(2) than 2.

A proper implementation of __repr__ makes it possible to recreate the object. In this example, it should also contain the other significant members, like neighours and distance.

An incomplete example:

class Node:

    def __init__(self, id, neighbours=[], distance=0):
        self.id = id
        self.neighbours = neighbours
        self.distance = distance


    def __str__(self):
        return str(self.id)


    def __repr__(self):
        return "Node(id={0.id}, neighbours={0.neighbours!r}, distance={0.distance})".format(self)
        # in an elaborate implementation, members that have the default
        # value could be left out, but this would hide some information


uno = Node(1)    
due = Node(2)    
tri = Node(3)    
qua = Node(4)

print uno
print str(uno)
print repr(uno)

uno.neighbours.append([[due, 4], [tri, 5]])

print uno
print uno.neighbours
print repr(uno)

Note: print repr(uno) together with a proper implementation of __eq__ and __ne__ or __cmp__ would allow to recreate the object and check for equality.

Answered By: trapicki