FAQ: Stacks: Python - Stacks Python Push and Pop

Hello, @bhuveh, and welcome to the Codecademy Forums!

Using push and pop methods to perform operations on the Stack ensures that the state of the Stack is adjusted properly whenever a change is made to its contents. Since those two operations involve changing pointers to nodes, as well as managing the values at those nodes, we need to make sure that the entire operation is properly coordinated. This makes the Stack easier to use, since the user can call a single method to perform an operation.

Edited on August 17, 2020 to add the following:

Concerning the use of the get_next_node and set_next_node methods of Node, that allows for the possibility of adding to the functionality of the operations. For instance, we could decide to provide a time stamp or operate a counter when either of those operations is performed.

If programmers are using methods to perform operations, instead of using direct access, it would be simple to add to the functionality of those methods without requiring the programmers to revise all of their existing code.

1 Like

Thank you for your answer, but I apologize for not being clear.

My question is that why do we use
value1 = example_node.get_value()
instead of
value1 = example_node.value

and
example_node.set_value(value2)
instead of
example_node.value = value2

for all exercises involving nodes (not just stacks or any structure in particular).

If programmers are using methods to perform operations, instead of using direct access, it would be simple to add to the functionality of those methods without requiring the programmers to revise all of their existing code.

Is it just for adding future functionality, like you explained?

1 Like

More generally, it is to provide a standardized stable means for the user to interact with the object. In a production environment, classes and modules would be documented. Once released, programmers would create lots of code, using the standardized documented modes of access. With time, any changes in these standardized modes of access would require increasing numbers of programmers to revise their code that utilizes the objects. So, with forethought, we could provide means of access that will not need to be revised later on. As the internal details of classes and modules are upgraded over time, we would like to shelter users from those details, so that their previously written code does not also need revision. We can do this by providing access via methods, rather than having the programmer access properties directly. With methods, the public interface can be stabilized while the internal workings of the objects are refined. If instead, we had standardized access via internal details, then changing those details would create more difficulty for programmers who use the objects.

While a Node is a simple class that might not need future upgrading, a more complex class could very well be upgraded after an initial release. Accordingly, the Node class was designed as a model for more complex classes. This involves using getters and setters, or more generally, it involves the use of methods to access or alter internal information, which hides the internal details.

Edited on August 18, 2020 to add the following:

Another issue is that some interactions involve multiple steps. For example, adding a value to a data structure might include validity checks and other operations. Access via methods can guarantee that all tasks related to a transaction are coordinated correctly.

1 Like

Awesome! Thank you so much!

1 Like

So, when I first do these lessons I’m using VSCode to suss out the solution and this was the initial code I used.

def push(self, value):
    item = Node(value, self.top_item)
    self.top_item = item

When Codecademy generated the error I realize you wanted this:

def push(self, value):
    item = Node(value)
    item.set_next_node(self.top_item)
    self.top_item = item

And, from your explanation, I take it both work equally well at doing exactly what is being asked. However, if we wanted to include something like print(“Node added.”) later on, putting that into the .set_next_node() method is preferable to doing it in .push(), right?

Hello @coreblaster01537,

As you are developing the code for a data structure, use calls print where you need them for debugging purposes. After refinement is complete, remove the calls to print when they are no longer necessary for the proper functioning of the data structure.

For a general purpose data structure, such as a stack, you probably would not want to permanently include calls to print, because the output would likely become a liability. Therefore, after debugging is complete, and you are using the data structure to perform a task, use external calls to print to test and debug the code for performing that task.

Following is code that might be used for debugging a calculator that uses a stack:

# functions for debugging the calculator
def push_with_report(val, stack):
  stack.push(val)
  print(f"Pushed: {val} ")

def pop_with_report(stack):
  val = stack.pop()
  print(f"Popped: {val} ")
  return val

calc = Stack()
push_with_report(7, calc)
push_with_report(4, calc)
val = pop_with_report(calc)

Output:

Pushed: 7 
Pushed: 4 
Popped: 4

Edited on November 12, 2020 to add the following:

As a general principle, use print calls to first test the lower level components of a system, Then, as the lower level components are perfected, progressively move the use of print calls to higher level components that are being tested.

1 Like

Just wondering/noticing, the pop method doesn’t actually “remove” a node in this exercise, right?
It just returns data.

The code of the pop method:

  def pop(self):
    item_to_remove = self.top_item
    self.top_item = item_to_remove.get_next_node()
    return item_to_remove.get_value()

When we want to remove nodes, we don’t actually delete them in these exercises. Rather, say the node we want to remove is node_x. We assign the link of node_x's previous node to node_x's following node. Example follows.

Before Removal

nodes: node_w | node_x | node_y
links: node_x | node_y | node_z

After Removal

nodes: node_w | node_x | node_y
links: node_y | node_y | node_z

Since node_x is no longer accessible because there’s no way to navigate through it since node_w has reassigned its next_node, it is essentially “removed”.


We remove the link to item_to_remove in the pop() method, then return its value. So, in short, we do remove the node from the stack in addition to returning its value.

