Previous | Tutorial index | Next
Write and use your own modules.
So far, you have been a consumer of Python modules – importing built-in, standard library, and third-party modules to extend your programs. But the true power of Python emerges when you become a creator: writing your own modules to encapsulate reusable logic, share code across projects, and collaborate with other developers.
Creating your own modules is a fundamental skill for any Python programmer. It allows you to:
This tutorial will guide you through the entire process: from writing your first module, to importing it correctly, to handling the nuances of module behavior, and finally to avoiding common pitfalls like circular imports.
A custom module is simply a .py file that you create. It can contain:
Follow the same naming rules as any Python identifier:
if, for, import, etc.).data_processor.py, string_utils.py.Create a file named greetings.py:
# greetings.py
# A constant
GREETING_PREFIX = "Hello"
# A function
def greet(name):
"""Return a friendly greeting."""
return f"{GREETING_PREFIX}, {name}!"
# A class
class Salutation:
def __init__(self, style="formal"):
self.style = style
def greet(self, name):
if self.style == "formal":
return f"Good day, {name}."
else:
return f"Hey {name}!"
Create another file main.py in the same directory:
# main.py
import greetings
print(greetings.greet("Alice"))
print(greetings.GREETING_PREFIX)
sal = greetings.Salutation("informal")
print(sal.greet("Bob"))
Run main.py; you should see:
Hello, Alice!
Hello
Hey Bob!
When you import greetings, Python:
greetings.py (in the current directory or sys.path).greetings.py from top to bottom.greetings and binds it in your namespace.That means any print() calls or other statements at the top level of greetings.py will run immediately upon import – which may be desirable or not. Usually, you want to avoid side effects at import time; instead, place them inside functions or behind the if __name__ == "__main__": guard.
__name__ Attribute – A Hidden GemEvery module in Python has a built-in attribute called __name__. Its value depends on how the module is being used:
| Scenario | Value of __name__ |
|---|---|
The module is being run directly (e.g., python greetings.py) |
"__main__" |
The module is being imported (e.g., import greetings) |
The module's name (e.g., "greetings") |
This distinction allows you to write code that behaves differently when the module is run as a standalone script versus when it is imported as a library. This is the key to creating reusable modules that can also be tested or demonstrated independently.
if __name__ == "__main__": GuardThe most common use of __name__ is to protect code that should only run when the module is executed directly, not when imported.
Add this to greetings.py:
# greetings.py (continued)
def main():
# Demo or test code
print("=== Greetings Module Demo ===")
print(greet("World"))
formal = Salutation("formal")
print(formal.greet("Dr. Smith"))
if __name__ == "__main__":
main()
Now:
python greetings.py, you will see the demo output.greetings in another script, the demo code does not run automatically. This prevents unwanted output and side effects.main() function, and call it only inside the guard.unittest or doctest modules) when the module is run directly.When you import a module, Python searches in the directories listed in sys.path. The first entry is usually the directory of the script you are running (or the current working directory in the interactive shell). Therefore, if your module is in the same folder as your main script, it will be found.
Suppose you have this structure:
project/
├── main.py
└── utils/
└── helpers.py
To import helpers from main.py, you can:
# main.py
import utils.helpers # Requires 'utils' to be a package (with __init__.py)
# or
from utils import helpers
# or
from utils.helpers import greet # if you only need 'greet'
You need an __init__.py file inside utils/ to make it a package (can be empty). See Tutorial 2 for details.
sys.pathIf your module is elsewhere, you can temporarily add its directory to sys.path:
import sys
sys.path.append("/path/to/my/modules")
import my_module
However, this is considered a hack; it's better to structure your project properly or set the PYTHONPATH environment variable.
During development, you might change your module and want to test the changes without restarting the interpreter. You can force a reload using importlib.reload().
import importlib
import greetings
# ... make changes to greetings.py ...
importlib.reload(greetings) # Now updated!
Caution: Reloading can cause subtle bugs, especially if the module has state (like global variables) or if other modules already depend on it. Use it sparingly, mainly in interactive sessions.
from module import * with __all__When you write from greetings import *, Python imports all names that do not start with an underscore (by default). However, you can explicitly control which names are exported by defining a list __all__ at the top of your module.
# greetings.py
__all__ = ['greet', 'Salutation'] # Only these will be imported with *
GREETING_PREFIX = "Hello" # Not included in __all__, so not imported with *
def greet(name):
...
class Salutation:
...
This is a good practice to prevent accidental exposure of internal helper functions or constants.
Good documentation is essential. Use a module docstring (a multi-line string at the top of the file) to describe what the module does. Each function and class should also have docstrings.
"""
greetings.py – A simple module for generating greetings.
This module provides functions and classes to create personalized
greetings in different styles.
Constants:
GREETING_PREFIX (str): The default greeting prefix.
Functions:
greet(name): Return a friendly greeting.
Classes:
Salutation: Create greeting objects with customizable styles.
"""
You can view this documentation with help(greetings) in the interactive interpreter.
A circular import occurs when two modules import each other, directly or indirectly. For example:
module_a.py imports module_b.module_b.py imports module_a.This can cause ImportError or AttributeError because one module may be partially initialized when the other tries to use it.
They often arise from poor design where modules are too tightly coupled. For instance, if module_a defines a function that calls a function in module_b, and module_b calls a function in module_a, you have a circular dependency.
module_a needs something from module_b only at runtime, you can place the import statement inside that function, not at the top level.import instead of from: Sometimes, import module is safer because it does not immediately try to access attributes.Bad (circular):
# module_a.py
from module_b import func_b
def func_a():
return func_b() + 1
# module_b.py
from module_a import func_a
def func_b():
return func_a() - 1
Fix (import inside function):
# module_a.py
def func_a():
from module_b import func_b
return func_b() + 1
# module_b.py
def func_b():
from module_a import func_a
return func_a() - 1
This works because func_b is only imported when func_a is called, not at module load time.
Fix (restructure):
Move the common logic into a third module common.py and have both import from there.
if __name__ == "__main__": guard.__all__ to control the public API.if __name__ == "__main__": block for quick tests.__version__ = "1.0.0" for future reference.1. What is the value of __name__ when a module is imported?
"__main__""__module__""greetings")"__import__"2. Why do we use the if __name__ == "__main__": guard?
3. Which of the following is a valid name for a custom module file?
my-module.pymy_module.py2my_module.pyimport.py4. What is a circular import?
5. How can you control which names are exported when someone does from module import *?
__all__ list in the module.__export__ attribute.EXPORTED variable._ (double underscore).6. True or False: When you import a module, Python executes all top-level code in that module.
7. If you have a module in a subdirectory, what do you need to make it importable?
__init__.py file in that directory.__main__.py file..pth file.Goal: Create a module math_utils.py that contains:
PI = 3.14159.circle_area(radius), circle_circumference(radius), cube_volume(side).if __name__ == "__main__": guard to demonstrate the functions with sample values when run directly.Then, write a separate script test_math.py that imports your module and calculates the area and circumference of a circle with radius 5.
math_utils.py:
"""
Math utilities for geometry.
"""
PI = 3.14159
def circle_area(radius):
return PI * radius ** 2
def circle_circumference(radius):
return 2 * PI * radius
def cube_volume(side):
return side ** 3
if __name__ == "__main__":
print("Testing math_utils:")
print(f"Circle area (r=5): {circle_area(5):.2f}")
print(f"Circle circumference (r=5): {circle_circumference(5):.2f}")
print(f"Cube volume (side=3): {cube_volume(3)}")
test_math.py:
import math_utils
r = 5
print(f"Area: {math_utils.circle_area(r):.2f}")
print(f"Circumference: {math_utils.circle_circumference(r):.2f}")
Goal: Create a module bank.py that defines:
BankAccount with attributes owner and balance.deposit(amount), withdraw(amount), get_balance().if __name__ == "__main__": block, create an account, perform some transactions, and print the balance.Then, import your module in another script, create an account, and transfer money between two accounts (you may need to add a transfer method or do it manually).
bank.py:
class BankAccount:
def __init__(self, owner, balance=0):
self.owner = owner
self.balance = balance
def deposit(self, amount):
if amount > 0:
self.balance += amount
print(f"Deposited {amount}. New balance: {self.balance}")
else:
print("Amount must be positive.")
def withdraw(self, amount):
if amount > self.balance:
print("Insufficient funds.")
else:
self.balance -= amount
print(f"Withdrew {amount}. New balance: {self.balance}")
def get_balance(self):
return self.balance
if __name__ == "__main__":
acc = BankAccount("Alice", 100)
acc.deposit(50)
acc.withdraw(30)
print(f"Final balance: {acc.get_balance()}")
__all__Goal: Modify your math_utils.py module to include an internal helper function _sanitize_radius(radius) that raises an error if the radius is negative. Export only circle_area, circle_circumference, and cube_volume (not the helper).
Write a script that imports using from math_utils import * and verify that you cannot access _sanitize_radius. Also try importing it explicitly: from math_utils import _sanitize_radius – it should work because __all__ does not prevent explicit imports.
Add to math_utils.py:
__all__ = ['circle_area', 'circle_circumference', 'cube_volume']
def _sanitize_radius(radius):
if radius < 0:
raise ValueError("Radius cannot be negative")
return radius
# Modify circle_area to use it
def circle_area(radius):
_sanitize_radius(radius)
return PI * radius ** 2
# similarly for circumference
Goal: Create two modules that cause a circular import and then fix it.
Create mod_a.py:
from mod_b import func_b
def func_a():
return "A calls " + func_b()
Create mod_b.py:
from mod_a import func_a
def func_b():
return "B calls " + func_a()
Try to import mod_a or mod_b – you will get an ImportError.
Fix the circular dependency by moving the import inside the function in at least one module.
Now import mod_a and call func_a() successfully.
Fixed mod_a.py:
def func_a():
from mod_b import func_b
return "A calls " + func_b()
Fixed mod_b.py:
def func_b():
from mod_a import func_a
return "B calls " + func_a()
Now in interpreter:
import mod_a
mod_a.func_a() # Works without error
| Problem | Likely Cause | Solution |
|---|---|---|
ModuleNotFoundError when importing your module. |
The module is not in the current directory or sys.path. |
Check the file name and location. Use absolute path or add to sys.path. |
| Your module's test code runs when you don't want it to. | You forgot the if __name__ == "__main__": guard. |
Wrap test code in that guard. |
ImportError: cannot import name 'something' from your module. |
You have a circular import or a typo. | Check spelling; avoid circular imports. |
| After modifying your module, changes are not reflected. | Python cached the module after the first import. | Restart the interpreter or use importlib.reload(module). |
| Your module has a name that conflicts with a built-in module. | You named your file random.py, math.py, etc. |
Rename your module to avoid shadowing built-ins. |
AttributeError when accessing a function you thought existed. |
You may have misspelled the function name or the module was not fully loaded due to circular import. | Use dir(module) to list available names. Check for circular imports. |
Answer the following questions in complete sentences. Each response should be 3–5 sentences unless otherwise specified.
1. Why is the if __name__ == "__main__": guard considered a best practice when writing modules?
2. What is a circular import, and why is it problematic? Give a common way to resolve it.
3. What is the purpose of the __all__ variable in a module?
4. Explain the difference between absolute and relative imports within a package. When would you use each?
Answer the following questions in 300–500 words each.
5. Discuss the importance of modular programming in Python. How do modules help with code reuse, organization, and collaboration? Include an example of how a well‑designed module can save time in a larger project.
Suggested outline:
string_utils module used across many projects.__all__.6. Describe the process of creating a module from scratch, including naming, writing functions, and importing. What are the common mistakes beginners make, and how can they be avoided?
Suggested outline:
if __name__ == "__main__": for testing.sys.path).These questions require additional research beyond the tutorial content.
7. Research the concept of "namespace packages" in Python. How do they differ from regular packages, and in what scenarios would you use them?
8. Investigate the use of __all__ in the Python standard library. Look at the source code of the os module or re module and see what they expose in __all__. How does this help users of the module?
.py file containing Python definitions and statements.__name__ attribute tells you if the module is being run directly ("__main__") or imported (module name).if __name__ == "__main__": guard prevents test/demo code from running on import, making your module both usable and testable.import statement at the end.__all__ to control what is exported with from module import *.__init__.py).Mastering module creation is a major step towards becoming a professional Python developer. You now have the skills to write clean, reusable, and organized code.
This concludes the Tutorial 6. Once you have completed the exercises and homework, you will be fully equipped to create, use, and maintain your own Python modules. You are now ready to move on to Tutorial 7, where we will combine your knowledge of modules and packages to create and use your own packages!