(Bài 7): Module và thư viện trong python

Tại sao cần học module & package?

Khi chương trình lớn lên (100 → 1.000 → 10.000 dòng), bạn không thể giữ mọi thứ trong một file .py duy nhất. Module và package là cách Python cho phép bạn tổ chức, tái sử dụng, phân phốichia sẻ mã nguồn. Hiểu rõ module/package giúp bạn:

  • Viết code có cấu trúc, dễ bảo trì.

  • Dùng lại code giữa nhiều project.

  • Cài đặt thư viện bên thứ ba (via pip).

  • Tạo package để phát hành cho cộng đồng (PyPI).

  • Quản lý môi trường (venv) để tránh xung đột dependency.

Bài này sẽ hướng dẫn từ cơ bản → thực tế: cách tạo module, package, import, virtualenv/venv, pip, requirements.txt, packaging (pyproject.toml), entry points, testing cơ bản, và best practices.


Module là gì?

  • Module = một file Python (ví dụ utils.py, math_utils.py) chứa biến, hàm, class…

  • Bạn có thể import module để dùng các thành phần bên trong.

Ví dụ math_utils.py:

# math_utils.py
def add(a, b):
return a + b
def mul(a, b):
return a * b

PI = 3.14159

Dùng module:

# main.py
import math_utils
print(math_utils.add(2, 3))
print(math_utils.PI)

Diễn giải: import math_utils nạp module và bạn truy cập tên bằng math_utils.<name>.


Cách import: absolute vs relative, from … import …

Absolute import

import package.module
from package.module import func, ClassName

From-import (chỉ lấy tên cần thiết)

from math_utils import add, PI
print(add(1,2))

Lưu ý: from module import * hiếm khi nên dùng — gây ô nhiễm namespace.

Relative import (trong package)

Trong package, bạn có thể dùng ...:

package/
├─ __init__.py
├─ a.py
└─ subpkg/
├─ __init__.py
└─ b.py

Trong subpkg/b.py, import a.py bằng:

from .. import a # lên một cấp, lấy module a
from ..a import some_fn # lấy trực tiếp hàm

Diễn giải: relative import chỉ dùng trong package (khi bạn chạy toàn bộ package, hoặc cài package; chạy file module trực tiếp có thể gây lỗi import tương đối).


Package là gì?

  • Package = thư mục chứa file __init__.py (trước kia bắt buộc; giờ có namespace packages nhưng dùng __init__.py vẫn phổ biến), chứa nhiều module và subpackage.

  • Cho phép tổ chức module theo cấu trúc.

Ví dụ cấu trúc package:

myapp/
├─ mypkg/
│ ├─ __init__.py
│ ├─ core.py
│ ├─ utils.py
│ └─ subpkg/
│ ├─ __init__.py
│ └─ helper.py
└─ setup.py (hoặc pyproject.toml)

__init__.py có thể để trống hoặc export các API mặc định:

# mypkg/__init__.py
from .core import main_function
from .utils import helper_func
__all__ = [“main_function”, “helper_func”]

Diễn giải: khi import mypkg, Python thực thi mypkg/__init__.py. Dùng __all__ để kiểm soát from mypkg import *.


Ví dụ thực tế: tạo một package “calculator”

Cấu trúc:

calculator/
├─ calculator/
│ ├─ __init__.py
│ ├─ operations.py
│ └─ utils.py
└─ pyproject.toml

operations.py:

# operations.py
def add(a, b): return a + b
def sub(a, b): return a - b
def mul(a, b): return a * b
def div(a, b):
if b == 0:
raise ZeroDivisionError("Division by zero")
return a / b

__init__.py:

from .operations import add, sub, mul, div

__all__ = [“add”, “sub”, “mul”, “div”]

Sử dụng:

# demo.py
from calculator import add, div
print(add(2,3))
print(div(10,2))


if __name__ == "__main__": — module vừa là module vừa là script

Trong file module, bạn có thể thêm đoạn này để cho phép chạy file trực tiếp:

def main():
print("This runs when executed as script")
if __name__ == “__main__”:
main()

  • Khi python file.py__name__ == "__main__" → chạy main().

  • Khi import file__name__ == "file" → không chạy.

Diễn giải: hữu ích để test module đơn giản.


Quản lý môi trường: venv / virtualenv

Vì các project dùng các version package khác nhau, luôn tạo môi trường ảo (virtual environment).

  • Tạo venv (Python 3 built-in):

python -m venv .venv
# hoặc
python3 -m venv venv
  • Kích hoạt:

    • Windows: .\venv\Scripts\activate

    • macOS/Linux: source venv/bin/activate

  • Cài package (sau activate):

