PCAP - Python Certification Course

Object Oriented Programming

Procedural vs OOP approach

A stack is a fundamental data structure that follows the last-in-first-out (LIFO) principle, meaning that the last element added is the first to be removed. In this guide, we demonstrate how to implement a stack in Python using both procedural and object-oriented programming (OOP) approaches.

Procedural Approach

In the procedural approach, the stack's data and the associated logic (functions for pushing and popping) are separate. Below is an example where the stack is represented as a simple list, and helper functions manipulate it.

stack = []

def push(val):
    stack.append(val)

def pop():
    val = stack[-1]
    del stack[-1]
    return val

push(3)
push(2)
push(1)
print(pop())
print(pop())
print(pop())

Note

In this implementation, the global stack variable can be accessed and modified from anywhere in your code, which can lead to unintended side effects. For multiple stacks, additional functions must be created, reducing reusability.

Object-Oriented Approach

The object-oriented method encapsulates both data and behavior within a class, promoting modularity and data protection. We begin by defining a Stack class with an initializer that confirms the creation of a new stack instance.

class Stack:
    def __init__(self):
        print("I am in the constructor function!")

stack_object = Stack()

Output:

I am in the constructor function!

Next, we introduce a property for the stack's data. Initially, this property (stack_list) is public, offering direct access:

class Stack:
    def __init__(self):
        self.stack_list = []

stack_object = Stack()
print(len(stack_object.stack_list))

Output:

0

Note

Although a public attribute is simple to implement, it compromises encapsulation. To protect the internal data structure, converting it to a private attribute is recommended.

To enforce encapsulation, we modify stack_list to a private attribute by prefixing it with two underscores. Attempting to access this attribute externally will raise an error:

class Stack:
    def __init__(self):
        self.__stack_list = []

stack_object = Stack()
# The following line will raise an AttributeError because __stack_list is private.
print(len(stack_object.__stack_list))

Output:

AttributeError: 'Stack' object has no attribute '__stack_list'

When a class attribute starts with two underscores, it becomes private and can only be used within the class. This encapsulation is crucial for maintaining the integrity of your data structure.

Next, we add the push and pop methods to the Stack class for manipulating the internal stack:

class Stack:
    def __init__(self):
        self.__stack_list = []

    def push(self, val):
        self.__stack_list.append(val)

    def pop(self):
        val = self.__stack_list[-1]
        del self.__stack_list[-1]
        return val

stack_object = Stack()
stack_object2 = Stack()

stack_object.push(3)
stack_object.push(2)
stack_object.push(1)

stack_object2.push(10)
stack_object2.push(9)

print(stack_object2.pop())
print(stack_object.pop())
print(stack_object.pop())
print(stack_object.pop())

This object-oriented implementation clearly demonstrates that separate instances of the Stack class maintain independent data, providing a scalable solution for handling multiple stacks.

Extending the Stack Class with Inheritance

Inheritance allows you to extend the functionality of a base class without modifying it. Consider a scenario where you need to track the cumulative sum of the elements in the stack. Instead of altering the original Stack class, you can create a subclass AddingStack that inherents from Stack and adds new behavior.

In the subclass:

  • The constructor calls the base class constructor using super().__init__().
  • The push method is overridden to update the cumulative sum before invoking the base class method.
  • The pop method is overridden to subtract the popped value from the cumulative sum.
  • A new method get_sum is introduced to retrieve the current sum.
class AddingStack(Stack):
    def __init__(self):
        super().__init__()
        self.__sum = 0

    def get_sum(self):
        return self.__sum

    def push(self, val):
        self.__sum += val
        super().push(val)

    def pop(self):
        val = super().pop()
        self.__sum -= val
        return val

stack = AddingStack()
stack.push(10)
stack.push(5)
print(stack.get_sum())

Output:

15

Note

If a subclass does not define its own constructor, it automatically inherits the constructor from its superclass. In cases where no additional behavior is required, you can simply use the pass keyword to inherit all functionalities without modification.

This comprehensive guide on stack implementation in Python illustrates both procedural and object-oriented techniques, along with best practices such as encapsulation and inheritance.

That's it for now—it's time to gain some hands-on practice!

For more details on Python programming and data structures, check out Python Official Documentation and Data Structures in Python.

Watch Video

Watch video content

Practice Lab

Practice lab

Previous
Introduction To OOP