Bài 12: Modules và Packages (Phần 2)

Mục Tiêu Bài Học

Sau khi hoàn thành bài này, bạn sẽ:

  • ✅ Hiểu package là gì và cách tổ chức
  • ✅ Tạo packages với __init__.py
  • ✅ Sử dụng pip để cài đặt packages
  • ✅ Tạo và quản lý virtual environments
  • ✅ Làm việc với requirements.txt

Package Là Gì?

Package là thư mục chứa nhiều modules và file __init__.py.

my_package/    __init__.py      # Makes it a package    module1.py       # Module 1    module2.py       # Module 2    subpackage/        __init__.py  # Subpackage        module3.py   # Module in subpackage

Package vs Module:

  • Module: File Python đơn lẻ (.py)
  • Package: Thư mục chứa modules + __init__.py

Tại Sao Dùng Packages?

# Không có package - flat structureproject/    utils.py    validators.py    formatters.py    parsers.py    converters.py    # ... 50 more files # Có packages - organizedproject/    utils/        __init__.py        string_utils.py        file_utils.py        date_utils.py    validators/        __init__.py        email.py        phone.py        password.py    formatters/        __init__.py        json_formatter.py        xml_formatter.py

Tạo Package

Basic Package Structure

# Project structuremyapp/    mypackage/        __init__.py        module1.py        module2.py    main.py # File: mypackage/module1.pydef hello():    return "Hello from module1" def add(a, b):    return a + b # File: mypackage/module2.pydef goodbye():    return "Goodbye from module2" def multiply(a, b):    return a * b # File: mypackage/__init__.py# Empty file (makes it a package) # File: main.pyfrom mypackage import module1, module2 print(module1.hello())      # Hello from module1print(module2.goodbye())    # Goodbye from module2print(module1.add(5, 3))    # 8print(module2.multiply(4, 2))  # 8

__init__.py - Package Initialization

__init__.py chạy khi package được import.

# File: mypackage/__init__.py"""My Package - A collection of utilities. This package provides various utility functions.""" print("Initializing mypackage...") # Package-level variableVERSION = "1.0.0" # Import commonly used itemsfrom .module1 import hello, addfrom .module2 import goodbye, multiply # Define public API__all__ = ['hello', 'goodbye', 'add', 'multiply', 'VERSION'] # File: main.pyimport mypackage# Output: Initializing mypackage... # Direct accessprint(mypackage.hello())      # Hello from module1print(mypackage.VERSION)      # 1.0.0 # Hoặcfrom mypackage import hello, addprint(hello())  # Hello from module1print(add(5, 3))  # 8

Relative Imports

Relative imports dùng trong packages.

# Project structuremyapp/    mypackage/        __init__.py        core.py        utils.py        helpers/            __init__.py            formatter.py # File: mypackage/core.pyfrom .utils import process_data      # Same levelfrom .helpers import formatter       # Subpackage def main():    data = process_data([1, 2, 3])    formatted = formatter.format_data(data)    return formatted # File: mypackage/utils.pydef process_data(data):    return [x * 2 for x in data] # File: mypackage/helpers/formatter.pyfrom ..utils import process_data  # Parent package def format_data(data):    return f"Data: {data}" # Relative import syntax:# . = current package# .. = parent package# ... = parent's parent package

Subpackages

# Project structuremyproject/    utils/        __init__.py        string/            __init__.py            operations.py            validators.py        math/            __init__.py            basic.py            advanced.py    main.py # File: utils/string/operations.pydef reverse(text):    return text[::-1] def capitalize_words(text):    return text.title() # File: utils/string/validators.pydef is_palindrome(text):    text = text.lower().replace(" ", "")    return text == text[::-1] # File: utils/string/__init__.pyfrom .operations import reverse, capitalize_wordsfrom .validators import is_palindrome __all__ = ['reverse', 'capitalize_words', 'is_palindrome'] # File: utils/__init__.pyfrom . import stringfrom . import math # File: main.pyfrom utils.string import reverse, is_palindrome print(reverse("hello"))           # ollehprint(is_palindrome("radar"))     # True # Orimport utilsprint(utils.string.reverse("world"))  # dlrow

pip - Python Package Manager

pip là tool để install, uninstall, và manage Python packages.

Basic pip Commands

