LMPTHW ex13 - What does it mean when an object is None?

Hello all,

Problem: I have difficulties understanding why the error in the code below appears.

Edit2: I am working on improving the code.

Erroneous Code/ First Piece of Code:

    def pop(self) -> str or None:
        """Removes the last item and returns it."""
        if self.before_last:
            removed_node = self.end.value
            self.before_last.next = None
            node = self.begin
            count = 0
            while node.next:
                node_before_last = node
                node = node.next
                count += 1
            if count == 1:
                self.begin = node_before_last
                self.end = node
                self.before_last = None
            else:
                self.before_last = node_before_last
                self.end = node
            return removed_node

        elif self.begin.next is None:  # Error here
            removed_node = self.begin.value
            self.begin = None  
            return removed_node

        elif self.begin.next:
            removed_node = self.end.value
            self.begin.next = None
            return removed_node

        return None

(Code is my version and is part of the SingleLinkedList Class, page 69 of LMPTHW)

Error: elif self.begin.next is None:
AttributeError: ‘NoneType’ object has no attribute 'next’

What I do not understand is: Why does the code break at this if? If self.begin = None, does it mean that self.begin.next is also None or just that self.begin does not exit, hence no attributes?

What is more weird is that the following code runs with no problems.
Second Piece of Code:

    def pop(self) -> str or None:
        """Removes the last item and returns it."""
        if self.before_last:
            removed_node = self.end.value
            self.before_last.next = None
            node = self.begin
            count = 0
            while node.next:
                node_before_last = node
                node = node.next
                count += 1
            if count == 1:
                self.begin = node_before_last
                self.end = node
                self.before_last = None
            else:
                self.before_last = node_before_last
                self.end = node
            return removed_node

        elif self.begin.next is None:
            removed_node = self.begin.value
            self.begin.value = None  # change introduced here: instead of self.begin, there is self.begin.value -> AttributeError no longer appears
            return removed_node

        elif self.begin.next:
            removed_node = self.end.value
            self.begin.next = None
            return removed_node

        return None

Why does the code run now?

Thanks!

Edit1: Added the full code.

class SingleLinkedListNode(object):

    def __init__(self, value: None or int, nxt: 'object' or int = None):
        self.value = value
        self.next = nxt

    def __repr__(self):
        nval = self.next and self.next.value or None
        return f"[{self.value}:{repr(nval)}]"
        # The repr() method returns a printable representational string of the given object.

class SingleLinkedList(object):

    def __init__(self):
        self.begin = None
        self.end = None
        self.before_last = None

    def push(self, obj: str):
        """Appends a new value at the end of the list."""
        node = SingleLinkedListNode(obj, None)
        if self.begin is None:
            self.begin = node
        elif self.begin.next is None:
            self.end = node
            self.begin.next = self.end
        else:
            self.before_last = self.end
            self.end = node
            self.before_last.next = self.end

    def pop(self) -> str or None:
        """Removes the last item and returns it."""
        if self.before_last:
            removed_node = self.end.value
            self.before_last.next = None
            node = self.begin
            count = 0
            while node.next:
                node_before_last = node
                node = node.next
                count += 1
            if count == 1:
                self.begin = node_before_last
                self.end = node
                self.before_last = None
            else:
                self.before_last = node_before_last
                self.end = node
            return removed_node

        elif self.begin.next is None:
            removed_node = self.begin.value
            self.begin.value = None
            return removed_node

        elif self.begin.next:
            removed_node = self.end.value
            self.begin.next = None
            return removed_node

        return None

    def shift(self, obj):
        """Another name for push."""

    def unshift(self):
        """Removes the first item and returns it."""

    def remove(self, obj):
        """Finds a matching item and removes it from the list."""

    def first(self):
        """Returns a *reference* to the first item, does not remove."""

    def last(self):
        """Returns a reference to the last item, does not remove."""

    def count(self):
        """Counts the number of elements in the list."""
        node = self.begin
        count = 0
        while node:
            count += 1
            node = node.next

        return count

    def get(self, index):
        """Get the value at index."""

    def dump(self):
        """Debugging function that dumps the contents of the list."""

None is nothing here. It has no any attribute, including ‘next’. Hence, if self.begin = None and python run to the line elif self.begin.next is None:, python will raise AttributeError.

So the answer is you should check self.begin before you write .next. If self.begin = None, your code should return None or raise IndexError.

