python

exercises

exercises.py🐍
"""
06 - Modules & Packages: Exercises
Complete these exercises to practice working with modules and packages!
"""

import sys
import os
from typing import Any
from pathlib import Path


# =============================================================================
# EXERCISE 1: Module Information
# =============================================================================
def get_module_path(module_name: str) -> str | None:
    """
    Get the file path of an imported module.
    
    Args:
        module_name: Name of module to look up (must be already imported)
        
    Returns:
        File path of the module, or None if not found/built-in
    """
    # YOUR CODE HERE
    if module_name not in sys.modules:
        return None
    
    module = sys.modules[module_name]
    
    if hasattr(module, '__file__') and module.__file__:
        return module.__file__
    
    return None


def list_module_functions(module_name: str) -> list[str]:
    """
    List all public functions (not starting with _) in a module.
    
    Args:
        module_name: Name of module (must be already imported)
        
    Returns:
        List of public function names
    """
    # YOUR CODE HERE
    if module_name not in sys.modules:
        return []
    
    module = sys.modules[module_name]
    functions = []
    
    for name in dir(module):
        if not name.startswith('_'):
            attr = getattr(module, name)
            if callable(attr):
                functions.append(name)
    
    return functions


# =============================================================================
# EXERCISE 2: Dynamic Import
# =============================================================================
def dynamic_import(module_name: str) -> Any:
    """
    Dynamically import a module by name.
    
    Args:
        module_name: Full module name (e.g., 'os.path')
        
    Returns:
        The imported module
        
    Raises:
        ImportError: If module cannot be imported
    """
    # YOUR CODE HERE
    import importlib
    return importlib.import_module(module_name)


def safe_import(module_name: str, fallback: Any = None) -> Any:
    """
    Try to import a module, return fallback if not available.
    
    Args:
        module_name: Module to try importing
        fallback: Value to return if import fails
        
    Returns:
        Imported module or fallback value
    """
    # YOUR CODE HERE
    try:
        import importlib
        return importlib.import_module(module_name)
    except ImportError:
        return fallback


def import_from_path(file_path: str, module_name: str) -> Any:
    """
    Import a module from a specific file path.
    
    Args:
        file_path: Path to the .py file
        module_name: Name to give the module
        
    Returns:
        The imported module
    """
    # YOUR CODE HERE
    import importlib.util
    
    spec = importlib.util.spec_from_file_location(module_name, file_path)
    if spec is None or spec.loader is None:
        raise ImportError(f"Cannot load module from {file_path}")
    
    module = importlib.util.module_from_spec(spec)
    sys.modules[module_name] = module
    spec.loader.exec_module(module)
    
    return module


# =============================================================================
# EXERCISE 3: Package Structure Creator
# =============================================================================
def create_package_structure(base_path: str, package_name: str, 
                            modules: list[str]) -> dict[str, str]:
    """
    Create a package directory structure with __init__.py and module files.
    
    Args:
        base_path: Directory to create package in
        package_name: Name of the package
        modules: List of module names (without .py)
        
    Returns:
        Dictionary mapping file paths to their contents
    """
    # YOUR CODE HERE
    files = {}
    package_dir = os.path.join(base_path, package_name)
    
    # Create __init__.py content
    init_content = f'"""\n{package_name} package\n"""\n\n'
    init_content += f"__all__ = {modules!r}\n\n"
    for mod in modules:
        init_content += f"from .{mod} import *\n"
    
    files[os.path.join(package_dir, '__init__.py')] = init_content
    
    # Create module files
    for mod in modules:
        mod_content = f'"""\n{package_name}.{mod} module\n"""\n\n'
        mod_content += f"def {mod}_function():\n"
        mod_content += f"    '''Function from {mod} module.'''\n"
        mod_content += "    pass\n"
        files[os.path.join(package_dir, f'{mod}.py')] = mod_content
    
    return files


# =============================================================================
# EXERCISE 4: Dependency Checker
# =============================================================================
def check_dependencies(required_modules: list[str]) -> dict[str, bool]:
    """
    Check if required modules are available.
    
    Args:
        required_modules: List of module names to check
        
    Returns:
        Dictionary mapping module name to availability (True/False)
    """
    # YOUR CODE HERE
    import importlib.util
    
    results = {}
    for module_name in required_modules:
        spec = importlib.util.find_spec(module_name)
        results[module_name] = spec is not None
    
    return results


def get_missing_dependencies(required_modules: list[str]) -> list[str]:
    """
    Get list of modules that are not installed.
    
    Args:
        required_modules: List of module names to check
        
    Returns:
        List of missing module names
    """
    # YOUR CODE HERE
    availability = check_dependencies(required_modules)
    return [mod for mod, available in availability.items() if not available]


