Project Anatomy — Packages, Imports และ pyproject.toml
Files, modules และ packages
หัวข้อที่มีชื่อว่า “Files, modules และ packages”ใน Node.js ทุกไฟล์ .js/.ts คือ module และคุณดึงของออกมาด้วย import ฝั่ง Python ทำงานแบบเดียวกัน ต่างกันแค่คำศัพท์เล็กน้อย:
- Module — ไฟล์
.pyไฟล์ไหนก็ได้ เช่นmath.pyคือ module ชื่อmath - Package — ไดเรกทอรีที่มี
__init__.pyอยู่ ใช้จัดกลุ่ม module ที่เกี่ยวข้องกัน - Namespace package — ไดเรกทอรีที่ไม่มี
__init__.py(Python 3.3+) ในงานทั่วไปแทบไม่ได้ใช้
// Node/TypeScript module layoutsrc/ utils/ string.ts ← module number.ts ← module api/ index.ts ← entry point index.ts ← root entry
// import from a moduleimport { capitalize } from "./utils/string";# Python package layoutsrc/ utils/ __init__.py ← marks this as a package string.py ← module number.py ← module api/ __init__.py routes.py main.py ← entry point
# import from a modulefrom utils.string import capitalizeไฟล์ __init__.py
หัวข้อที่มีชื่อว่า “ไฟล์ __init__.py”__init__.py ทำหน้าที่เดียวกับ index.ts ที่วางไว้ในโฟลเดอร์ คือประกาศว่าไดเรกทอรีนี้เป็น package และจะ re-export symbol ต่อออกไปด้วยก็ได้ ปล่อยให้เป็นไฟล์เปล่าก็ถูกต้องแล้ว และนั่นคือขั้นต่ำที่ต้องมี
// TypeScript: src/utils/index.tsexport { capitalize } from "./string";export { clamp } from "./number";
// Caller imports from the directory:import { capitalize, clamp } from "./utils";# Python: src/utils/__init__.pyfrom .string import capitalizefrom .number import clamp
# Caller imports from the package:from utils import capitalize, clampไวยากรณ์ Import
หัวข้อที่มีชื่อว่า “ไวยากรณ์ Import”Python มีรูปแบบ import สองแบบ และใช้กันทั่วไปทั้งคู่ เลือกแบบไหนขึ้นอยู่กับบริบทที่ใช้
// TypeScript import stylesimport path from "path"; // default importimport { join, resolve } from "path"; // named importsimport * as fs from "fs"; // namespace importimport type { Dirent } from "fs"; // type-only import# Python import stylesimport math # import the whole modulefrom math import sqrt, pi # import specific namesfrom math import sqrt as sq # aliasimport os.path # sub-module importfrom . import sibling # relative: same packagefrom ..utils import helper # relative: parent packagepyproject.toml — เทียบเท่ากับ package.json
หัวข้อที่มีชื่อว่า “pyproject.toml — เทียบเท่ากับ package.json”โปรเจกต์ Python สมัยใหม่ใช้ pyproject.toml เป็น project manifest ไฟล์เดียวจบ เข้ามาแทน setup.py, setup.cfg และงานบางส่วนของ requirements.txt
// package.json (Node){ "name": "my-api", "version": "1.0.0", "description": "A REST API", "main": "dist/index.js", "scripts": { "dev": "ts-node src/index.ts", "build": "tsc", "test": "jest" }, "dependencies": { "express": "^4.18.2" }, "devDependencies": { "typescript": "^5.0.0", "jest": "^29.0.0" }}# pyproject.toml (Python — PEP 517/518/621)[project]name = "my-api"version = "1.0.0"description = "A REST API"requires-python = ">=3.11"dependencies = [ "fastapi>=0.110.0", "uvicorn>=0.27.0",]
[project.optional-dependencies]dev = ["pytest>=8.0", "mypy>=1.8"]
[project.scripts]start = "my_api.main:main" # like "main" in package.json
[build-system]requires = ["hatchling"]build-backend = "hatchling.build"requirements.txt — เทียบเท่ากับ package-lock
หัวข้อที่มีชื่อว่า “requirements.txt — เทียบเท่ากับ package-lock”requirements.txt คือ list แบบ flat ของ package version ที่ pin ไว้ โครงสร้างเรียบง่ายกว่า package-lock.json แต่มีเป้าหมายเดียวกัน คือทำให้เพื่อนร่วมทีมและ CI ติดตั้งได้ผลลัพธ์เหมือนกันทุกครั้ง
# Nodenpm install # reads package.json + package-lock.jsonnpm ci # clean install, fails if lock is missing
# Add a packagenpm install express # auto-updates package.json + lock# Python (classic)pip install -r requirements.txt # install from snapshot
# Generate the snapshotpip freeze > requirements.txt
# Add a packagepip install fastapipip freeze > requirements.txt # update snapshot manually
# Modern: use poetry or uvpoetry add fastapi # adds to pyproject.toml + lockLayout โปรเจกต์ทั่วไป
หัวข้อที่มีชื่อว่า “Layout โปรเจกต์ทั่วไป”ด้านล่างคือ layout ของโปรเจกต์ Python API แบบที่ใช้งานจริง พร้อมกำกับว่าอะไรเทียบกับอะไรในฝั่ง Node:
# Node/TypeScript projectmy-api/ src/ index.ts ← entry point routes/ users.ts services/ user.service.ts package.json ← manifest package-lock.json ← lockfile tsconfig.json ← compiler config node_modules/ ← dependencies (gitignored) .env ← secrets (gitignored)# Python projectmy-api/ src/ my_api/ __init__.py main.py ← entry point routes/ __init__.py users.py services/ __init__.py user.py pyproject.toml ← manifest requirements.txt ← lockfile (or poetry.lock) venv/ ← dependencies (gitignored) .env ← secrets (gitignored)# Demonstrating Python imports and stdlib modulesimport mathimport os.path
# Using a stdlib module (like a built-in npm package)radius = 5area = math.pi * radius ** 2print(f"Circle area (r={radius}): {area:.4f}")
# Path manipulation — like Node's path.joinparts = ["src", "my_api", "main.py"]joined = os.path.join(*parts)print(f"Joined path: {joined}")
# __name__ guard — entry point detectiondef main() -> None: print("main() called from entry point")
if __name__ == "__main__": main()Loading Python runtime (first run only)…