Python Intermediate

Python Modules and Packages: Organizing and Reusing Code

CodingerWeb
CodingerWeb
21 views 50 min read

Understanding Python Modules and Packages

Modules and packages help organize code into reusable components, making your programs more maintainable and scalable.

What are Modules?

A module is a file containing Python code that can define functions, classes, and variables. It can also include runnable code.


# Create a file called math_utils.py
# math_utils.py

"""
Mathematical utility functions
"""

import math

PI = 3.14159

def circle_area(radius):
    """Calculate the area of a circle"""
    return PI * radius ** 2

def circle_circumference(radius):
    """Calculate the circumference of a circle"""
    return 2 * PI * radius

def factorial(n):
    """Calculate factorial of n"""
    if n < 0:
        raise ValueError("Factorial is not defined for negative numbers")
    if n == 0 or n == 1:
        return 1
    return n * factorial(n - 1)

def is_prime(n):
    """Check if a number is prime"""
    if n < 2:
        return False
    for i in range(2, int(math.sqrt(n)) + 1):
        if n % i == 0:
            return False
    return True

def fibonacci(n):
    """Generate first n fibonacci numbers"""
    if n <= 0:
        return []
    elif n == 1:
        return [0]
    elif n == 2:
        return [0, 1]
    
    fib = [0, 1]
    for i in range(2, n):
        fib.append(fib[i-1] + fib[i-2])
    return fib

# Code that runs when module is executed directly
if __name__ == "__main__":
    print("Testing math_utils module:")
    print(f"Circle area (radius=5): {circle_area(5)}")
    print(f"Factorial of 5: {factorial(5)}")
    print(f"Is 17 prime? {is_prime(17)}")
    print(f"First 10 Fibonacci numbers: {fibonacci(10)}")

Importing Modules


# Different ways to import modules

# 1. Import entire module
import math_utils

print(math_utils.circle_area(5))
print(math_utils.PI)

# 2. Import specific functions
from math_utils import circle_area, factorial

print(circle_area(3))
print(factorial(4))

# 3. Import with alias
import math_utils as mu

print(mu.is_prime(17))

# 4. Import specific functions with alias
from math_utils import fibonacci as fib

print(fib(8))

# 5. Import all (not recommended for large modules)
from math_utils import *

print(circle_circumference(4))

# 6. Import built-in modules
import os
import sys
import datetime
import random

print(f"Current directory: {os.getcwd()}")
print(f"Python version: {sys.version}")
print(f"Current time: {datetime.datetime.now()}")
print(f"Random number: {random.randint(1, 100)}")

Module Search Path


import sys

# Python searches for modules in these locations (in order):
print("Module search path:")
for i, path in enumerate(sys.path, 1):
    print(f"{i}. {path}")

# Add custom path to module search
sys.path.append("/path/to/your/modules")

# Check if a module can be imported
def can_import(module_name):
    try:
        __import__(module_name)
        return True
    except ImportError:
        return False

print(f"Can import 'requests': {can_import('requests')}")
print(f"Can import 'math_utils': {can_import('math_utils')}")

# Get module information
import math_utils
print(f"Module name: {math_utils.__name__}")
print(f"Module file: {math_utils.__file__}")
print(f"Module doc: {math_utils.__doc__}")

Creating Packages

A package is a directory containing multiple modules. It must have an __init__.py file.


# Package structure:
# my_package/
#     __init__.py
#     math_operations.py
#     string_operations.py
#     data_structures.py

# my_package/__init__.py
"""
My custom package for various utilities
"""

__version__ = "1.0.0"
__author__ = "Your Name"

# Import key functions to package level
from .math_operations import add, multiply
from .string_operations import reverse_string, count_words

# Package-level function
def get_package_info():
    return {
        "name": __name__,
        "version": __version__,
        "author": __author__
    }

# my_package/math_operations.py
"""
Mathematical operations module
"""

def add(a, b):
    """Add two numbers"""
    return a + b

def subtract(a, b):
    """Subtract two numbers"""
    return a - b

def multiply(a, b):
    """Multiply two numbers"""
    return a * b

def divide(a, b):
    """Divide two numbers"""
    if b == 0:
        raise ValueError("Cannot divide by zero")
    return a / b

def power(base, exponent):
    """Calculate base raised to exponent"""
    return base ** exponent

# my_package/string_operations.py
"""
String manipulation operations
"""

def reverse_string(s):
    """Reverse a string"""
    return s[::-1]

def count_words(text):
    """Count words in text"""
    return len(text.split())

def capitalize_words(text):
    """Capitalize each word"""
    return " ".join(word.capitalize() for word in text.split())