def generate_requirements(modules: list[str]) -> str:
    """
    Generate a requirements.txt format string for modules.
    Try to get version if module is installed.
    
    Args:
        modules: List of module names
        
    Returns:
        Multi-line string in requirements.txt format
    """
    # YOUR CODE HERE
    lines = []
    
    for module_name in modules:
        try:
            module = dynamic_import(module_name)
            version = getattr(module, '__version__', None)
            if version:
                lines.append(f"{module_name}=={version}")
            else:
                lines.append(module_name)
        except ImportError:
            lines.append(module_name)
    
    return '\n'.join(sorted(lines))


# =============================================================================
# EXERCISE 5: Plugin System
# =============================================================================
class PluginManager:
    """
    Simple plugin system that loads modules from a directory.
    """
    
    def __init__(self, plugin_dir: str):
        """Initialize with plugin directory path."""
        self.plugin_dir = plugin_dir
        self.plugins: dict[str, Any] = {}
    
    def discover_plugins(self) -> list[str]:
        """
        Discover all Python files in the plugin directory.
        
        Returns:
            List of plugin names (without .py extension)
        """
        # YOUR CODE HERE
        plugins = []
        plugin_path = Path(self.plugin_dir)
        
        if not plugin_path.exists():
            return plugins
        
        for py_file in plugin_path.glob('*.py'):
            if not py_file.name.startswith('_'):
                plugins.append(py_file.stem)
        
        return plugins
    
    def load_plugin(self, plugin_name: str) -> bool:
        """
        Load a specific plugin.
        
        Args:
            plugin_name: Name of the plugin to load
            
        Returns:
            True if loaded successfully, False otherwise
        """
        # YOUR CODE HERE
        plugin_path = os.path.join(self.plugin_dir, f'{plugin_name}.py')
        
        if not os.path.exists(plugin_path):
            return False
        
        try:
            module = import_from_path(plugin_path, f'plugin_{plugin_name}')
            self.plugins[plugin_name] = module
            return True
        except Exception:
            return False
    
    def load_all_plugins(self) -> dict[str, bool]:
        """
        Load all discovered plugins.
        
        Returns:
            Dictionary mapping plugin name to success status
        """
        # YOUR CODE HERE
        results = {}
        for plugin_name in self.discover_plugins():
            results[plugin_name] = self.load_plugin(plugin_name)
        return results
    
    def get_plugin(self, plugin_name: str) -> Any | None:
        """
        Get a loaded plugin module.
        
        Args:
            plugin_name: Name of the plugin
            
        Returns:
            Plugin module or None if not loaded
        """
        return self.plugins.get(plugin_name)
    
    def run_plugin_function(self, plugin_name: str, function_name: str, 
                           *args: Any, **kwargs: Any) -> Any:
        """
        Run a function from a plugin.
        
        Args:
            plugin_name: Name of the plugin
            function_name: Name of the function to call
            *args, **kwargs: Arguments to pass
            
        Returns:
            Result of the function call
            
        Raises:
            KeyError: If plugin not loaded
            AttributeError: If function not found
        """
        # YOUR CODE HERE
        if plugin_name not in self.plugins:
            raise KeyError(f"Plugin '{plugin_name}' not loaded")
        
        plugin = self.plugins[plugin_name]
        
        if not hasattr(plugin, function_name):
            raise AttributeError(f"Plugin '{plugin_name}' has no function '{function_name}'")
        
        func = getattr(plugin, function_name)
        return func(*args, **kwargs)


# =============================================================================
# EXERCISE 6: Module Analyzer
# =============================================================================
def analyze_module(module: Any) -> dict[str, Any]:
    """
    Analyze a module and return information about its contents.
    
    Args:
        module: An imported module object
        
    Returns:
        Dictionary with keys:
        - 'name': Module name
        - 'file': Module file path (or None)
        - 'doc': Module docstring
        - 'functions': List of function names
        - 'classes': List of class names
        - 'variables': List of public variable names (excluding functions/classes)
    """
    # YOUR CODE HERE
    import inspect
    
    result = {
        'name': getattr(module, '__name__', 'unknown'),
        'file': getattr(module, '__file__', None),
        'doc': getattr(module, '__doc__', None),
        'functions': [],
        'classes': [],
        'variables': []
    }
    
    for name in dir(module):
        if name.startswith('_'):
            continue
        
        attr = getattr(module, name)
        
        if inspect.isfunction(attr):
            result['functions'].append(name)
        elif inspect.isclass(attr):
            result['classes'].append(name)
        elif not callable(attr):
            result['variables'].append(name)
    
    return result


def compare_modules(module1: Any, module2: Any) -> dict[str, Any]:
    """
    Compare two modules and find differences.
    
    Args:
        module1: First module
        module2: Second module
        
    Returns:
        Dictionary with:
        - 'common': Names in both
        - 'only_in_first': Names only in module1
        - 'only_in_second': Names only in module2
    """
    # YOUR CODE HERE
    names1 = set(n for n in dir(module1) if not n.startswith('_'))
    names2 = set(n for n in dir(module2) if not n.startswith('_'))
    
    return {
        'common': sorted(names1 & names2),
        'only_in_first': sorted(names1 - names2),
        'only_in_second': sorted(names2 - names1)
    }


