From Scripts to Software: My OOP Transformation at DataraFlow

Week 2 at DataraFlow: Architecting Code with Object-Oriented Programming
The transition from Week 1 to Week 2 at DataraFlow felt like moving from learning how to swing a hammer to understanding architectural blueprints. This week, we left behind simple scripts and stepped into the world of Object-Oriented Programming (OOP). Our goal was to learn how to structure code for reusability, scalability, and clarity; skills essential for any data scientist or software engineer.
The journey was intense, culminating in building a fully functional Library Management System. Here’s a breakdown of my deep dive into OOP.

What I Set Out to Learn/Do
The objectives for Week 2 were ambitious and clear:
Master the Pillars of OOP: Iteration, Encapsulation, Inheritance, and Polymorphism.
Implement Core OOP Concepts: Build classes, create objects, and use methods and properties effectively.
Design Class Hierarchies: Use inheritance to create parent and child classes that model real-world relationships.
Utilize Advanced Features: Implement iterators, understand method resolution order (MRO), and control variable scope with
globalandnonlocal.Apply Knowledge in a Capstone Project: Build a Library Management System that demonstrates all learned concepts.
The How: Building with Objects and Classes
This week was a deep dive into the mechanics and philosophy of OOP. Here’s how I tackled each concept.
1. The Foundation: Classes, Objects, and the self Keyword
I started by understanding that a class is a blueprint, and an object is an instance built from that blueprint. The __init__ method is the constructor that initializes each new object, and the self keyword is how an object refers to its own attributes and methods.
# Creating a simple Person class
class Person:
def __init__(self, name, age): # Constructor
self.name = name # Instance variable
self.age = age
def greet(self): # Method
print(f"Hello, my name is {self.name}!")
# Creating an object (instance) of the Person class
person1 = Person("Alice", 30)
person1.greet() # Output: Hello, my name is Alice!
2. Inheritance: The "Is-A" Relationship
Inheritance allows a new class (child) to inherit attributes and methods from an existing class (parent). I used the super() function to call the parent class's methods, which is a cleaner and more maintainable approach than calling the parent directly.
# Parent Class
class Vehicle:
def __init__(self, brand, model):
self.brand = brand
self.model = model
# Child Class inheriting from Vehicle
class Car(Vehicle):
def __init__(self, brand, model, doors):
super().__init__(brand, model) # Call parent constructor
self.doors = doors
my_car = Car("Toyota", "Corolla", 4)
print(f"My car is a {my_car.brand} {my_car.model} with {my_car.doors} doors.")
3. Polymorphism: "Many Forms"
Polymorphism allows methods to do different things based on the object that is acting upon them. The same method name can be used for different types. This is a powerful concept for writing flexible and reusable code.
class Bird:
def speak(self):
return "Chirp!"
class Dog:
def speak(self):
return "Woof!"
# The same function works with different objects
def make_animal_speak(animal):
print(animal.speak())
bird = Bird()
dog = Dog()
make_animal_speak(bird) # Output: Chirp!
make_animal_speak(dog) # Output: Woof!
4. Encapsulation & Scope: Controlling Access
I learned to manage data integrity through understanding scope. The global keyword allows modifying a variable in the global module scope, while nonlocal is used to modify a variable in a function scope.
# Understanding scope with the nonlocal keyword
def outer():
message = "Hi"
def inner():
nonlocal message # Refers to the 'message' in the outer function
message = "Hello from inside!"
inner()
print(message) # Output: Hello from inside!
outer()
5. Creating Custom Iterators
I discovered that for loops work by calling iter() and next() behind the scenes. I learned to create my own iterators by implementing these methods, which gives fine-grained control over iteration behavior.
# Creating a custom iterator that counts down
class CountDown:
def __init__(self, start):
self.current = start
def __iter__(self):
return self
def __next__(self):
if self.current <= 0:
raise StopIteration
else:
num = self.current
self.current -= 1
return num
# Using the iterator in a for loop
for num in CountDown(5):
print(num) # Output: 5, 4, 3, 2, 1
The Capstone Project: A Library Management System
The weekly task culminated in building a comprehensive Library Management System. This project required using every OOP concept we learned.
Key Features Implemented:
Inheritance: Created a base
Bookclass withEBookandPrintedBooksubclasses.Polymorphism: Each book type overrides the
get_description()method to provide specific details.Encapsulation: Used protected attributes (e.g.,
_borrowed_books) and provided methods to interact with them.Iterators: Made the
Libraryclass iterable to loop over all books and made eachMemberiterable to loop over their borrowed books.Class Variables: Used
total_borrowedto track borrowing across all members.
Snippet of the Library Class:
class Library:
def __init__(self, library_name):
self.__library_name = library_name # Private attribute
self.books = []
self.members = []
def __iter__(self):
"""Iterator over all books"""
return iter(self.books)
# Getter for private attribute
def get_library_name(self):
return self.__library_name
# ... (more methods for adding books, searching, etc.)
Running the system demonstrated all these features working in harmony, from students and teachers borrowing books with different limits to searching the catalog and iterating through collections.
Video Summary: Data Structures and Algorithms in Python
To reinforce our OOP learning, we summarized a video on "Data Structures and Algorithms in Python." I ended up dozing off mid-watch but rewound to catch the key points; turns out, persistence pays off! The video used a relatable analogy of a carpenter selecting the right tool from their toolbox for a specific task, emphasizing how choosing the appropriate data structure is crucial for efficient problem-solving as a programmer.
Joe James started off with the basics of sequences: strings, lists, and tuples. We explored data manipulation techniques like slicing (e.g., seq[1:3]), concatenating (seq1 + seq2), multiplying (seq * 3), membership checks (in and not in), iteration with for loops, len(), and built-in functions like min(), max(), and sum().
Sorting got a spotlight with sorted() (which returns a new list) and key-based sorting (e.g., sorted(items, key=lambda x: x['name'])). The index() method was demoed to find the position of the first occurrence of an item, like fruits.index('apple').
A standout tip for efficiency: unpacking! Instead of tedious individual assignments (e.g., a = seq[0]; b = seq[1]), you can unpack directly: a, b, c = seq for n items into n variables; a game-changer for cleaner code.
It refreshed list operations: comprehensions ([x*2 for x in range(10)]), plus methods like append(), extend(), insert(), pop(), remove(), reverse(), and sort(). Tuples were highlighted for immutability, sets for unique elements and fast lookups, and dictionaries for key-value pairs with methods like get(), keys(), and values().
The video wrapped with advanced structures implemented via OOP: stacks (LIFO with push()/pop()), linked lists (nodes with next pointers), binary search trees (balanced trees for fast searches), and graphs (nodes/edges for networks). Defining classes and methods for these really tied back to our OOP focus, showing how data structures aren't just tools but objects one can customize and extend.
This summary not only bridged our OOP week to algorithmic thinking but also primed us for handling real data flows in data science projects.
Challenges Faced & How I Overcame Them
Challenge: Understanding
super()and Method Resolution Order (MRO)What happened? In a multiple inheritance scenario (e.g., a
Duckclass that inherits fromFlyerandSwimmer). I was unsure which parent methodsuper()would call.My Solution: I learned about the MRO; the order in which Python searches for methods in a class hierarchy. By checking
Class.__mro__, I could see the order and understand thatsuper()calls the next method in that sequence.
My Key Takeaways & Perspective
Week 2 was a paradigm shift. Here’s my perspective:
OOP is About Design, Not Just Syntax: The biggest lesson was that OOP is a way of thinking about problems. It’s about breaking down a system into logical, interacting objects, which makes complex software much easier to manage.
Code Reusability is a Superpower: Inheritance and composition allow you to build on existing code rather than rewriting it. This makes development faster and reduces errors.
Planning is Crucial: With OOP, you have to think about the structure of your program before you write a line of code. A little planning upfront saves a lot of debugging later.
These Are Foundational Data Science Skills: Libraries like Pandas (DataFrames are objects!) and Scikit-learn (models are objects!) are built on these principles. Understanding OOP is key to moving from using these tools to understanding and extending them.
I'm excited to see how these architectural principles form the foundation of the data science and machine learning libraries we'll explore in the coming weeks.
Resources That Helped Me
Python Official Documentation: The definitive source on classes and the
super()function.
#Python #OOP #ObjectOrientedProgramming #Inheritance #Polymorphism #Programming #LearningJourney #DataScience #DataraFlow #DataNinjas
Previous Article: Coding My Way Through Week 1: A Data Science Intern’s Story
Next Article: Week 3: Coming soon!



