Search code examples
pythonpython-3.xclassindentationinstance-methods

Python: Why does this AttributeError in __str__ method disappear after this indentation change


I hope you will be able to replicate this error on your machine. I couldn't figure out why exactly the code below gave me an error [pasted thereafter] when I had the __str__ method aligned with the same indentation as the methods: __init__ and addNeighbor. I had written part of the class from this link but named my variables in __str__ differently. When I got the error, I just pasted the code from the link and it worked.

It was at this point that I realized that my indentation for the __str__ method was not inline with that of the other methods. When I put everything into the same indentation, I had the same error so I returned the method into its prior implementation and got this output: <__main__.Vertex object at 0x1079b4128> Why is this the case? Shouldn't I have gotten an IndentationError from the beginning? When I indent my own version of the __str__ method and give it more of an indentation, I don't get any error. Why? Can anyone replicate this error?

class Vertex:
  def __init__(self, key):
    self.id = key
    self.connectedTo = {}

  def addNeighbor(self, neighbor, weight=0):
    self.connectedTo[neighbor] = weight

    def __str__(self):
      return str(self.id) + " connected to: " + str([vertex.id for vertex in self.connectedTo])

    # def __str__(self):
    #   return str(self.id) + ' connectedTo: ' + str([x.id for x in self.connectedTo])

if __name__ == '__main__':
  vertex = Vertex(1)
  vertex.addNeighbor('1', 20)
  vertex.addNeighbor('2', 10)
  print(vertex)

The error I get is:

Traceback (most recent call last):
  File "graph_implementation.py", line 19, in <module>
    print(vertex)
  File "graph_implementation.py", line 13, in __str__
    return str(self.id) + ' connectedTo: ' + str([x.id for x in self.connectedTo])
  File "graph_implementation.py", line 13, in <listcomp>
    return str(self.id) + ' connectedTo: ' + str([x.id for x in self.connectedTo])
AttributeError: 'str' object has no attribute 'id'

Solution

  • There are a few things going on here.

    First of all, as you said, your indentation of __str__ is wrong. But it's still a valid indentation, which is why you wouldn't get an IndentationError. The error only appears when __str__ gets called, which only happens when it's at the correct indentation level, which is the lesser indentation, in line with __init__ and addNeighbor. (Otherwise, it's just a local function inside addNeighbor, and pretty useless.)

    Once the __str__ method is being called, the error you are getting is because of the structure of the self.connectedTo dictionary. In addNeighbor() you are mapping neighbor: weight, where neighbor is getting passed in as a str and weight as an int. When you iterate through a dictionary as in [x.id for x in self.connectedTo], Python will iterate through the keys of that dictionary, which in this case is neighbor, a string. So x becomes a string. (The list comprehension is better written as [x.id for x in self.connectedTo.keys()], which is equivalent to the original but clearer.)

    The page you linked shows the original mistake, though – namely that neighbor should be another Vertex, not a str. Meaning it all boils down to passing the wrong type of argument into addNeighbor(). So with that in mind, I might rewrite your test code like this...

    class Vertex:
      def __init__(self, key):
        self.id = key
        self.connectedTo = {}
    
      def addNeighbor(self, neighbor, weight=0):
        self.connectedTo[neighbor] = weight
    
      def __str__(self):
        return str(self.id) + ' connectedTo: ' + str([x.id for x in self.connectedTo])
    
    if __name__ == '__main__':
      vertex = Vertex(1)
      vertex.addNeighbor(Vertex(1), 20) # This creates a second Vertex with the same ID
                                        # as `vertex` -- probably not what you intend?
      vertex.addNeighbor(Vertex(2), 10)
      print(vertex)
    
    
    # Output:
    1 connected to: [1, 2]