AttributeError: 'tuple' object has no attribute 'first'

Question:

I’m a beginner in python and having trouble with this problem. I wrote this convert_link function while it returned the AttributeError: ‘tuple’ object has no attribute ‘first’. I don’t understand where the tuple came from?

def convert_link(link):
    """Takes a linked list and returns a Python list with the same elements.

    >>> link = Link(1, Link(2, Link(3, Link(4))))
    >>> convert_link(link)
    [1, 2, 3, 4]
    >>> convert_link(Link.empty)
    []
    """
    if link.first is Link.empty:
        return []
    return [link.first]+convert_link(link.rest)

this the class definition I was given:

class Link:
    """A linked list.

    >>> s = Link(1)
    >>> s.first
    1
    >>> s.rest is Link.empty
    True
    >>> s = Link(2, Link(3, Link(4)))
    >>> s.first = 5
    >>> s.rest.first = 6
    >>> s.rest.rest = Link.empty
    >>> s                                    # Displays the contents of repr(s)
    Link(5, Link(6))
    >>> s.rest = Link(7, Link(Link(8, Link(9))))
    >>> s
    Link(5, Link(7, Link(Link(8, Link(9)))))
    >>> print(s)                             # Prints str(s)
    <5 7 <8 9>>
    """
    empty = ()

    def __init__(self, first, rest=empty):
        assert rest is Link.empty or isinstance(rest, Link)
        self.first = first
        self.rest = rest

    def __repr__(self):
        if self.rest is not Link.empty:
            rest_repr = ', ' + repr(self.rest)
        else:
            rest_repr = ''
        return 'Link(' + repr(self.first) + rest_repr + ')'

    def __str__(self):
        string = '<'
        while self.rest is not Link.empty:
            string += str(self.first) + ' '
            self = self.rest
        return string + str(self.first) + '>'

This is what I got after running the code in terminal

File "/Users/yiting/Downloads/lab08/lab08.py", line 12, in convert_link
    return [link.first]+convert_link(link.rest)
  [Previous line repeated 1 more time]
  File "/Users/yiting/Downloads/lab08/lab08.py", line 10, in convert_link
    if link.first is Link.empty:
AttributeError: 'tuple' object has no attribute 'first'
Asked By: Yiting

||

Answers:

There is no first or first() attr in tuple. You need to use indexing like mytuple[0].

Answered By: Roman

You test if link.first is Link.empty:. But Link.empty is only ever the value for link.rest (to indicate end of linked list).

You meant to just test if link is Link.empty:, because when you reached the end of the linked list, you’d recurse calling with Link.empty itself, not something that has a first attribute of Link.empty.

Your function is correct, after that one tweak:

def convert_link(link):
    """Takes a linked list and returns a Python list with the same elements.

    >>> link = Link(1, Link(2, Link(3, Link(4))))
    >>> convert_link(link)
    [1, 2, 3, 4]
    >>> convert_link(Link.empty)
    []
    """
    if link is Link.empty:  # Removed .first
        return []
    return [link.first]+convert_link(link.rest)
Answered By: ShadowRanger

Your function convert_link is called recursively, until it reaches the innermost instance of your Link class that in turn has a link.rest attribute of type tuple. This final link.rest (that contains a tuple instead of an instance of class Link) is then passed to convert_link that then tries to access the attribute .first that the tuple does not have.

A quick fix to your function would be to add at its beginning an if statement, that checks whether the link being used as input is an instance of the class Link, and if not converts it into a Link object:

def convert_link(link):
    """Takes a linked list and returns a Python list with the same elements.

    >>> link = Link(1, Link(2, Link(3, Link(4))))
    >>> convert_link(link)
    [1, 2, 3, 4] 
    >>> convert_link(Link.empty)
    []
    """
    if not isinstance(link, Link):
        link = Link(link)
    if link.first == Link.empty:
        return []
    return [link.first] + convert_link(link.rest)


link = Link(1, Link(2, Link(3, Link(4))))
print(convert_link(link))
# Prints:
#
# [1, 2, 3, 4]

Another solution would be to add an additional condition to your if link. First == Link.empty, checking whether link has an attribute named first:

def convert_link(link):
    """Takes a linked list and returns a Python list with the same elements.

    >>> link = Link(1, Link(2, Link(3, Link(4))))
    >>> convert_link(link) [1, 2, 3, 4] 
    >>> convert_link(Link.empty) []
    """
    # The order of both conditions matters here
    if not hasattr(link, 'first') or link.first == Link.empty:
        return []
    return [link.first] + convert_link(link.rest)


link = Link(1, Link(2, Link(3, Link(4))))
print(convert_link(link))
# Prints:
#
# [1, 2, 3, 4]

A third solution would be to change the if statement like so:

def convert_link(link):
    """Takes a linked list and returns a Python list with the same elements.

    >>> link = Link(1, Link(2, Link(3, Link(4))))
    >>> convert_link(link) [1, 2, 3, 4] 
    >>> convert_link(Link.empty) []
    """
    if link == Link.empty:
        return []
    return [link.first] + convert_link(link.rest)


link = Link(1, Link(2, Link(3, Link(4))))
print(convert_link(link))
# Prints:
#
# [1, 2, 3, 4]

print(convert_link(Link.empty))
# Prints:
#
# []

Note

Adding a print statement right before the second return from your original convert_link might help better understanding what’s going on inside the convert_link function. If you add print(link.rest, type(link.rest)), you would see the following messages being printed to your console:

def convert_link(link):
    """Takes a linked list and returns a Python list with the same elements.

    >>> link = Link(1, Link(2, Link(3, Link(4))))
    >>> convert_link(link) [1, 2, 3, 4] 
    >>> convert_link(Link.empty) []
    """
    # The order of both conditions matters here
    if not hasattr(link, 'first') or link.first == Link.empty:
        return []
    print(link.rest, type(link.rest))
    return [link.first] + convert_link(link.rest)


link = Link(1, Link(2, Link(3, Link(4))))
print(convert_link(link))
"""
<2 3 4> <class '__main__.Link'>
<3 4> <class '__main__.Link'>
<4> <class '__main__.Link'>
() <class 'tuple'>      # <---- Here's the problem
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-222-5fde863fdd94> in <module>
     14 
     15 
---> 16 convert_link(link)

4 frames
<ipython-input-222-5fde863fdd94> in convert_link(link)
      8 
      9     # The order of both conditions matters here
---> 10     if link.first == Link.empty:
     11         return []
     12     print(link.rest, type(link.rest))

AttributeError: 'tuple' object has no attribute 'first'
"""

You could also observe the problem using a more direct approach:

print(type(link.rest))
print(type(link.rest.rest))
print(type(link.rest.rest.rest))
print(type(link.rest.rest.rest.rest))
# Prints:
#
# <class '__main__.Link'>
# <class '__main__.Link'>
# <class '__main__.Link'>
# <class 'tuple'>
Answered By: Ingwersen_erik
Categories: questions Tags:
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.