Stacks in Python: The Object-Oriented Approaches
Imagine you want to create something called a "stack" in a computer program. It's like a collection of items arranged on top of each other, and you can add or remove items from the top. To make this work, you need a place to store these items.
So, we're going to use a list to store these items in our program. But how do we make this list work as part of our stack idea?
We start by creating a special thing called a "class" in the program. Think of a class as a blueprint that tells the computer how to create and manage our stack.
Here's where it starts:
class Stack:
Now, we have two important things we want from this class:
- We want the class to have a place to store our stack's items. For every stack we create, we need a separate place to keep its items. This place should be like a private spot only for that stack.
- We want to hide this place from anyone using our class. They shouldn't see or mess with this storage directly.
Python, unlike some other programming languages, doesn't automatically provide a special place to store our stack's items. So, we need to create this place ourselves inside the class.
Here's how we do it:
class Stack:
def __init__(self):
self.stack_list = []
This might look a bit strange, but it's like telling the computer:
"Every time you create a new stack, make a special storage called stack_list
just for it. And keep it hidden from others."
The __init__
function is like a special helper that gets called automatically when a new stack is created. It's responsible for setting up the stack's storage.
In simple words, think of it like setting up a new room (storage
) inside a building (class
) for each stack you create.
So, to sum it up: We're creating a class for our stack. Inside the class, we make sure each stack has its own private storage using the __init__
function. This way, we're making sure that every time someone wants to use a stack, it comes with its own hidden storage space.
A constructor is a special kind of method that is called when an object is created. It is used to initialize the object and set up its initial state.
The constructor is always named __init__
in Python. It takes at least one parameter, which conventionally represents the instance of the object itself and is typically named self
.
The constructor can be used to do things like:
- Create and initialize the object's properties
- Perform any other necessary initialization
So, from the above example, we are actually creating a constructor.
The constructor is used to create a private storage space for each stack object. This storage space is not directly accessible from outside the class, which helps protect the integrity of the stack's data.
Let's add a very simple constructor to the Stack
class we've been working on. In this example, we'll add an initialization message to the constructor:
class Stack:
def __init__(self):
self.stack_list = []
print("Hi! A new stack is created.")
# Creating an instance of the Stack class
stack_object = Stack()
In the example:
- The
__init__
method is the constructor.
- The parameter self refers to the instance of the
Stack
object being created.
- Inside the constructor, we initialize the
self.stack_list
attribute to an empty list to create a private storage for each stack.
- We also print a message to indicate that a new stack is created.
Note: The use of self
as the first parameter in the constructor is a convention, and it simplifies the readability and understanding of your code. While it's not a strict requirement, it's highly recommended to follow this convention. Try to run the code, and you will get the output as shown below:
In the next section, we'll delve into the concept of instantiation, where we'll explore how to create instances of a class and how the constructor plays a crucial role in that process.
Instantiation is the process of creating an object from a class. When an object is instantiated, the constructor of the class is called. The constructor is responsible for initializing the object and setting up its initial state.
In the example above, the following code instantiates a Stack object:
stack_object = Stack()
This code calls the __init__()
method of the Stack
class. The __init__()
method initializes the stack_list
attribute to an empty list and prints a message to indicate that a new stack is created.
The __init__()
method is always called when an object is instantiated. This means that the stack_list
attribute will be initialized for every Stack
object that is created. This ensures that each Stack
object has its own private storage space.
Instantiation is a fundamental concept in OOP. It is the process of creating objects from classes, and it is how we can use the features of a class in our code.
Here are some of the benefits of instantiation:
- It allows us to create objects that are tailored to our specific needs.
- It allows us to reuse code.
- It makes our code more modular and maintainable.
In the previous section, we learned about the object-oriented approaches to implementing a stack in Python. In this section, we will continue our discussion on how to encapsulate data in a stack class by making variables private.
Private Variables
In Python, variables that are prefixed with two underscores (__
) are considered private. This means that they cannot be accessed from outside the class.
For example, the following code defines a stack class with a private variable called __stack_list
:
class Stack:
def __init__(self):
self.__stack_list = []
The __stack_list
variable can only be accessed from within the Stack
class. If you try to access it from outside the class, you will get an AttributeError exception. For instance, the code below will give you an ERROR.
class Stack:
def __init__(self):
self.__stack_list = []
print("Hi! A new stack is created.")
# Creating an instance of the Stack class
stack_object = Stack()
print(stack_object.__stack_list)
Why Make Variables Private?
There are several reasons why you might want to make variables private.
- To protect data: By making variables private, you can prevent accidental changes to the data. This can help to ensure the integrity of the data.
- To enforce encapsulation: Encapsulation is the principle of hiding the implementation details of an object from the outside world. By making variables private, you are enforcing encapsulation and preventing other code from directly accessing the data.
- To improve readability: Private variables are not visible to the user, which can make your code more readable and easier to understand.
Not all variables should be private. You should only make variables private if you need to protect them from accidental changes or if you want to enforce encapsulation.
For example, in the stack class, the __stack_list
variable should be private because it stores the data for the stack. This data should be protected from accidental changes.
Later, we will add two methods to the Stack
class: push()
and pop()
. How are we going to treat these methods? Unlike the constructor, both methods in the Stack
class do not need to be private. These methods are used to manipulate the data in the stack, and it is okay for other code to call them.
In brief, encapsulating data by making variables private is a fundamental principle of object-oriented programming. It helps to protect data, enforce encapsulation, and improve readability.
In the next section, we will discuss methods in more detail and discuss how they contribute to the functionality of classes.