python
exercises
exercises.py🐍python
"""
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! ✓")