Skip to Content
Course content

159: Building Packages and __init__.py

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Imagine you've spent the last few days building a set of data processing tools. To keep things tidy, you've moved your logic into a folder called analytics. You've got a file in there called processor.py that contains a great function called run_analysis(). It feels professional, organized, and clean.

Then you try to use it in your main script:

import analytics

# You expect this to work because run_analysis is inside the analytics folder
analytics.run_analysis()

You run the code and Python hits you with this: AttributeError: module 'analytics' has no attribute 'run_analysis'. You're staring at the screen, confused, because the function is right there in the folder. Why isn't Python seeing it?

The "Hidden" Submodule Problem

Here is the deal: by default, importing a package (the folder) doesn't automatically import all the modules (the .py files) inside that folder. When you call import analytics, Python looks at the directory, recognizes it as a package, but it doesn't go hunting through every file inside to see what functions are available. It only loads the package itself.

To get to your function without __init__.py, you'd have to be explicit: from analytics.processor import run_analysis. While that works, it gets tedious and ugly if you have twenty different modules. You don't want your users (or your future self) to have to remember exactly which file every single function lives in.

Using __init__.py to Expose Your API

This is where __init__.py comes in. This file tells Python, "Hey, when someone imports this package, execute this code first." It's essentially the front door of your package.

To fix the bug from earlier, you create a file named __init__.py inside the analytics folder. Inside that file, you "hoist" the function from the submodule up to the package level:

# analytics/__init__.py
from .processor import run_analysis

Notice that dot before processor? That's a relative import. It tells Python to look for processor.py in the same directory as the __init__.py file. Now, when you run your main script again, import analytics triggers the code in __init__.py, which imports run_analysis into the analytics namespace. Now, analytics.run_analysis() works perfectly.

Managing Namespace Clutter

I've seen a lot of developers make the mistake of importing everything into __init__.py. While that seems convenient, it can lead to a bloated namespace and slow import times if your package is huge.

If you have a module with fifty functions, but only three of them are meant to be used by the end-user, only import those three into __init__.py. This allows you to keep your internal "helper" functions hidden away in the submodules where they belong, providing a clean, intentional API for whoever is using your library. It's the difference between handing someone a neatly organized toolbox and dumping a giant bucket of screws and bolts on their desk.




📋 Practical Task

Building a Financial Toolkit Package

Your goal is to create a package that handles simple financial calculations. You need to structure it so that the user doesn't have to navigate your internal folder structure to get the results they need.

Requirements:

  • Create a directory named fin_tools.
  • Inside fin_tools, create two modules: tax.py (containing a function calculate_vat(amount)) and interest.py (containing a function calculate_simple_interest(principal, rate, time)).
  • Create an __init__.py file inside the fin_tools folder that exposes both calculate_vat and calculate_simple_interest at the package level.
  • Create a separate script (outside the folder) that imports fin_tools and calls both functions directly from the package (e.g., fin_tools.calculate_vat(100)) without importing the submodules explicitly.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.