# =============================================================================
# EXERCISE 7: Lazy Importer
# =============================================================================
class LazyImporter:
    """
    Import modules lazily (only when first accessed).
    Useful for optional dependencies or speeding up startup.
    """
    
    def __init__(self, module_name: str):
        """Initialize with module name (don't import yet)."""
        self._module_name = module_name
        self._module: Any = None
    
    def _load(self) -> Any:
        """Load the module if not already loaded."""
        # YOUR CODE HERE
        if self._module is None:
            self._module = dynamic_import(self._module_name)
        return self._module
    
    def __getattr__(self, name: str) -> Any:
        """
        Get attribute from the module, loading it first if needed.
        """
        # YOUR CODE HERE
        module = self._load()
        return getattr(module, name)
    
    @property
    def is_loaded(self) -> bool:
        """Check if module has been loaded."""
        return self._module is not None


# =============================================================================
# EXERCISE 8: Import Hooks
# =============================================================================
def with_import_hook(transform_func):
    """
    Decorator that transforms module content before use.
    This is a simplified version of Python's import hooks.
    
    Args:
        transform_func: Function to transform imported object names
        
    Returns:
        Decorator
    """
    def decorator(func):
        def wrapper(module_name: str):
            module = dynamic_import(module_name)
            # Create a namespace with transformed names
            transformed = {}
            for name in dir(module):
                if not name.startswith('_'):
                    new_name = transform_func(name)
                    transformed[new_name] = getattr(module, name)
            return func(transformed)
        return wrapper
    return decorator


# =============================================================================
# TEST YOUR SOLUTIONS
# =============================================================================
if __name__ == "__main__":
    import tempfile
    import shutil
    
    print("Testing modules & packages exercises...")
    print("=" * 60)
    
    # Test 1: Module information
    import math
    path = get_module_path('math')
    # math might be a built-in, so path could be None
    print(f"✓ get_module_path (math path: {path})")
    
    import os
    funcs = list_module_functions('os')
    assert 'getcwd' in funcs or 'path' in funcs  # Should have some functions
    print("✓ list_module_functions")
    
    # Test 2: Dynamic import
    os_module = dynamic_import('os')
    assert hasattr(os_module, 'getcwd')
    print("✓ dynamic_import")
    
    result = safe_import('nonexistent_module_xyz', "fallback")
    assert result == "fallback"
    print("✓ safe_import")
    
    # Test 3: Package structure
    structure = create_package_structure('/tmp', 'mypackage', ['utils', 'core'])
    assert '/tmp/mypackage/__init__.py' in structure
    assert '/tmp/mypackage/utils.py' in structure
    print("✓ create_package_structure")
    
    # Test 4: Dependency checker
    deps = check_dependencies(['os', 'sys', 'nonexistent_xyz'])
    assert deps['os'] is True
    assert deps['nonexistent_xyz'] is False
    print("✓ check_dependencies")
    
    missing = get_missing_dependencies(['os', 'nonexistent_xyz'])
    assert 'nonexistent_xyz' in missing
    assert 'os' not in missing
    print("✓ get_missing_dependencies")
    
    # Test 5: Plugin system
    temp_dir = tempfile.mkdtemp()
    try:
        # Create a test plugin
        plugin_content = '''
def greet(name):
    return f"Hello, {name}!"

def add(a, b):
    return a + b
'''
        with open(os.path.join(temp_dir, 'greeting.py'), 'w') as f:
            f.write(plugin_content)
        
        manager = PluginManager(temp_dir)
        plugins = manager.discover_plugins()
        assert 'greeting' in plugins
        print("✓ PluginManager.discover_plugins")
        
        loaded = manager.load_plugin('greeting')
        assert loaded is True
        print("✓ PluginManager.load_plugin")
        
        result = manager.run_plugin_function('greeting', 'greet', 'World')
        assert result == "Hello, World!"
        print("✓ PluginManager.run_plugin_function")
    finally:
        shutil.rmtree(temp_dir)
    
    # Test 6: Module analyzer
    import collections
    info = analyze_module(collections)
    assert info['name'] == 'collections'
    assert 'Counter' in info['classes']
    print("✓ analyze_module")
    
    import json
    import pickle
    comparison = compare_modules(json, pickle)
    assert 'dump' in comparison['common']
    print("✓ compare_modules")
    
    # Test 7: Lazy importer
    lazy_json = LazyImporter('json')
    assert lazy_json.is_loaded is False
    
    # Access an attribute, triggering load
    dumps = lazy_json.dumps
    assert lazy_json.is_loaded is True
    assert callable(dumps)
    print("✓ LazyImporter")
    
    # Test 8: Import hook
    @with_import_hook(lambda name: name.upper())
    def use_module(namespace):
        return 'PI' in namespace
    
    has_pi = use_module('math')
    assert has_pi is True
    print("✓ with_import_hook")
    
    print("\n" + "=" * 60)
    print("All tests passed! ✓")
Exercises - Python Tutorial | DeepML