def remove_duplicates(text):
    """Remove duplicate characters"""
    return "".join(dict.fromkeys(text))

# my_package/data_structures.py
"""
Custom data structure implementations
"""

class Stack:
    """Simple stack implementation"""
    
    def __init__(self):
        self.items = []
    
    def push(self, item):
        """Add item to top of stack"""
        self.items.append(item)
    
    def pop(self, item):
        """Remove and return top item"""
        if self.is_empty():
            raise IndexError("Stack is empty")
        return self.items.pop()
    
    def peek(self):
        """Return top item without removing"""
        if self.is_empty():
            raise IndexError("Stack is empty")
        return self.items[-1]
    
    def is_empty(self):
        """Check if stack is empty"""
        return len(self.items) == 0
    
    def size(self):
        """Return stack size"""
        return len(self.items)

class Queue:
    """Simple queue implementation"""
    
    def __init__(self):
        self.items = []
    
    def enqueue(self, item):
        """Add item to rear of queue"""
        self.items.append(item)
    
    def dequeue(self):
        """Remove and return front item"""
        if self.is_empty():
            raise IndexError("Queue is empty")
        return self.items.pop(0)
    
    def front(self):
        """Return front item without removing"""
        if self.is_empty():
            raise IndexError("Queue is empty")
        return self.items[0]
    
    def is_empty(self):
        """Check if queue is empty"""
        return len(self.items) == 0
    
    def size(self):
        """Return queue size"""
        return len(self.items)

Using Packages


# Import from package
import my_package

# Use package-level imports
result = my_package.add(5, 3)
print(f"5 + 3 = {result}")

reversed_text = my_package.reverse_string("Hello")
print(f"Reversed: {reversed_text}")

# Get package info
info = my_package.get_package_info()
print(f"Package info: {info}")

# Import specific modules
from my_package import math_operations, string_operations, data_structures

# Use module functions
print(f"10 * 4 = {math_operations.multiply(10, 4)}")
print(f"Word count: {string_operations.count_words('Hello world Python')}")

# Use custom data structures
stack = data_structures.Stack()
stack.push(1)
stack.push(2)
stack.push(3)
print(f"Stack size: {stack.size()}")
print(f"Top item: {stack.peek()}")

queue = data_structures.Queue()
queue.enqueue("first")
queue.enqueue("second")
print(f"Queue front: {queue.front()}")

# Import with different syntax
from my_package.math_operations import power
from my_package.string_operations import capitalize_words

print(f"2^8 = {power(2, 8)}")
print(f"Capitalized: {capitalize_words('hello world')}")

Standard Library Modules


# Commonly used standard library modules

# os - Operating system interface
import os
print(f"Current directory: {os.getcwd()}")
print(f"Files in current directory: {os.listdir('.')}")

# sys - System-specific parameters
import sys
print(f"Python executable: {sys.executable}")
print(f"Command line arguments: {sys.argv}")

# datetime - Date and time handling
from datetime import datetime, timedelta, date
now = datetime.now()
print(f"Current time: {now}")
print(f"Tomorrow: {now + timedelta(days=1)}")

# random - Generate random numbers
import random
print(f"Random integer: {random.randint(1, 100)}")
print(f"Random choice: {random.choice(['apple', 'banana', 'orange'])}")

# json - JSON encoder and decoder
import json
data = {"name": "Alice", "age": 30}
json_string = json.dumps(data)
print(f"JSON string: {json_string}")
parsed_data = json.loads(json_string)
print(f"Parsed data: {parsed_data}")

# re - Regular expressions
import re
text = "The phone number is 123-456-7890"
pattern = r'd{3}-d{3}-d{4}'
match = re.search(pattern, text)
if match:
    print(f"Found phone number: {match.group()}")

# collections - Specialized container datatypes
from collections import Counter, defaultdict, namedtuple
words = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple']
word_count = Counter(words)
print(f"Word count: {word_count}")

# Create named tuple
Person = namedtuple('Person', ['name', 'age', 'city'])
person = Person('Alice', 30, 'New York')
print(f"Person: {person.name}, {person.age}, {person.city}")

# itertools - Functions for creating iterators
import itertools
numbers = [1, 2, 3]
permutations = list(itertools.permutations(numbers))
print(f"Permutations: {permutations}")

combinations = list(itertools.combinations(numbers, 2))
print(f"Combinations: {combinations}")

Third-Party Packages


# Installing third-party packages with pip
# pip install requests numpy pandas matplotlib

# Example usage (if packages are installed)

# requests - HTTP library
try:
    import requests
    response = requests.get('https://api.github.com/users/octocat')
    if response.status_code == 200:
        data = response.json()
        print(f"GitHub user: {data['name']}")
