Previous | Tutorial index | Next

Tutorial 6: Creating and Using Your Own Modules

Learning Objectives

Write and use your own modules.

1. Introduction: From Consumer to Creator

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.

2. Creating a Module – The Basics

What is a Custom Module?

A custom module is simply a .py file that you create. It can contain:

Naming Your Module

Follow the same naming rules as any Python identifier:

Step 1: Write Your Module

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}!"

Step 2: Import and Use the Module

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!

Step 3: What Happens on Import?

When you import greetings, Python:

  1. Finds greetings.py (in the current directory or sys.path).
  2. Compiles it to bytecode (if needed).
  3. Executes all the top-level statements in greetings.py from top to bottom.
  4. Creates a module object named 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.

3. The __name__ Attribute – A Hidden Gem

Every 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")

Why Does This Matter?

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.

4. The if __name__ == "__main__": Guard

The most common use of __name__ is to protect code that should only run when the module is executed directly, not when imported.

Example

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:

Best Practices for the Guard

5. Importing Your Module from Different Locations

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.

If Your Module is in a Subdirectory

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.

Adding a Custom Directory to sys.path

If 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.

6. Module Reloading (Advanced)

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.

7. Controlling 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.

8. Documenting Your Module

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.

9. Avoiding Circular Imports

A circular import occurs when two modules import each other, directly or indirectly. For example:

This can cause ImportError or AttributeError because one module may be partially initialized when the other tries to use it.

Why Circular Imports Happen

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.

How to Avoid Circular Imports

  1. Restructure your code: Move the shared functionality to a third module that both can import.
  2. Import inside a function: If a function in module_a needs something from module_b only at runtime, you can place the import statement inside that function, not at the top level.
  3. Import at the end: You can place the import at the end of the module, after all definitions, so that the module is fully built before the import is executed.
  4. Use import instead of from: Sometimes, import module is safer because it does not immediately try to access attributes.

Example of Fixing a Circular Import

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.

10. Best Practices for Writing Modules

  1. Keep it focused: A module should have a single, clear responsibility (e.g., a module for string utilities, another for file operations).
  2. Avoid side effects at import: Do not open files, connect to databases, or print messages at the top level. Put such code inside functions or in the if __name__ == "__main__": guard.
  3. Use descriptive names: The module name should indicate its purpose.
  4. Write docstrings: Document the module, its functions, and classes.
  5. Export only what is needed: Use __all__ to control the public API.
  6. Handle errors gracefully: Use exceptions and proper error messages.
  7. Use relative imports only within packages (with caution), but prefer absolute imports for clarity.
  8. Test your module: Write a separate test script or use the if __name__ == "__main__": block for quick tests.
  9. Version your module: Include a constant like __version__ = "1.0.0" for future reference.
  10. Keep backward compatibility when making changes.

11. Quiz: Check Your Understanding

1. What is the value of __name__ when a module is imported?

Answer(C) The module's name (e.g., `"greetings"`).

2. Why do we use the if __name__ == "__main__": guard?

Answer(B) To allow test/demo code to run only when the module is executed directly.

3. Which of the following is a valid name for a custom module file?

Answer(B) `my_module.py` (no hyphens, doesn't start with a digit, not a keyword).

4. What is a circular import?

Answer(B) When two modules attempt to import each other.

5. How can you control which names are exported when someone does from module import *?

Answer(A) By defining a `__all__` list.

6. True or False: When you import a module, Python executes all top-level code in that module.

AnswerTrue. Python executes all top-level code when importing.

7. If you have a module in a subdirectory, what do you need to make it importable?

Answer(A) An `__init__.py` file in that directory (to make it a package).

6.12 Exercises

Exercise 1: A Simple Math Module

Goal: Create a module math_utils.py that contains:

Then, write a separate script test_math.py that imports your module and calculates the area and circumference of a circle with radius 5.

Sample Solution

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}")

Exercise 2: Module with a Class

Goal: Create a module bank.py that defines:

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).

Sample Solution

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()}")

Exercise 3: Using __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.

Sample Solution

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

Exercise 4: Circular Import Demonstration and Fix

Goal: Create two modules that cause a circular import and then fix it.

  1. Create mod_a.py:

    from mod_b import func_b def func_a(): return "A calls " + func_b()
  2. Create mod_b.py:

    from mod_a import func_a def func_b(): return "B calls " + func_a()
  3. Try to import mod_a or mod_b – you will get an ImportError.

  4. Fix the circular dependency by moving the import inside the function in at least one module.

  5. Now import mod_a and call func_a() successfully.

Sample Solution

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

6.13 Common Pitfalls and Troubleshooting

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.

6.14 Homework Questions

Answer the following questions in complete sentences. Each response should be 3–5 sentences unless otherwise specified.

Short Answer Questions

1. Why is the if __name__ == "__main__": guard considered a best practice when writing modules?

Sample AnswerIt prevents code inside the block from running when the module is imported, keeping the module clean and reusable. This allows the module to be used as a library without side effects like printing or performing tests. It also enables the module to be run directly as a script for testing or demonstration purposes.

2. What is a circular import, and why is it problematic? Give a common way to resolve it.

Sample AnswerA circular import occurs when two modules import each other (directly or indirectly). It can cause `ImportError` because one module might be partially initialized when the other tries to use it. One common fix is to move the import statement inside a function, so that the import happens only when the function is called, not at module load time.

3. What is the purpose of the __all__ variable in a module?

Sample Answer`__all__` is a list of strings that defines which names will be imported when someone uses `from module import *`. It helps control the public API of the module, preventing internal helper functions or variables from being accidentally exposed.

4. Explain the difference between absolute and relative imports within a package. When would you use each?

Sample AnswerAbsolute imports specify the full path from the top-level package, e.g., `from package.sub import module`. Relative imports use dots to refer to the current or parent package, e.g., `from . import module`. Absolute imports are more explicit and portable; relative imports are convenient but can only be used inside a package and break when the script is run directly.

Essay Questions

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:

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:

Research Questions

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?

Sample AnswerNamespace packages are a way to split a single package across multiple directories, without the need for an `__init__.py` in each. They are useful for distributing a package across multiple projects or for plugin architectures, where different parts of a package can be installed separately.

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?

Sample AnswerMany standard library modules define `__all__` to list the public functions and classes. This tells users which parts are intended for external use, reducing confusion and helping with backward compatibility. For example, the `re` module exposes only the main functions like `search`, `match`, etc., while hiding internal helpers.

15. Summary of Tutorial 6

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!

Previous | Tutorial index | Next