# Check pip versionpip --version# pip 24.0 from /usr/local/lib/python3.11/site-packages/pip # Install packagepip install requests # Install specific versionpip install requests==2.31.0 # Install minimum versionpip install requests>=2.30.0 # Upgrade packagepip install --upgrade requests # Uninstall packagepip uninstall requests # Show package infopip show requests # List installed packagespip list # Search for packages (deprecated, use pypi.org)# pip search package_name
# Web frameworkspip install djangopip install flaskpip install fastapi # Data sciencepip install numpypip install pandaspip install matplotlib # Testingpip install pytestpip install unittest2 # HTTP requestspip install requestspip install httpx # Databasepip install psycopg2-binary  # PostgreSQLpip install pymongo          # MongoDB # Utilitiespip install python-dotenv    # Environment variablespip install pillow           # Image processingpip install beautifulsoup4   # Web scraping

Using Installed Packages

# After: pip install requestsimport requests # Make HTTP requestresponse = requests.get('https://api.github.com')print(response.status_code)  # 200print(response.json())       # JSON data # After: pip install python-dotenvfrom dotenv import load_dotenvimport os load_dotenv()  # Load .env fileapi_key = os.getenv('API_KEY') # After: pip install pillowfrom PIL import Image img = Image.open('photo.jpg')img.thumbnail((200, 200))img.save('thumbnail.jpg')

Virtual Environments

Virtual Environment là isolated Python environment cho mỗi project.

Tại Sao Cần Virtual Environment?

# ❌ Không có venv - conflicts# Project A needs Django 3.2# Project B needs Django 4.2# Cannot install both globally! # ✅ Có venv - isolatedproject_a/    venv/           # Django 3.2    app.py    project_b/    venv/           # Django 4.2    app.py

Tạo Virtual Environment

# Using venv (built-in, Python 3.3+)python -m venv myenv # Or with specific namepython -m venv venvpython -m venv .venvpython -m venv env # Directory structure createdmyenv/    bin/         # Unix/Mac    Scripts/     # Windows    lib/    include/    pyvenv.cfg

Activate Virtual Environment

# macOS/Linuxsource myenv/bin/activate # Windows (CMD)myenv\Scripts\activate.bat # Windows (PowerShell)myenv\Scripts\Activate.ps1 # After activation(myenv) $ python --version(myenv) $ which python# /path/to/myenv/bin/python

Deactivate Virtual Environment

# Any OSdeactivate # Returns to global Python$ python --version$ which python# /usr/bin/python

Virtual Environment Workflow

# 1. Create projectmkdir myprojectcd myproject # 2. Create virtual environmentpython -m venv venv # 3. Activatesource venv/bin/activate  # macOS/Linux # 4. Install packages(venv) $ pip install django(venv) $ pip install requests(venv) $ pip install pytest # 5. Check installed packages(venv) $ pip list # 6. Work on project(venv) $ python app.py # 7. Deactivate when done(venv) $ deactivate

requirements.txt

requirements.txt lists all dependencies.

Creating requirements.txt

# Generate from current environmentpip freeze > requirements.txt # View contentscat requirements.txt# Django==4.2.7# requests==2.31.0# pytest==7.4.3

Format của requirements.txt

# Basic formatDjango==4.2.7requests==2.31.0 # With comments# Web frameworkDjango==4.2.7 # HTTP libraryrequests==2.31.0 # Version specifiersDjango>=4.2.0,<5.0.0    # Rangerequests>=2.30          # Minimumpytest==7.4.*           # Any patch version # From git repositorygit+https://github.com/user/repo.git@branch # From local directory./local-package # With extrasrequests[security]django[argon2]

Using requirements.txt

# Install all dependenciespip install -r requirements.txt # Upgrade allpip install --upgrade -r requirements.txt # Install in editable mode (development)pip install -e .

Different Requirements Files

# Project structuremyproject/    requirements/        base.txt        # Base dependencies        dev.txt         # Development only        prod.txt        # Production only    requirements.txt    # All dependencies # File: requirements/base.txtDjango==4.2.7requests==2.31.0python-dotenv==1.0.0 # File: requirements/dev.txt-r base.txt          # Include basepytest==7.4.3black==23.11.0flake8==6.1.0 # File: requirements/prod.txt-r base.txt          # Include basegunicorn==21.2.0psycopg2-binary==2.9.9 # Installpip install -r requirements/dev.txt   # Developmentpip install -r requirements/prod.txt  # Production

Ví Dụ Thực Tế

1. Complete Package Example