pip install requests
  • Lưu dependencies:

pip freeze > requirements.txt
  • Cài dependencies từ file:

pip install -r requirements.txt

Diễn giải: venv giúp cô lập dependencies giữa project.


pip cơ bản (cài, nâng cấp, gỡ)

  • Cài package: pip install <package>

  • Cài phiên bản cụ thể: pip install requests==2.31.0

  • Nâng cấp: pip install --upgrade <package>

  • Gỡ: pip uninstall <package>

Lưu ý: cài trong venv để tránh cài global.


requirements.txt

File văn bản liệt kê dependencies, ví dụ:

requests>=2.28
numpy==1.25.1
flask>=2.2,<3.0

Sử dụng pip freeze > requirements.txt để tạo file từ môi trường hiện tại. Khi deploy hoặc share project, dùng pip install -r requirements.txt.


Tạo package chuẩn: pyproject.toml (khuyến nghị hiện nay)

pyproject.toml là chuẩn hiện đại để mô tả package, thay thế setup.py/setup.cfg truyền thống.

Ví dụ tối giản:

[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = “calculator”
version = “0.1.0”
description = “A tiny calculator package”
authors = [{name=“Your Name”, email=“you@example.com”}]
readme = “README.md”
license = {text = “MIT”}
dependencies = [
“numpy>=1.25”
]

  • Sau khi cấu hình, bạn có thể build:

python -m build

(tạo dist/ chứa wheel và sdist).

  • Upload lên PyPI với twine (ngoài phạm vi chi tiết từng bước).

Diễn giải: pyproject là tiêu chuẩn PEP 518/PEP 621; hiện là cách hiện đại để packaging.


Cài package đang phát triển (editable install)

Khi phát triển package, dùng:

pip install -e .

Trong thư mục chứa pyproject.toml hay setup.py. -e (editable) cho phép thay đổi mã nguồn ngay lập tức có hiệu lực trong môi trường.

Diễn giải: tiện lợi khi code package và test trong project khác.


Entry points / tạo CLI cho package

Bạn có thể tạo command-line entry point để người dùng chạy lệnh từ terminal.

Trong pyproject.toml (setuptools) hoặc setup.cfg, cấu hình entry_points:

[project.scripts]
calc = "calculator.cli:main"

Nếu build & cài, người dùng có thể gõ calc để chạy calculator/cli.py‘s main().

Ví dụ calculator/cli.py:

def main():
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("op", choices=["add","sub"])
parser.add_argument("a", type=float)
parser.add_argument("b", type=float)
args = parser.parse_args()
if args.op == “add”:
print(args.a + args.b)
else:
print(args.a – args.b)


Testing cơ bản: pytest

Viết test giúp bảo vệ code khi refactor.

Cài pytest: pip install pytest

Ví dụ test cho operations.py:

tests/
test_operations.py

test_operations.py:

from calculator.operations import add, div

def test_add():
assert add(2,3) == 5

def test_div():
assert div(6,2) == 3

Chạy pytest để chạy tất cả test. Tích hợp CI (GitHub Actions) là bước tiếp theo.

Diễn giải: test giúp tránh regressions.


Relative imports và vấn đề khi chạy module trực tiếp

  • Khi dùng relative import, không chạy file module trực tiếp (python subpkg/module.py), vì __package__ sẽ khác và imports tương đối phá vỡ.

  • Thay vào đó, chạy package bằng:

python -m package.module

-m chạy module theo package context, import tương đối sẽ hoạt động.


Namespace packages (PEP 420)

Bạn có thể tạo package không cần __init__.py — gọi là namespace package. Thường dùng khi nhiều project muốn đóng gói subpackages chung. Tuy nhiên cho người mới, tốt nhất vẫn dùng __init__.py.


Lazy imports (nhập khi cần) và performance

Đôi khi import sẽ tốn thời gian (nạp nhiều module). Nếu một hàm chỉ dùng một package lớn vài trường hợp, bạn có thể import bên trong hàm:

def parse_csv(path):
import pandas as pd
return pd.read_csv(path)

Diễn giải: tránh chi phí import khi module được import ở những tình huống không dùng chức năng đó.


Circular imports (vòng lặp import) và cách tránh

Circular import xảy ra khi a.py import b.pyb.py import a.py. Triệu chứng: AttributeError hoặc ImportError vì module chưa được khởi tạo.

Cách tránh:

  • Tái cấu trúc: tách phần chung ra module thứ ba (common.py).

  • Dùng import trong hàm (lazy import).

  • Dùng interfaces/abstractions để giảm phụ thuộc.

Ví dụ fix:

# bad: a.py
from b import func_b
def func_a(): func_b()
# bad: b.py
from a import func_a
def func_b(): func_a()

Fix bằng tách shared.py.


Best practices & style khi làm module/package

  • Đặt tên package, module ngắn, có ý nghĩa, không đặt tên trùng stdlib (ví dụ json.py).

  • Tách logic thành module theo trách nhiệm (SRP).

  • Sử dụng __all__ nếu muốn giới hạn export.

  • Không dùng wildcard import (from X import *).

  • Document (docstring) module & hàm.

  • Viết tests.

  • Sử dụng semantic versioning (MAJOR.MINOR.PATCH).

  • Để requirements.txtpyproject.toml rõ ràng.

  • Dùng CI để test & lint (flake8, black).


Ví dụ nâng cao: kết hợp mọi thứ vào project nhỏ

Project: notes – package cho phép lưu/đọc ghi chú text, có CLI.

Cấu trúc:

notes/
notes/
__init__.py
storage.py
cli.py
pyproject.toml
README.md

storage.py:

from pathlib import Path

NOTES_DIR = Path.home() / “.notes”

def save(title, content):
NOTES_DIR.mkdir(exist_ok=True)
path = NOTES_DIR / f”{title}.txt”
with open(path, “w”, encoding=“utf8”) as f:
f.write(content)
return str(path)

def read(title):
path = NOTES_DIR / f”{title}.txt”
with open(path, “r”, encoding=“utf8”) as f:
return f.read()

cli.py:

def main():
import argparse
from .storage import save, read
p = argparse.ArgumentParser()
p.add_argument(“cmd”, choices=[“save”,“read”])
p.add_argument(“title”)
p.add_argument(“–content”, default=“”)
args = p.parse_args()

if args.cmd == “save”:
path = save(args.title, args.content)
print(“Saved to”, path)
else:
print(read(args.title))

Trong pyproject.toml thêm entry point:

[project.scripts]
notes = "notes.cli:main"

Sau build & cài, người dùng chạy notes save mynote --content "hello" rồi notes read mynote.


Khi nào tạo module riêng, khi nào dùng thư viện có sẵn?

  • Nếu chức năng nhỏ & dự án nội bộ → tạo module trong repo.

  • Nếu là chức năng chung, cần dùng nhiều project → cân nhắc làm package và publish lên PyPI.

  • Nếu có thư viện nổi tiếng (requests, numpy, pandas) hãy dùng thay vì reinvent wheel.


Tóm tắt — checklist khi làm module/package

  1. Tạo thư mục package với __init__.py.

  2. Viết module có docstring, hàm/class rõ ràng.

  3. Dùng virtualenv để phát triển.

  4. Quản lý dependencies bằng pyproject.toml / requirements.txt.

  5. Viết tests (pytest).

  6. Dùng pip install -e . khi phát triển.

  7. Tạo pyproject.toml hợp lệ để build package.

  8. Thiết lập entry points nếu muốn CLI.

  9. Lưu version hợp lý (semantic versioning).

  10. Liên tục test & lint trước khi publish.


Bài tập thực hành (có lời hướng dẫn)

  1. Tạo module string_utils.py chứa hàm:

    • is_palindrome(s)

    • count_vowels(s)

    • reverse_words(s)
      Viết file test bằng pytest.

  2. Tạo package todo:

    • todo/storage.py lưu todo vào file JSON.

    • todo/cli.py có lệnh add, list, remove.

    • Tạo pyproject.toml và cài pip install -e ., chạy CLI.

  3. Quản lý dependencies:

    • Tạo venv, cài requests, export requirements.txt.

    • Trong một module, lazy-import requests chỉ khi hàm fetch(url) được gọi.

  4. Packaging nhỏ:

    • Viết pyproject.toml cho package string_utils, build wheel, cài wheel vào venv khác.


Tầm quan trọng của module & package

Module và package là kỹ năng không thể thiếu để trở thành lập trình viên Python thực thụ. Học cách tổ chức mã nguồn, quản lý môi trường, sử dụng pip và packaging sẽ giúp bạn:

  • Phát triển dự án an toàn, reproducible.

  • Chia sẻ code cho cộng đồng (hoặc team).

  • Dễ dàng liên kết với hệ sinh thái Python rộng lớn.

Tiếp tục chuỗi series tự học python: (BÀI 8): Deployment & CI/CD với GitHub Actions

1 Trackback / Pingback

  1. (Bài 6): Hàm (function) trong Python - aidanang.com

Leave a Reply

Your email address will not be published.


*