Hello! Welcome to the second lesson in our module on Professional Python for Data Engineering.
In our previous lesson, we focused on the tools for processing data efficiently. You learned how to choose the right data structure for the job and use comprehensions and generators to handle large datasets with high performance and low memory overhead.
Today, we shift our focus from what the code does to how the code is organized. This lesson addresses the learning outcome: "Write modular and reusable code using functions and classes." Building on your ability to manipulate data, you will now learn how to structure your logic to create professional-grade data pipelines that are easy to read, test, and maintain. This is a crucial step in moving from writing scripts to engineering robust systems.
1. The Core Idea: What is Modular Code?
Modular programming is the practice of breaking down a large program into smaller, independent, and interchangeable modules. Each module is responsible for one specific piece of functionality. This approach has several key benefits that are vital in data engineering:
- Maintainability: When a bug occurs in a specific transformation, you only need to fix it in one place.
- Reusability: A function that cleans phone numbers can be used in multiple data pipelines across your organization.
- Testability: Small, self-contained units of code are much easier to test than a single, monolithic script.
- Readability: Well-organized code is easier for you and your colleagues to understand.
To get a better sense of these principles, take a look at the following short article.
A Simple Guide to Writing Modular Python Code
The article 'A Simple Guide to Writing Modular Python Code' provides an excellent, high-level introduction to the concept of modularity and its benefits.
Please read the introductory section titled 'What is Modular Programming, Anyway?'. This sets the stage for why we want to structure our code this way.
The two fundamental building blocks for creating modular code in Python are functions and classes. Let's explore each one.
2. Functions: The Atomic Units of Action
A function is a block of code that performs a specific, repeatable action. In the context of data engineering, you should think of functions as the primary tool for implementing data transformations. A well-designed function:
- Does one thing and does it well. This is the Single Responsibility Principle. A function shouldn't fetch data, clean it, and save it. It should do just one of those things.
- Is "pure" when possible. A pure function's output depends only on its inputs, and it has no side effects (like modifying a global variable or writing to a file). This makes them predictable and easy to test.
- Has a descriptive name and clear documentation (docstring).
Consider this simple refactoring example. The "Before" code has duplicated logic inside the if/else block. The "After" code extracts the common logic, making the intent clearer. This is a micro-example of creating modularity.