# Project structuremylib/    setup.py    README.md    mylib/        __init__.py        core.py        utils/            __init__.py            string_utils.py            math_utils.py    tests/        __init__.py        test_core.py    requirements.txt # File: mylib/__init__.py"""MyLib - A utility library. Version: 1.0.0""" __version__ = "1.0.0"__author__ = "Your Name" from .core import process, analyzefrom .utils import string_utils, math_utils __all__ = ['process', 'analyze', 'string_utils', 'math_utils'] # File: mylib/core.pyfrom .utils.string_utils import clean_textfrom .utils.math_utils import calculate_stats def process(data):    """Process data."""    cleaned = [clean_text(item) for item in data]    return cleaned def analyze(numbers):    """Analyze numbers."""    return calculate_stats(numbers) # File: mylib/utils/string_utils.pydef clean_text(text):    """Clean and normalize text."""    return text.strip().lower() def split_words(text):    """Split text into words."""    return text.split() # File: mylib/utils/math_utils.pydef calculate_stats(numbers):    """Calculate basic statistics."""    return {        'count': len(numbers),        'sum': sum(numbers),        'avg': sum(numbers) / len(numbers) if numbers else 0,        'min': min(numbers) if numbers else None,        'max': max(numbers) if numbers else None    } # File: mylib/utils/__init__.pyfrom . import string_utilsfrom . import math_utils # Usagefrom mylib import process, analyze data = ["  Hello  ", "  World  "]print(process(data))  # ['hello', 'world'] numbers = [1, 2, 3, 4, 5]print(analyze(numbers))# {'count': 5, 'sum': 15, 'avg': 3.0, 'min': 1, 'max': 5}

2. Django-like Project Structure

# Project structuremyproject/    venv/    myproject/        __init__.py        settings.py        urls.py        wsgi.py    apps/        users/            __init__.py            models.py            views.py            urls.py        posts/            __init__.py            models.py            views.py            urls.py    utils/        __init__.py        validators.py        helpers.py    manage.py    requirements.txt    .env # File: myproject/settings.py"""Project settings.""" import osfrom pathlib import Path BASE_DIR = Path(__file__).resolve().parent.parent SECRET_KEY = os.getenv('SECRET_KEY', 'dev-secret-key') DEBUG = os.getenv('DEBUG', 'True') == 'True' INSTALLED_APPS = [    'apps.users',    'apps.posts',] # File: utils/validators.py"""Validation utilities.""" import re def validate_email(email):    pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'    return bool(re.match(pattern, email)) # File: apps/users/models.py"""User models.""" from utils.validators import validate_email class User:    def __init__(self, email, name):        if not validate_email(email):            raise ValueError("Invalid email")        self.email = email        self.name = name # File: requirements.txtDjango==4.2.7python-dotenv==1.0.0psycopg2-binary==2.9.9 # File: .envSECRET_KEY=your-secret-key-hereDEBUG=TrueDATABASE_URL=postgresql://user:pass@localhost/dbname

3. Data Processing Package

# Project structuredataprocessor/    venv/    dataprocessor/        __init__.py        loader.py        cleaner.py        analyzer.py        exporter.py    examples/        example.py        data.csv    requirements.txt # File: dataprocessor/__init__.py"""Data Processor Package. A package for loading, cleaning, and analyzing data.""" __version__ = "1.0.0" from .loader import load_csv, load_jsonfrom .cleaner import clean_data, remove_nullsfrom .analyzer import calculate_stats, find_outliersfrom .exporter import export_csv, export_json __all__ = [    'load_csv', 'load_json',    'clean_data', 'remove_nulls',    'calculate_stats', 'find_outliers',    'export_csv', 'export_json'] # File: dataprocessor/loader.py"""Data loading utilities.""" import jsonimport csv def load_csv(filepath):    """Load data from CSV file."""    with open(filepath, 'r') as f:        reader = csv.DictReader(f)        return list(reader) def load_json(filepath):    """Load data from JSON file."""    with open(filepath, 'r') as f:        return json.load(f) # File: dataprocessor/cleaner.py"""Data cleaning utilities.""" def clean_data(data):    """Clean data by removing empty strings."""    return [{k: v for k, v in item.items() if v} for item in data] def remove_nulls(data):    """Remove null values from data."""    return [item for item in data if all(item.values())] # File: dataprocessor/analyzer.py"""Data analysis utilities.""" def calculate_stats(numbers):    """Calculate basic statistics."""    return {        'count': len(numbers),        'sum': sum(numbers),        'avg': sum(numbers) / len(numbers),        'min': min(numbers),        'max': max(numbers)    } def find_outliers(numbers, threshold=2):    """Find outliers in data."""    avg = sum(numbers) / len(numbers)    return [x for x in numbers if abs(x - avg) > threshold * avg] # File: examples/example.py"""Example usage.""" from dataprocessor import load_csv, clean_data, calculate_stats # Load datadata = load_csv('data.csv') # Clean dataclean = clean_data(data) # Analyzeages = [int(row['age']) for row in clean if 'age' in row]stats = calculate_stats(ages) print(f"Statistics: {stats}") # File: requirements.txt# No external dependencies needed for this example

