Python
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Data Types
-
Section 3: Collections
-
39: Set Operations: Union, Intersection, Difference
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Turtle Graphics and Early Practice Projects
-
Section 7: Working with Files and I/O
-
Section 8: Regular Expressions
-
Section 9: Object-Oriented Python
-
Section 10: Error Handling
-
Section 11: Modules and Packages
-
Section 12: Iterators, Generators, and Functional Tools
-
Section 13: Decorators and Metaprogramming
-
Section 14: Concurrency and Parallelism
-
Section 15: Working with Dates, Times, and Numbers
-
Section 16: Standard Library Deep Dive I: Data Structures
-
Section 17: Standard Library Deep Dive II: System and Introspection
-
Section 18: Standard Library Deep Dive III: Security and Encoding
-
Section 19: Standard Library Deep Dive IV: Text and Data Utilities
-
Section 20: Networking and Web Basics
-
Section 21: Working with Databases
-
Section 22: Testing and Quality
-
Section 23: Advanced Typing
-
Section 24: Context Managers and Resource Handling
-
Section 25: Text, Unicode, and Binary Data
-
Section 26: More Functional and Iteration Tools
-
Section 27: Data Validation and Configuration
-
Section 28: Working with Images and Media
-
Section 29: Property-Based and Documentation Testing
-
Section 30: Packaging and Deployment
-
Section 31: Performance and Internals
-
Section 32: Design Patterns in Python
-
Section 33: GUI Programming
-
Section 34: Security Basics
-
Section 35: Data Structures and Algorithms
-
Section 36: Practical Projects
-
Section 37: Capstone Projects
-
Section 38: Interview and Algorithm Practice
-
Section 39: Writing Idiomatic Python
159: Building Packages and __init__.py
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 functioncalculate_vat(amount)) andinterest.py(containing a functioncalculate_simple_interest(principal, rate, time)). - Create an
__init__.pyfile inside thefin_toolsfolder that exposes bothcalculate_vatandcalculate_simple_interestat the package level. - Create a separate script (outside the folder) that imports
fin_toolsand calls both functions directly from the package (e.g.,fin_tools.calculate_vat(100)) without importing the submodules explicitly.
There are no comments for now.