ข้ามไปยังเนื้อหา

Project Anatomy — Packages, Imports และ pyproject.toml

ใน Node.js ทุกไฟล์ .js/.ts คือ module และคุณดึงของออกมาด้วย import ฝั่ง Python ทำงานแบบเดียวกัน ต่างกันแค่คำศัพท์เล็กน้อย:

  • Module — ไฟล์ .py ไฟล์ไหนก็ได้ เช่น math.py คือ module ชื่อ math
  • Package — ไดเรกทอรีที่มี __init__.py อยู่ ใช้จัดกลุ่ม module ที่เกี่ยวข้องกัน
  • Namespace package — ไดเรกทอรีที่ไม่มี __init__.py (Python 3.3+) ในงานทั่วไปแทบไม่ได้ใช้
TypeScript
// Node/TypeScript module layout
src/
utils/
string.tsmodule
number.tsmodule
api/
index.ts ← entry point
index.ts ← root entry
// import from a module
import { capitalize } from "./utils/string";
Python
# Python package layout
src/
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 module
from utils.string import capitalize

__init__.py ทำหน้าที่เดียวกับ index.ts ที่วางไว้ในโฟลเดอร์ คือประกาศว่าไดเรกทอรีนี้เป็น package และจะ re-export symbol ต่อออกไปด้วยก็ได้ ปล่อยให้เป็นไฟล์เปล่าก็ถูกต้องแล้ว และนั่นคือขั้นต่ำที่ต้องมี

TypeScript
// TypeScript: src/utils/index.ts
export { capitalize } from "./string";
export { clamp } from "./number";
// Caller imports from the directory:
import { capitalize, clamp } from "./utils";
Python
# Python: src/utils/__init__.py
from .string import capitalize
from .number import clamp
# Caller imports from the package:
from utils import capitalize, clamp

Python มีรูปแบบ import สองแบบ และใช้กันทั่วไปทั้งคู่ เลือกแบบไหนขึ้นอยู่กับบริบทที่ใช้

TypeScript
// TypeScript import styles
import path from "path"; // default import
import { join, resolve } from "path"; // named imports
import * as fs from "fs"; // namespace import
import type { Dirent } from "fs"; // type-only import
Python
# Python import styles
import math # import the whole module
from math import sqrt, pi # import specific names
from math import sqrt as sq # alias
import os.path # sub-module import
from . import sibling # relative: same package
from ..utils import helper # relative: parent package

โปรเจกต์ Python สมัยใหม่ใช้ pyproject.toml เป็น project manifest ไฟล์เดียวจบ เข้ามาแทน setup.py, setup.cfg และงานบางส่วนของ requirements.txt

TypeScript
// 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"
}
}
Python
# 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 คือ list แบบ flat ของ package version ที่ pin ไว้ โครงสร้างเรียบง่ายกว่า package-lock.json แต่มีเป้าหมายเดียวกัน คือทำให้เพื่อนร่วมทีมและ CI ติดตั้งได้ผลลัพธ์เหมือนกันทุกครั้ง

TypeScript
# Node
npm install # reads package.json + package-lock.json
npm ci # clean install, fails if lock is missing
# Add a package
npm install express # auto-updates package.json + lock
Python
# Python (classic)
pip install -r requirements.txt # install from snapshot
# Generate the snapshot
pip freeze > requirements.txt
# Add a package
pip install fastapi
pip freeze > requirements.txt # update snapshot manually
# Modern: use poetry or uv
poetry add fastapi # adds to pyproject.toml + lock

ด้านล่างคือ layout ของโปรเจกต์ Python API แบบที่ใช้งานจริง พร้อมกำกับว่าอะไรเทียบกับอะไรในฝั่ง Node:

TypeScript
# Node/TypeScript project
my-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)
.envsecrets (gitignored)
Python
# Python project
my-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 modules
import math
import os.path
# Using a stdlib module (like a built-in npm package)
radius = 5
area = math.pi * radius ** 2
print(f"Circle area (r={radius}): {area:.4f}")
# Path manipulation — like Node's path.join
parts = ["src", "my_api", "main.py"]
joined = os.path.join(*parts)
print(f"Joined path: {joined}")
# __name__ guard — entry point detection
def main() -> None:
print("main() called from entry point")
if __name__ == "__main__":
main()
อะไรทำให้ไดเรกทอรีเป็น Python package?
ไฟล์ไหนคือ Python เทียบเท่าสมัยใหม่ของ `package.json`?
`from utils import capitalize` ถือว่าอะไรเกี่ยวกับไดเรกทอรี `utils`?
คุณติดตั้งโปรเจกต์ในโหมด editable เพื่อให้การเปลี่ยนแปลง source สะท้อนทันทีได้อย่างไร?