My code comes as follow:

    def pop(self) -> str or None:
        probe = self.begin
        result = None
        if probe is None:
            raise IndexError("Can't pop from empty SingleLinkList")
        elif probe.next is None:  # pop begin
            result = self.begin
            self.begin = None
        else:
            while probe.next.next:  # reach the second last node of ssl
                probe = probe.next
            result = probe.next 
            probe.next = None
        return result and result.value  # if result is None: return None; else: return resu

Thank you @gantrol for the explanation and for the code.
I also do not understand why Attribute Error is not raised in the following code:


def pop(self) -> str or None:
        """Removes the last item and returns it."""
        if self.before_last:
            removed_node = self.end.value
            self.before_last.next = None
            node = self.begin
            count = 0
            while node.next:
                node_before_last = node
                node = node.next
                count += 1
            if count == 1:
                self.begin = node_before_last
                self.end = node
                self.before_last = None
            else:
                self.before_last = node_before_last
                self.end = node
            return removed_node

        elif self.begin.next is None:
            removed_node = self.begin.value
            self.begin.value = None  # change introduced here: instead of self.begin, there is self.begin.value
            return removed_node

        elif self.begin.next:
            removed_node = self.end.value
            self.begin.next = None
            return removed_node

        return None

I’m on my phone at work, but looking at your push(), it looks like you need to test your push method before you go any further.

You should be able to run through the nodes from begin - end without any hiccups, and I don’t see your begin connecting to the rest of the nodes.

I ran through the push (it works my apologies) , there’s a lot of syntax I didn’t recognize in your code, but now I’m really curious about it. The before_last is a part of the list, but not the node. What are the intentions for that part?

Hi @nellietobey! Thanks for the input! Yeah, the before_last was a part of the list. I wanted to have easier access to the node before last without having to iterate through the list 2 times. But I have changed and changed the code ever since. It seemed that introducing the last_before node was not a good idea.

My problem is that I did not understand why in the first piece of code I posted AttributeError appears (this has been explained by @gantrol) , but in the second piece of code I posted Attribute Error does not appear anymore(not explained).

Thank you!

Let me see if I can walk through this in the code:

def pop(self) -> str or None:
        """Removes the last item and returns it."""
        if self.before_last: #<--- if self.before_last exists: do something:  Does it always exist?
            removed_node = self.end.value
            self.before_last.next = None
            node = self.begin
            count = 0
            while node.next:
                node_before_last = node #you are always setting to node_before last
                node = node.next # you are going to the next node, from node_before last
                count += 1 # you are incrementing the counter. count now = 1 first go
            if count == 1: #This will execute first, because on first run, count == 1
                self.begin = node_before_last #your setting the self.begin to node_before last
                self.end = node #self.end is now the original end node I think
                self.before_last = None #you are setting node_before last to None
            else: # this will execute every time after the first
                self.before_last = node_before_last
                self.end = node
            return removed_node # A return statement ends the while loop
            # It will never make it past the first loop.

        elif self.begin.next is None: # this is checking if self.begin.next is None
            # it will not execute if the first if executes, or if self.begin.next is != None.
            removed_node = self.begin.value
            self.begin.value = None  # change introduced here...
            return removed_node # return removed_node ends the function call for pop()

        elif self.begin.next: # if self.begin.next exists All other if elif have not activated
            removed_node = self.end.value #set end.value as the removed_node
            self.begin.next = None #set the begin.next to None
            return removed_node #return this removed node... IT could even be None
            # this return statement ends the pop() execution


        return None # if none of these if/elif statements qualifies-activates, return None.

Your pop has qualifiers for each if. The only qualifier-activation for your while loop, is 'IF before_next exists, is not Null or None, or NoneType. If it is None, this will run to the next elif, and check if it is valid/true, if not, it moves on until it reaches no other if/elif/else. In that case your function return’s None.

I think this would return a NoneType error if in that while loop of the first if clause, your self.end was == None. Try not pushing to the list, and then running pop on an empty list. But do something like :

linkedlist = SingleLinkedList()

linkedlist.before_last = "carrots"

linkedlist.pop() 
2 Likes

Hi @nellietobey,

I am sorry for the late reply. I did not have internet for a period of time (good that you can code without internet…).

Thank you for going through the code and for the explanation!

1 Like