3 Likes

Oh yes! I forgot about how nodes link and how that effects things. Thank you for this insight!

1 Like

I’m a beginner here just like you. But from what i understand, when a node is pointing to another node say
Node_a points to Node_b
and you then point Node_c to Node_b, Node_a is orphaned and therefore removed. I believe why we sent the_head_node to None while traversing the linked list in the previous linked list segment was to exit the while loop. But please clarify to be sure. Hope this helps.

This is false. Nodes are orphaned when there is no way of accessing them because there are no other nodes pointing to them. I provided an example a bit earlier in this topic.

If I were to illustrate the situation you described, it would look like the following.

node_a  -->  node_b
node_c  -----^

As you can see, both node_a and node_c would point to node_b. Just because we made node_c point to node_b, that doesn’t mean that node_a no longer points to node_b.


That aside, the exercise the user is asking about is related to stacks, not linked lists, so the implementation and functionality are different. The Stack class in the exercise uses an instance variable to keep track of the top-most node and this is used in orphaning/removing the top value when we want to pop from the stack.

1 Like

Now I’m back in a state of total confusion and not knowing anything because that is how i have been explaining it to myself in my notes since the linked lists lesson. I need help please?!!

In a singly linked list, there is only one direction of traversal through the list.

node_a -> node_b -> node_c -> null

We would not link node_c to node_a, but, if we wanted to remove node_b, we would link node_a to node_c and the orphaned node_b would drop out of the picture. In a key ring, node_c would link forward to node_a, but only in that configuration.

Only in a doubly linked list would we point from node_b back to node_a. In a key ring we would be able to traverse in both directions given the correct links as apply.

node_a -> node_b -> node_c -> node_a
node_a <- node_b <- node_c <- node_a

In order for node_b to be orphaned, both links from node_a and node_c would need to be removed.

Not sure any of this helps with your question. See if you can find a way to frame the question around the most confusing bits and we can have another go at it.

1 Like

This is exactly what I thought was happening actually. Thank you very much.

1 Like

If I’m not misunderstanding, shouldn’t the representation be:
nodes: node_w becomes node_w.next_node = node_x | node_y
links: node_y | node_z
?
As we are not removing the first node with the .set_next_node() method, but with = get_next_node()?

Sorry, I’m not understanding your question. Could you rephrase it and format the example so it’s easier to see what you’re trying to convey?

I completely missed the fact that you gave a different example with node number 2 being removed, not the head node.
Sorry for wasting your time :frowning:

But since I have your attention, and to not have it be a complete waste of your time, shouldn’t it be:

We remove the link from item_to_remove in the pop() method, then return its value. So, in short, we do remove the node from the stack in addition to returning its value.
instead of:
“We remove the link to item_to_remove in the pop() method, then return its value. So, in short, we do remove the node from the stack in addition to returning its value.”

Or is there no direction connotation necessary when we talk about links?

No worries, we’re all here to help one another! :slight_smile:

I think using the word “link” might be confusing you a bit here. I believe that to should be used rather than from. When we remove the link to a node, we remove our ability to access that node. When we remove the link from a node (here’s where you might have gotten confused), if we are talking about a node’s .next_node as the link, then we are removing our ability to access another node and not the node we are currently looking at.


I’ll try explaining this another way. Because the Stack class uses a .top_item instance variable to keep track of the topmost node, rather than removing a node’s .next_node link to item_to_remove, we are changing the value of self.top_item and thereby removing our ability to access it in the stack.

What’s happening in the .pop() method is as follows:

  1. We set item_to_remove to the topmost node in the stack.
  2. We set the .top_item instance variable of the stack to have the value of self.top_item.get_next_node(). This means we are setting the topmost node of the stack to the node that is second to the top of the stack.
  3. Since self.top_item is equal to the second-topmost node in the stack, and we don’t have any other way of accessing the topmost node (which is stored in item_to_remove), the topmost node is inaccessible and considered orphaned. This is because our only way of accessing the topmost node was through the .top_item instance variable and now .top_item stores a different node.
  4. We return the popped node, item_to_remove.
1 Like

I appreciate the detailed answer, it helped to really sediment the notions in my head.
I see the distinction more clearly now.
What was causing my(more of a linguistical) confusion was the fact that when learning nodes and linked lists we saw that nodes contain data and a pointer “->”, a link to the next node(which implies a directional component: A = Node(value, B) the pointer is from A to B). A top.item has no other node linking to it, in this paradigm.
But in the example given, I guess by removing any link to the head node we mean “we remove any way to access our head node” and I see how saying we remove any link from the Node would not correctly express what is happening - us removing the node completely.

Thank you for the responses, they always help a lot!

1 Like

Defines the push() method that takes the parameter value

def push(self, value):
# Instantiates a Node with value as an argument and assigns it to the variable item
item = Node(value)
# Sets item‘s next node to the stack’s current top_item
item.set_next_node(self.top_item)
# Sets the stack instance’s top_item equal to the new item
self.top_item = item