mirror of
https://github.com/blshaer/python-by-example.git
synced 2026-03-27 23:29:25 +01:00
444 lines
11 KiB
Python
444 lines
11 KiB
Python
"""
|
|
================================================================================
|
|
File: 02_custom_modules.py
|
|
Topic: Creating and Using Custom Modules
|
|
================================================================================
|
|
|
|
This file demonstrates how to create your own modules and packages in Python.
|
|
Custom modules help organize code, promote reusability, and maintain clean
|
|
project structures.
|
|
|
|
Key Concepts:
|
|
- Creating module files
|
|
- The __name__ variable
|
|
- __init__.py for packages
|
|
- Relative imports
|
|
- Module documentation
|
|
- Publishing modules
|
|
|
|
================================================================================
|
|
"""
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 1. What is a Module?
|
|
# -----------------------------------------------------------------------------
|
|
# A module is simply a .py file containing Python code
|
|
|
|
print("--- What is a Module? ---")
|
|
|
|
# Any Python file is a module!
|
|
# If you have: my_utils.py
|
|
# You can: import my_utils
|
|
|
|
# Example module content (imagine this is saved as 'my_math.py'):
|
|
"""
|
|
# my_math.py
|
|
|
|
def add(a, b):
|
|
return a + b
|
|
|
|
def subtract(a, b):
|
|
return a - b
|
|
|
|
PI = 3.14159
|
|
|
|
class Calculator:
|
|
def multiply(self, a, b):
|
|
return a * b
|
|
"""
|
|
|
|
print("A module is just a Python file that can be imported.")
|
|
print("It can contain functions, classes, and variables.")
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 2. The __name__ Variable
|
|
# -----------------------------------------------------------------------------
|
|
# __name__ tells you how the module is being used
|
|
|
|
print("\n--- The __name__ Variable ---")
|
|
|
|
# When a file is run directly: __name__ == "__main__"
|
|
# When a file is imported: __name__ == module_name
|
|
|
|
print(f"Current __name__: {__name__}")
|
|
|
|
# Common pattern for executable modules:
|
|
"""
|
|
# utils.py
|
|
|
|
def useful_function():
|
|
return "I'm useful!"
|
|
|
|
def main():
|
|
print("Running as main program")
|
|
print(useful_function())
|
|
|
|
# This block only runs when file is executed directly
|
|
if __name__ == "__main__":
|
|
main()
|
|
"""
|
|
|
|
# Demo of the pattern
|
|
def demo_function():
|
|
"""A function that would be in a module."""
|
|
return "Hello from demo!"
|
|
|
|
def main():
|
|
"""Main function that runs when executed directly."""
|
|
print("This module is being run directly!")
|
|
print(demo_function())
|
|
|
|
# This is the __name__ check pattern
|
|
if __name__ == "__main__":
|
|
# This runs only when this file is executed directly
|
|
# Not when it's imported
|
|
pass # In real code, you'd call main()
|
|
|
|
print("\nThe 'if __name__ == \"__main__\"' pattern prevents code from")
|
|
print("running when the module is imported.")
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 3. Module Structure Example
|
|
# -----------------------------------------------------------------------------
|
|
|
|
print("\n--- Module Structure ---")
|
|
|
|
# A well-structured module typically has:
|
|
module_template = '''
|
|
"""
|
|
Module: my_module
|
|
Description: What this module does
|
|
|
|
This module provides functionality for...
|
|
"""
|
|
|
|
# Imports at the top
|
|
import os
|
|
from typing import List
|
|
|
|
# Module-level constants
|
|
VERSION = "1.0.0"
|
|
DEFAULT_TIMEOUT = 30
|
|
|
|
# Private helpers (convention: prefix with _)
|
|
def _internal_helper():
|
|
"""Not meant to be used outside this module."""
|
|
pass
|
|
|
|
# Public functions
|
|
def public_function(arg1: str) -> str:
|
|
"""
|
|
This is a public function.
|
|
|
|
Args:
|
|
arg1: Description of argument
|
|
|
|
Returns:
|
|
Description of return value
|
|
"""
|
|
return f"Result: {arg1}"
|
|
|
|
# Classes
|
|
class MyClass:
|
|
"""A class in the module."""
|
|
pass
|
|
|
|
# Main entry point (if applicable)
|
|
def main():
|
|
"""Entry point when run as script."""
|
|
print(public_function("test"))
|
|
|
|
if __name__ == "__main__":
|
|
main()
|
|
'''
|
|
|
|
print("A module should have:")
|
|
print(" 1. Module docstring at top")
|
|
print(" 2. Imports")
|
|
print(" 3. Constants")
|
|
print(" 4. Private helpers (prefixed with _)")
|
|
print(" 5. Public functions/classes")
|
|
print(" 6. Main block if it's executable")
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 4. Creating a Package
|
|
# -----------------------------------------------------------------------------
|
|
|
|
print("\n--- Creating a Package ---")
|
|
|
|
# A package is a directory containing:
|
|
# - __init__.py (can be empty)
|
|
# - One or more module files
|
|
|
|
package_structure = """
|
|
my_package/
|
|
__init__.py
|
|
module1.py
|
|
module2.py
|
|
utils/
|
|
__init__.py
|
|
helpers.py
|
|
validators.py
|
|
"""
|
|
|
|
print("Package structure:")
|
|
print(package_structure)
|
|
|
|
# The __init__.py file makes a directory a package
|
|
# It can be empty or contain initialization code
|
|
|
|
init_example = '''
|
|
# my_package/__init__.py
|
|
|
|
# Package version
|
|
__version__ = "1.0.0"
|
|
|
|
# Control what gets imported with "from my_package import *"
|
|
__all__ = ['module1', 'module2', 'important_function']
|
|
|
|
# Import commonly used items for convenient access
|
|
from .module1 import important_function
|
|
from .module2 import AnotherClass
|
|
'''
|
|
|
|
print("__init__.py can:")
|
|
print(" - Define package version")
|
|
print(" - Control __all__ exports")
|
|
print(" - Import frequently used items for convenience")
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 5. Import Examples for Packages
|
|
# -----------------------------------------------------------------------------
|
|
|
|
print("\n--- Package Import Examples ---")
|
|
|
|
import_examples = """
|
|
# Given this package structure:
|
|
# myapp/
|
|
# __init__.py
|
|
# core.py
|
|
# utils/
|
|
# __init__.py
|
|
# helpers.py
|
|
|
|
# Import the package
|
|
import myapp
|
|
|
|
# Import a module from the package
|
|
from myapp import core
|
|
|
|
# Import a function from a module
|
|
from myapp.core import process_data
|
|
|
|
# Import from nested package
|
|
from myapp.utils import helpers
|
|
from myapp.utils.helpers import format_output
|
|
|
|
# Relative imports (inside the package)
|
|
# In myapp/core.py:
|
|
from . import utils # Same-level import
|
|
from .utils import helpers # Nested module
|
|
from .utils.helpers import format_output
|
|
from .. import other_package # Parent-level (if applicable)
|
|
"""
|
|
|
|
print("Package import patterns:")
|
|
print(" import package")
|
|
print(" from package import module")
|
|
print(" from package.module import function")
|
|
print(" from package.sub import submodule")
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 6. Simulating a Module
|
|
# -----------------------------------------------------------------------------
|
|
|
|
print("\n--- Simulated Module Example ---")
|
|
|
|
# Let's create module-like code inline to demonstrate
|
|
|
|
# === This would be in: calculator.py ===
|
|
class Calculator:
|
|
"""A simple calculator class."""
|
|
|
|
def __init__(self):
|
|
self.history = []
|
|
|
|
def add(self, a, b):
|
|
"""Add two numbers."""
|
|
result = a + b
|
|
self.history.append(f"{a} + {b} = {result}")
|
|
return result
|
|
|
|
def subtract(self, a, b):
|
|
"""Subtract b from a."""
|
|
result = a - b
|
|
self.history.append(f"{a} - {b} = {result}")
|
|
return result
|
|
|
|
def get_history(self):
|
|
"""Get calculation history."""
|
|
return self.history
|
|
|
|
# === Using the "module" ===
|
|
calc = Calculator()
|
|
print(f"5 + 3 = {calc.add(5, 3)}")
|
|
print(f"10 - 4 = {calc.subtract(10, 4)}")
|
|
print(f"History: {calc.get_history()}")
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 7. Module Documentation
|
|
# -----------------------------------------------------------------------------
|
|
|
|
print("\n--- Module Documentation ---")
|
|
|
|
# Good module documentation includes:
|
|
# - Module-level docstring
|
|
# - Function/class docstrings
|
|
# - Type hints
|
|
# - Examples
|
|
|
|
documented_module = '''
|
|
"""
|
|
mymodule - A demonstration of proper documentation
|
|
|
|
This module provides utilities for string manipulation
|
|
and validation. It follows Google-style docstrings.
|
|
|
|
Example:
|
|
>>> from mymodule import validate_email
|
|
>>> validate_email("test@example.com")
|
|
True
|
|
|
|
Attributes:
|
|
EMAIL_REGEX (str): Regular expression for email validation
|
|
"""
|
|
|
|
import re
|
|
from typing import Optional
|
|
|
|
EMAIL_REGEX = r'^[\w.-]+@[\w.-]+\.\w+$'
|
|
|
|
def validate_email(email: str) -> bool:
|
|
"""
|
|
Validate an email address format.
|
|
|
|
Args:
|
|
email: The email address to validate
|
|
|
|
Returns:
|
|
True if valid format, False otherwise
|
|
|
|
Raises:
|
|
TypeError: If email is not a string
|
|
|
|
Example:
|
|
>>> validate_email("user@domain.com")
|
|
True
|
|
>>> validate_email("invalid-email")
|
|
False
|
|
"""
|
|
if not isinstance(email, str):
|
|
raise TypeError("email must be a string")
|
|
return bool(re.match(EMAIL_REGEX, email))
|
|
'''
|
|
|
|
print("Include in your modules:")
|
|
print(" - Module docstring explaining purpose")
|
|
print(" - Type hints for parameters and returns")
|
|
print(" - Examples in docstrings")
|
|
print(" - Proper exception documentation")
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 8. The __all__ Variable
|
|
# -----------------------------------------------------------------------------
|
|
|
|
print("\n--- The __all__ Variable ---")
|
|
|
|
# __all__ controls what's exported with "from module import *"
|
|
|
|
all_example = '''
|
|
# mymodule.py
|
|
|
|
# Only these will be exported with "from mymodule import *"
|
|
__all__ = ['public_func', 'PublicClass', 'CONSTANT']
|
|
|
|
CONSTANT = 42
|
|
|
|
def public_func():
|
|
"""This is meant to be used externally."""
|
|
pass
|
|
|
|
def _private_func():
|
|
"""This is internal (won't be exported)."""
|
|
pass
|
|
|
|
class PublicClass:
|
|
"""This class is for external use."""
|
|
pass
|
|
'''
|
|
|
|
print("__all__ defines what gets exported with 'import *'")
|
|
print("Items NOT in __all__ won't be exported")
|
|
print("Underscore-prefixed items are convention for private")
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 9. Reloading Modules
|
|
# -----------------------------------------------------------------------------
|
|
|
|
print("\n--- Reloading Modules ---")
|
|
|
|
# During development, you might need to reload a modified module
|
|
from importlib import reload
|
|
|
|
# If you modify 'mymodule', you can reload it:
|
|
# import mymodule
|
|
# # ... modify the file ...
|
|
# reload(mymodule) # Get the updated version
|
|
|
|
print("Use importlib.reload() to reload a modified module")
|
|
print("Useful during development and debugging")
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 10. Project Structure Best Practices
|
|
# -----------------------------------------------------------------------------
|
|
|
|
print("\n--- Project Structure Best Practices ---")
|
|
|
|
project_structure = """
|
|
myproject/
|
|
README.md
|
|
setup.py or pyproject.toml
|
|
requirements.txt
|
|
.gitignore
|
|
|
|
src/
|
|
mypackage/
|
|
__init__.py
|
|
core.py
|
|
utils.py
|
|
models/
|
|
__init__.py
|
|
user.py
|
|
product.py
|
|
|
|
tests/
|
|
__init__.py
|
|
test_core.py
|
|
test_utils.py
|
|
|
|
docs/
|
|
index.md
|
|
|
|
scripts/
|
|
run_server.py
|
|
"""
|
|
|
|
print("Recommended project structure:")
|
|
print(project_structure)
|
|
|
|
print("Key points:")
|
|
print(" - Separate source code (src/) from tests")
|
|
print(" - Keep configuration at project root")
|
|
print(" - Use __init__.py for all packages")
|
|
print(" - Tests mirror source structure")
|