4. API Client Package

# Project structureapiclient/    venv/    apiclient/        __init__.py        client.py        auth.py        exceptions.py        models/            __init__.py            user.py            post.py    tests/        test_client.py    requirements.txt # File: apiclient/__init__.py"""API Client Package. A simple API client for interacting with REST APIs.""" __version__ = "1.0.0" from .client import APIClientfrom .auth import BasicAuth, TokenAuthfrom .exceptions import APIError, AuthenticationError __all__ = ['APIClient', 'BasicAuth', 'TokenAuth', 'APIError'] # File: apiclient/client.py"""API client implementation.""" import requestsfrom .exceptions import APIError class APIClient:    """Simple API client."""        def __init__(self, base_url, auth=None):        self.base_url = base_url        self.auth = auth        self.session = requests.Session()        def get(self, endpoint):        """Make GET request."""        url = f"{self.base_url}{endpoint}"        response = self.session.get(url, auth=self.auth)                if response.status_code != 200:            raise APIError(f"Request failed: {response.status_code}")                return response.json()        def post(self, endpoint, data):        """Make POST request."""        url = f"{self.base_url}{endpoint}"        response = self.session.post(url, json=data, auth=self.auth)                if response.status_code not in [200, 201]:            raise APIError(f"Request failed: {response.status_code}")                return response.json() # File: apiclient/auth.py"""Authentication methods.""" from requests.auth import HTTPBasicAuth class BasicAuth(HTTPBasicAuth):    """Basic authentication."""    pass class TokenAuth:    """Token-based authentication."""        def __init__(self, token):        self.token = token        def __call__(self, request):        request.headers['Authorization'] = f'Bearer {self.token}'        return request # File: apiclient/exceptions.py"""Custom exceptions.""" class APIError(Exception):    """Base API error."""    pass class AuthenticationError(APIError):    """Authentication failed."""    pass # File: requirements.txtrequests==2.31.0 # Usage examplefrom apiclient import APIClient, TokenAuth client = APIClient(    base_url="https://api.example.com",    auth=TokenAuth("your-token-here")) users = client.get("/users")print(users)

Best Practices

# 1. Package structuremypackage/    __init__.py      # Always include    core.py          # Main functionality    utils.py         # Utilities    exceptions.py    # Custom exceptions    constants.py     # Constants # 2. Clear __init__.py# File: __init__.py"""Package description.""" __version__ = "1.0.0"__author__ = "Your Name" from .core import main_functionfrom .utils import helper_function __all__ = ['main_function', 'helper_function'] # 3. Use relative imports trong packagefrom .utils import helper  # Goodfrom mypackage.utils import helper  # Avoid # 4. Separate requirementsrequirements/    base.txt    dev.txt    prod.txt # 5. Always use virtual environmentpython -m venv venvsource venv/bin/activatepip install -r requirements.txt # 6. Document dependenciespip freeze > requirements.txt # 7. .gitignore for venv# File: .gitignorevenv/__pycache__/*.pyc.env

Bài Tập Thực Hành

Bài 1: Create Package

Tạo package mathtools:

  • basic.py - Basic operations
  • advanced.py - Advanced operations
  • __init__.py - Package initialization
  • Test trong separate file

Bài 2: Virtual Environment Setup

  1. Tạo project mới
  2. Setup virtual environment
  3. Install: requests, pytest
  4. Create requirements.txt
  5. Deactivate và recreate từ requirements.txt

Bài 3: Subpackage Structure

Tạo package với subpackages:

myapp/    utils/        string/        math/        date/

Bài 4: Package với CLI

Tạo package:

  • Core functionality
  • Command-line interface
  • if __name__ == "__main__" in __main__.py

Bài 5: Full Project

Tạo complete project:

  • Virtual environment
  • Multiple packages
  • requirements.txt (base, dev, prod)
  • .env for config
  • README.md
  • Tests

Tóm Tắt

Package: Thư mục + __init__.py
__init__.py: Chạy khi package import
Relative imports: .module (same level), ..module (parent)
pip: pip install package_name
Virtual env: python -m venv venv
Activate: source venv/bin/activate (macOS/Linux)
requirements.txt: pip freeze > requirements.txt
Install deps: pip install -r requirements.txt

Bài Tiếp Theo

Bài 13: File I/O - Read/write files, working với text, CSV, JSON, và binary files.


Remember:

  • Always use virtual environments!
  • One venv per project
  • Track dependencies in requirements.txt
  • Never commit venv/ to git
  • __init__.py makes directory a package
  • Use relative imports in packages!