except ImportError:
    print("requests library not installed")

# numpy - Numerical computing
try:
    import numpy as np
    arr = np.array([1, 2, 3, 4, 5])
    print(f"NumPy array: {arr}")
    print(f"Array mean: {np.mean(arr)}")
except ImportError:
    print("numpy library not installed")

# Check installed packages
import pkg_resources
installed_packages = [d.project_name for d in pkg_resources.working_set]
print(f"Installed packages: {sorted(installed_packages)[:10]}...")  # Show first 10

Module Documentation and Help


# Getting help and documentation

# Built-in help function
help(len)  # Help for built-in function
help(str.split)  # Help for string method

# Module documentation
import math
help(math)  # Full module documentation
help(math.sqrt)  # Specific function help

# Using dir() to explore modules
print("Math module contents:")
print(dir(math))

# Module attributes
print(f"Math module name: {math.__name__}")
print(f"Math module doc: {math.__doc__[:100]}...")

# Inspect module
import inspect

def example_function(x, y=10):
    """Example function with parameters"""
    return x + y

# Get function signature
sig = inspect.signature(example_function)
print(f"Function signature: {sig}")

# Get source code (if available)
try:
    source = inspect.getsource(example_function)
    print(f"Source code:
{source}")
except:
    print("Source code not available")

Practice Exercise

Create a comprehensive text analysis package:


# text_analyzer/
#     __init__.py
#     statistics.py
#     sentiment.py
#     readability.py

# text_analyzer/__init__.py
"""
Text Analysis Package

A comprehensive package for analyzing text content including
statistics, sentiment analysis, and readability metrics.
"""

__version__ = "1.0.0"
__author__ = "Your Name"

from .statistics import TextStatistics
from .sentiment import SentimentAnalyzer
from .readability import ReadabilityAnalyzer

class TextAnalyzer:
    """Main text analyzer class combining all analysis tools"""
    
    def __init__(self):
        self.stats = TextStatistics()
        self.sentiment = SentimentAnalyzer()
        self.readability = ReadabilityAnalyzer()
    
    def analyze(self, text):
        """Perform comprehensive text analysis"""
        return {
            'statistics': self.stats.analyze(text),
            'sentiment': self.sentiment.analyze(text),
            'readability': self.readability.analyze(text)
        }

# text_analyzer/statistics.py
"""
Text statistics analysis module
"""

import re
from collections import Counter

class TextStatistics:
    """Calculate various text statistics"""
    
    def __init__(self):
        self.word_pattern = re.compile(r'w+')
        self.sentence_pattern = re.compile(r'[.!?]+s*')
    
    def count_characters(self, text):
        """Count total characters"""
        return len(text)
    
    def count_characters_no_spaces(self, text):
        """Count characters excluding spaces"""
        return len(re.sub(r's', '', text))
    
    def count_words(self, text):
        """Count total words"""
        return len(self.word_pattern.findall(text))
    
    def count_sentences(self, text):
        """Count sentences"""
        sentences = self.sentence_pattern.split(text)
        return len([s for s in sentences if s.strip()])
    
    def count_paragraphs(self, text):
        """Count paragraphs"""
        paragraphs = text.split('

')
        return len([p for p in paragraphs if p.strip()])
    
    def average_word_length(self, text):
        """Calculate average word length"""
        words = self.word_pattern.findall(text)
        if not words:
            return 0
        return sum(len(word) for word in words) / len(words)
    
    def average_sentence_length(self, text):
        """Calculate average sentence length in words"""
        word_count = self.count_words(text)
        sentence_count = self.count_sentences(text)
        return word_count / sentence_count if sentence_count > 0 else 0
    
    def word_frequency(self, text, top_n=10):
        """Get most frequent words"""
        words = [word.lower() for word in self.word_pattern.findall(text)]
        return Counter(words).most_common(top_n)
    
    def analyze(self, text):
        """Perform complete statistical analysis"""
        return {
            'character_count': self.count_characters(text),
            'character_count_no_spaces': self.count_characters_no_spaces(text),
            'word_count': self.count_words(text),
            'sentence_count': self.count_sentences(text),
            'paragraph_count': self.count_paragraphs(text),
            'average_word_length': round(self.average_word_length(text), 2),
            'average_sentence_length': round(self.average_sentence_length(text), 2),
            'top_words': self.word_frequency(text)
        }

# text_analyzer/sentiment.py
"""
Simple sentiment analysis module
"""

class SentimentAnalyzer:
    """Basic sentiment analysis using word lists"""
    
    def __init__(self):
        # Simple word lists for demonstration
        self.positive_words = {
            'good', 'great', 'excellent', 'amazing', 'wonderful',
            'fantastic', 'awesome', 'brilliant', 'outstanding', 'superb',
            'happy', 'joy', 'love', 'like', 'enjoy', 'pleased',
            'satisfied', 'delighted', 'thrilled', 'excited'
        }
        
        self.negative_words = {
            'bad', 'terrible', 'awful', 'horrible', 'disgusting',
            'hate', 'dislike', 'angry', 'sad', 'disappointed',
            'frustrated', 'annoyed', 'upset', 'worried', 'concerned',
            'poor', 'worst', 'fail', 'failed', 'wrong'
        }
    
    def get_word_sentiment(self, word):
        """Get sentiment score for a single word"""
        word = word.lower()
        if word in self.positive_words:
            return 1
        elif word in self.negative_words:
            return -1
        else:
            return 0
    
    def analyze(self, text):
        """Analyze sentiment of text"""
        words = text.lower().split()
        
        positive_count = 0
        negative_count = 0
        total_sentiment = 0
        
        for word in words:
            # Remove punctuation
            clean_word = '.join(c for c in word if c.isalnum())
            sentiment = self.get_word_sentiment(clean_word)
            
            if sentiment > 0:
                positive_count += 1
            elif sentiment < 0:
                negative_count += 1
            
            total_sentiment += sentiment
        
        # Calculate overall sentiment
        if total_sentiment > 0:
            overall = 'positive'
        elif total_sentiment < 0:
            overall = 'negative'
        else:
            overall = 'neutral'
        
        return {
            'overall_sentiment': overall,
            'sentiment_score': total_sentiment,
            'positive_words': positive_count,
            'negative_words': negative_count,
            'total_words': len(words)
        }

# text_analyzer/readability.py
"""
Text readability analysis module
"""

import re

class ReadabilityAnalyzer:
    """Calculate readability metrics"""
    
    def __init__(self):
        self.sentence_pattern = re.compile(r'[.!?]+s*')
        self.word_pattern = re.compile(r'w+')
        self.syllable_pattern = re.compile(r'[aeiouyAEIOUY]+')
    
    def count_syllables(self, word):
        """Estimate syllable count in a word"""
        # Simple syllable counting (not perfect but reasonable)
        word = word.lower()
        syllables = len(self.syllable_pattern.findall(word))
        
        # Adjust for silent e
        if word.endswith('e') and syllables > 1:
            syllables -= 1
        
        # Ensure at least 1 syllable
        return max(1, syllables)
    
    def flesch_reading_ease(self, text):
        """Calculate Flesch Reading Ease score"""
        words = self.word_pattern.findall(text)
        sentences = len([s for s in self.sentence_pattern.split(text) if s.strip()])
        
        if not words or sentences == 0:
            return 0
        
        total_syllables = sum(self.count_syllables(word) for word in words)
        
        # Flesch Reading Ease formula
        score = (206.835 - 
                (1.015 * len(words) / sentences) - 
                (84.6 * total_syllables / len(words)))
        
        return round(score, 2)
    
    def flesch_kincaid_grade(self, text):
        """Calculate Flesch-Kincaid Grade Level"""
        words = self.word_pattern.findall(text)
        sentences = len([s for s in self.sentence_pattern.split(text) if s.strip()])
        
        if not words or sentences == 0:
            return 0
        
        total_syllables = sum(self.count_syllables(word) for word in words)
        
        # Flesch-Kincaid Grade Level formula
        grade = (0.39 * len(words) / sentences) + (11.8 * total_syllables / len(words)) - 15.59
        
        return round(max(0, grade), 2)
    
    def get_reading_level(self, flesch_score):
        """Convert Flesch score to reading level description"""
        if flesch_score >= 90:
            return "Very Easy"
        elif flesch_score >= 80:
            return "Easy"
        elif flesch_score >= 70:
            return "Fairly Easy"
        elif flesch_score >= 60:
            return "Standard"
        elif flesch_score >= 50:
            return "Fairly Difficult"
        elif flesch_score >= 30:
            return "Difficult"
        else:
            return "Very Difficult"

# Example usage
analyzer = ReadabilityAnalyzer()
text = "Python is a powerful programming language. It is easy to learn and use."

print(f"Word count: {analyzer.count_words(text)}")
print(f"Character count: {analyzer.count_characters(text)}")
print(f"Flesch Reading Ease: {analyzer.flesch_reading_ease(text)}")
print(f"Reading Level: {analyzer.get_reading_level(analyzer.flesch_reading_ease(text))}")