3. Classes: Blueprints for Objects with State and Behavior
While functions are ideal for stateless actions, we often need to manage information—or state—along with the operations that act on that state. This is where Object-Oriented Programming (OOP) and classes come in.
A class is a blueprint for creating objects. An object bundles together:
- Attributes (state): Data or properties associated with the object (e.g., a database connection's hostname, username).
- Methods (behavior): Functions that belong to the object and can operate on its attributes (e.g.,
connect(),disconnect()).
Since your goal is to build a strong foundation "from scratch," the following comprehensive video will walk you through the fundamentals of creating and using classes in Python.
Python Object Oriented Programming (OOP) - For Beginners
The video 'Python Object Oriented Programming (OOP) - For Beginners' by Tech With Tim is a detailed tutorial. It covers the core concepts you need to understand how classes work.
Please watch from 05:36 to 16:45. This section is the most critical part of the video and covers: How to define a class (class Dog:). The special __init__ method for initializing a new object. The meaning of self and how it refers to the specific instance of the class. Defining attributes (self.name) to store state. Defining methods (functions inside a class) that operate on that state.
To give you a relevant analogy from your physics background, you could model a Particle as a class. Its attributes might be mass, charge, and momentum. Its methods could be get_energy() (which calculates ) or decay() (which might return a list of new Particle objects). The Particle class bundles the state (mass, etc.) and the behavior (get_energy) into a single, logical unit.
4. When to Use Functions vs. Classes: A Data Engineer's Guide
Now for the central question: how do you decide whether to write a function or a class? The answer depends on whether you are primarily concerned with actions or with state.
This next video provides an excellent mental model for making this decision.
Functions vs Classes: When to Use Which and Why?
The video 'Functions vs Classes: When to Use Which and Why?' by ArjanCodes clearly frames this choice. It argues that functions are for action-focused code, while classes are for state-focused code.
Watch the entire video. It's short and directly addresses our key question with practical examples, one of which is data analysis—perfect for our context.
To apply this directly to data engineering, let's turn to an article written specifically for data engineers that provides a powerful rule of thumb.
Should Data Pipelines in Python be Function based or ...
The article 'Should Data Pipelines in Python be Function based or Object-Oriented (OOP)?' gives a fantastic, practical framework for data engineers.
Please read two sections: Section 2: 'Data transformations as functions lead to maintainable code': This reinforces why pure functions are great for your transformation logic. Section 3: 'Objects help track things (aka state)': This gives concrete data engineering examples where classes are the right choice, such as managing database connections or configurations for systems like Spark.
In summary, for a typical data pipeline, you will use a hybrid approach:
- Use functions for the core transformation logic (e.g.,
calculate_age_from_dob,standardize_address_format). These are stateless actions. - Use classes to manage stateful components like database connectors (
PostgresConnector), API clients (SalesforceClient), or loggers. These objects hold configuration (state) and provide methods to interact with external systems.
Test your understanding!
Imagine you are building a data pipeline to process daily sales data. The pipeline needs to:
- Connect to a source PostgreSQL database to fetch raw sales records.
- Remove any records where the
sale_amountis negative or zero. - Anonymize the
customer_emailfield by hashing it. - Load the cleaned and transformed data into a target Snowflake data warehouse.
For each of the four steps, would you primarily use a function or a class? Briefly explain why, based on the "action-focused vs. state-focused" principle.
Show answer
-
Connect to PostgreSQL: Use a class (e.g.,
PostgresConnector). This is stateful. The object needs to hold the connection credentials (host, user, password) as attributes and provide methods likeconnect(),fetch_data(), andclose(). -
Filter invalid records: Use a function (e.g.,
filter_invalid_sales(data)). This is a stateless, repeatable action. The function takes data as input and returns the filtered data as output, without needing to store any state itself. -
Anonymize email: Use a function (e.g.,
hash_email(email_string)). Like filtering, this is a pure, stateless transformation. It takes a string as input and returns a hashed string. -
Load to Snowflake: Use a class (e.g.,
SnowflakeLoader). Similar to the PostgreSQL connector, this is stateful. The object holds connection details for Snowflake and provides methods likeload_dataframe(df, table_name).
5. Advanced Patterns for Reusability
As you build more pipelines, you'll notice recurring patterns (e.g., Extract -> Transform -> Load). OOP provides powerful features like inheritance to create reusable templates.
- Inheritance allows a "child" class to inherit all the attributes and methods from a "parent" class. You can then extend or override the parent's behavior in the child class.
For example, you could define a generic BaseETLPipeline class that has extract, transform, and load methods. Then, for a specific UserPipeline, you could inherit from BaseETLPipeline and just implement the specific logic for users, reusing the common framework.

If you're interested in how inheritance works in code, the "Tech With Tim" video you saw earlier has a great section on it.
Python Object Oriented Programming (OOP) - For Beginners
This optional segment explains inheritance, which is a key concept for building reusable class-based frameworks in larger projects.
You can watch from 28:15 to 40:46 to see how inheritance is implemented in Python, including the use of super() to call methods from the parent class.
Conclusion
In this lesson, we established the fundamental principles of writing modular and reusable code in Python, a cornerstone of professional software and data engineering.
Key Takeaways:
- Modularity makes code easier to maintain, test, and reuse.
- Functions are best for stateless, repeatable actions, such as individual data transformation steps.
- Classes are best for managing state and behavior together, such as connectors to external systems or representations of real-world entities.
- Effective data pipelines combine both functional and object-oriented paradigms, using the right tool for the right job.
By consciously deciding between functions and classes, you can start structuring your projects in a clean, professional way that will make your life (and your colleagues' lives) much easier.
Preview of the Next Lesson:
In our next lesson, we will focus on "Read and write complex data formats like CSV, JSON, and Parquet using libraries like Pandas and PyArrow." You'll be able to apply what you've learned today by writing well-structured functions and classes to handle these data I/O tasks, which often require managing state (like file paths or schemas) and performing actions (like reading or writing).