# โมดูลและแพ็กเกจของ Python: import, pip, venv และไลบรารีอธิบายง่าย ๆ

URL: https://skillaria.top/th/guides/python/python-modules

โมดูลใน Python คือไฟล์ .py แพ็กเกจคือโฟลเดอร์ที่รวมโมดูลไว้ และไลบรารีคือชุดของแพ็กเกจที่เรียกใช้ด้วยคำสั่ง import ไลบรารีจากภายนอกติดตั้งด้วย pip หรือ uv ลงในสภาพแวดล้อมเสมือน venv ซึ่งเป็นโฟลเดอร์แยกที่มีตัวแปลภาษาและแพ็กเกจของโปรเจกต์นั้น ทำให้เวอร์ชันของแต่ละโปรเจกต์ไม่ตีกัน

โมดูลใน Python คือไฟล์ที่มีโค้ด และ `import` คือวิธีนำฟังก์ชันและคลาสจากไฟล์นั้นมาใช้ โมดูลรวมกันเป็นแพ็กเกจ แพ็กเกจรวมกันเป็นไลบรารี และเพื่อไม่ให้ไลบรารีของแต่ละโปรเจกต์ตีกัน เราติดตั้งไลบรารีภายนอกลงในสภาพแวดล้อมเสมือน venv บทความนี้จะอธิบายทั้งกระบวนการ ตั้งแต่สิ่งที่เกิดขึ้นตอน import โครงสร้างแพ็กเกจ การใช้ pip และ venv ไปจนถึง lazy import ที่มาใน Python 3.15

## โมดูล แพ็กเกจ ไลบรารี ต่างกันอย่างไร

| คำ | คืออะไร | ตัวอย่าง |
| --- | --- | --- |

| โมดูล (module) | ไฟล์ `.py` หนึ่งไฟล์ ชื่อโมดูลคือชื่อไฟล์ที่ไม่มีนามสกุล | `categories.py` → `import categories` |

| แพ็กเกจ (package) | โฟลเดอร์ที่มีโมดูลและไฟล์ `__init__.py` | `expenses/report.py` → `from expenses.report import build` |

| ไลบรารี (library) | ชุดโมดูลและแพ็กเกจสำเร็จรูปสำหรับงานอย่างหนึ่ง | `requests`, `pandas`, `pathlib` ในไลบรารีมาตรฐาน |

| ดิสทริบิวชัน (แพ็กเกจบน PyPI) | ไฟล์บีบอัดที่ pip ใช้ติดตั้ง | `pip install requests` |

ในการพูดคุยทั่วไปมักใช้คำว่า "ไลบรารี" "แพ็กเกจ" และ "โมดูล" สลับกันซึ่งเป็นเรื่องปกติ สิ่งสำคัญคือ ในทางเทคนิค `import` จะโหลดโมดูลหรือแพ็กเกจเสมอ ส่วน pip ติดตั้งดิสทริบิวชันจาก PyPI ซึ่งภายในมีแพ็กเกจอยู่

## import ใน Python ทำงานอย่างไร

การ import มีหลายรูปแบบ และทุกแบบพบได้ในโค้ดจริง:

```
import math                    # ทั้งโมดูล เรียกผ่าน math.sqrt
from math import sqrt, pi      # เฉพาะชื่อที่ต้องการ
import datetime as dt          # ตั้งชื่อย่อ
from collections import Counter as C

print(math.sqrt(16), sqrt(9), pi)
print(dt.date.today())
```

ไลบรารีขนาดใหญ่นิยมตั้งชื่อย่อ เช่น `import numpy as np` และ `import pandas as pd` ส่วนการใช้ `from module import *` ไม่แนะนำ เพราะทำให้เนมสเปซรกและไม่รู้ว่าชื่อนั้นมาจากไหน

### import ทำอะไรจริง ๆ

1. ค้นหาโมดูลในแคช `sys.modules` ถ้าโหลดไว้แล้วก็คืนค่าโมดูลนั้นทันที
2. หากไม่มี ค้นหาไฟล์ตามรายการโฟลเดอร์ใน `sys.path`: โฟลเดอร์ของสคริปต์ที่รัน ไลบรารีมาตรฐาน และ site-packages ของสภาพแวดล้อม
3. รันไฟล์ที่พบทั้งไฟล์ตั้งแต่บนลงล่าง
4. เก็บออบเจกต์โมดูลไว้ในแคชและผูกกับชื่อ

จากขั้นตอนนี้มีข้อสรุปเชิงปฏิบัติสองข้อ การ import โมดูลเดิมซ้ำแทบไม่เสียเวลา เพราะโค้ดไม่ถูกรันรอบที่สอง และโค้ดระดับบนสุดของโมดูลทั้งหมด รวมถึง `print` และการคำนวณหนัก จะทำงานทันทีที่ถูก import

```
import sys
for p in sys.path:
    print(p)
print("math" in sys.modules)
```

### if __name__ == "__main__"

เนื่องจากการ import รันไฟล์ จึงต้องมีวิธีแยกระหว่าง "ถูกรันเป็นสคริปต์" กับ "ถูก import" ตัวแปร `__name__` ทำหน้าที่นี้ เมื่อรันไฟล์โดยตรงค่าจะเป็น `"__main__"` และเมื่อถูก import ค่าจะเป็นชื่อโมดูล

```
# categories.py
def guess(title: str) -> str:
    return "อาหาร" if "คาเฟ่" in title.lower() else "อื่น ๆ"

if __name__ == "__main__":
    print(guess("คาเฟ่ใกล้บ้าน"))   # จะทำงานเฉพาะเมื่อรัน python categories.py
```

สำหรับแพ็กเกจมีอีกทางเลือกคือใส่ไฟล์ `__main__.py` ไว้ในแพ็กเกจ แล้วรันด้วย `python -m expenses`

## โมดูลมาตรฐานของ Python

Python มาพร้อมกับ "แบตเตอรี่" คือโมดูลในตัวหลายร้อยตัวที่ใช้ได้ทันทีโดยไม่ต้องติดตั้ง ตัวที่ใช้บ่อยได้แก่:

| โมดูล | ใช้ทำอะไร |
| --- | --- |

| `os`, `sys`, `pathlib`, `shutil` | ระบบ พาธ และไฟล์ |

| `json`, `csv`, `pickle` | รูปแบบข้อมูล |

| `datetime`, `time`, `calendar` | วันที่และเวลา |

| `math`, `random`, `statistics`, `fractions` | ตัวเลข |

| `re`, `string` | การจัดการข้อความ |

| `collections`, `itertools`, `functools` | โครงสร้างข้อมูลและตัววนซ้ำ |

| `logging`, `argparse`, `subprocess` | บันทึกล็อก อาร์กิวเมนต์บรรทัดคำสั่ง และรันโปรแกรมอื่น |

| `asyncio`, `threading` | อะซิงโครนัสและเธรด |

รายชื่อโมดูลในไลบรารีมาตรฐานทั้งหมดเก็บไว้ใน `sys.stdlib_module_names` (Python 3.10 ขึ้นไป) และดูว่าโมดูลมีอะไรบ้างได้ด้วย `dir(math)` และ `help(math)`

## สร้างแพ็กเกจของตัวเอง: โครงสร้างโปรเจกต์

เมื่อโมดูลมีจำนวนมากขึ้น ให้รวบรวมไว้เป็นแพ็กเกจ โครงสร้างที่นิยมคือ src-layout:

```
expenses-project/
├── pyproject.toml
└── src/
    └── expenses/
        ├── __init__.py
        ├── cli.py
        ├── parsing.py
        ├── categories.py
        └── report.py
```

ภายในแพ็กเกจ โมดูลจะ import กันโดยใช้ชื่อแพ็กเกจ (`from expenses.categories import guess`) หรือแบบสัมพันธ์ (`from .categories import guess`) การ import แบบสัมพันธ์ใช้ได้เฉพาะภายในแพ็กเกจ ไม่ใช้ได้กับไฟล์ที่ถูกรันโดยตรงเป็นสคริปต์

## pip ใน Python: ติดตั้งไลบรารี

ไลบรารีภายนอกอยู่ในคลัง PyPI และติดตั้งด้วย pip ซึ่งมาพร้อมกับ Python วิธีที่ปลอดภัยที่สุดคือเรียกผ่านตัวแปลภาษาโดยตรง เพื่อให้ไลบรารีถูกติดตั้งลงใน Python ตัวเดียวกับที่คุณใช้รันโค้ด

```
python -m pip install requests          # ติดตั้ง
python -m pip install "pandas>=2.3"     # ระบุเวอร์ชันขั้นต่ำ
python -m pip install --upgrade requests
python -m pip uninstall requests        # ถอนการติดตั้ง
python -m pip list                      # รายการแพ็กเกจที่ติดตั้งอยู่
python -m pip show requests             # เวอร์ชันและ dependency
python -m pip freeze > requirements.txt
python -m pip install -r requirements.txt
```

บน Windows มักใช้ตัวเรียก `py` แทน `python` หากไม่พบ pip ให้ติดตั้งด้วยโมดูลในตัว: `python -m ensurepip --upgrade`

### ไลบรารี Python ยอดนิยม

แพ็กเกจบน PyPI มีหลายแสนตัว แต่โปรเจกต์ส่วนใหญ่ใช้รายการสั้น ๆ ชุดเดียวกัน ควรเริ่มจากรายการนี้แทนการค้นหาของแปลก ๆ

| งาน | ไลบรารี |
| --- | --- |

| คำขอ HTTP และ API | `requests`, `httpx` |

| วิเคราะห์ข้อมูลและตาราง | `pandas`, `numpy` |

| กราฟ | `matplotlib` |

| เว็บเซอร์วิส | `fastapi`, `django`, `flask` |

| การทดสอบ | `pytest` |

| รูปภาพ | `pillow` (import เป็น `PIL`) |

สังเกตว่าชื่อที่ใช้ติดตั้งกับชื่อที่ใช้ import ไม่จำเป็นต้องตรงกัน เช่น ติดตั้งด้วย `pip install pillow` แต่เขียน `from PIL import Image` หรือติดตั้ง `opencv-python` แล้ว import เป็น `cv2` หาก `import` ล้มเหลวทันทีหลังติดตั้ง ให้ตรวจในเอกสารของแพ็กเกจว่าใช้ชื่อใดในการ import

### การติดตั้งไลบรารีบน Linux

บน Debian และ Ubuntu รุ่นใหม่ การรัน `pip install` ลงใน Python ของระบบจะจบด้วยข้อผิดพลาด `externally-managed-environment` (PEP 668) นี่คือการป้องกัน เพราะ Python ของระบบเป็นของตัวจัดการแพ็กเกจของ OS และ pip ไม่ควรทำให้มันเสีย วิธีที่ถูกต้องไม่ใช่ `--break-system-packages` แต่คือการใช้สภาพแวดล้อมเสมือน

## venv ใน Python: สภาพแวดล้อมเสมือน

สภาพแวดล้อมเสมือนคือโฟลเดอร์แยกที่มีตัวแปลภาษาของตัวเอง (เป็นลิงก์ไปยัง Python) และ site-packages ของตัวเอง ไลบรารีที่ติดตั้งในนั้นจะมองเห็นได้เฉพาะโปรเจกต์นั้น ดังนั้นโปรเจกต์หนึ่งใช้ Django 4 ได้ ส่วนอีกโปรเจกต์ใช้ Django 5 โดยไม่ตีกัน

โมดูล `venv` มาพร้อม Python อยู่แล้ว ไม่ต้องติดตั้งแยก (บน Debian/Ubuntu บางครั้งต้องติดตั้งแพ็กเกจระบบ `python3-venv` ก่อน)

```
# สร้างสภาพแวดล้อมในโฟลเดอร์ .venv
python -m venv .venv

# เปิดใช้งาน: Linux และ macOS
source .venv/bin/activate
# เปิดใช้งาน: Windows PowerShell
.venv\Scripts\Activate.ps1

# ตอนนี้ pip จะติดตั้งแพ็กเกจลงใน .venv
python -m pip install requests
python -c "import sys; print(sys.prefix)"

# ออกจากสภาพแวดล้อม
deactivate
```

หลังเปิดใช้งาน หน้าบรรทัดในเทอร์มินัลจะแสดง `(.venv)` ไม่ควรนำโฟลเดอร์สภาพแวดล้อมเข้า git (เพิ่ม `.venv/` ใน `.gitignore`) และไม่ควรย้ายไปเครื่องอื่น ให้เก็บรายการ dependency ไว้แทนแล้วสร้างสภาพแวดล้อมใหม่ หาก PowerShell ไม่ยอมรันสคริปต์เปิดใช้งาน ให้อนุญาตสำหรับผู้ใช้: `Set-ExecutionPolicy -Scope CurrentUser RemoteSigned`

### uv และ pyproject.toml

โปรเจกต์สมัยใหม่กำหนด dependency ไว้ในไฟล์ `pyproject.toml` ตัวจัดการ uv ที่ใช้ในคอร์สนี้รวมการสร้างสภาพแวดล้อมและการติดตั้งเข้าด้วยกัน `uv add pandas` จะดาวน์โหลดแพ็กเกจ เขียนลงใน `pyproject.toml` และล็อกเวอร์ชันที่แน่นอนไว้ใน `uv.lock` เพื่อนร่วมทีมที่โคลนโปรเจกต์ลงมาสั่ง `uv sync` ก็จะได้สภาพแวดล้อมที่เหมือนกันทุกประการ

```
[project]
name = "expenses"
version = "0.1.0"
requires-python = ">=3.15"
dependencies = [
    "pandas>=2.3",
]

[project.scripts]
expenses = "expenses.cli:main"
```

## lazy import ใน Python 3.15 (PEP 810)

ของใหม่หลักใน Python 3.15 ที่เกี่ยวกับหัวข้อนี้คือคีย์เวิร์ด `lazy` ไลบรารีขนาดใหญ่โหลดนาน จากการทดสอบในคอร์สบนอิมเมจ `python:3.15-slim` การ `import pandas` ปกติใช้เวลาราว 415 มิลลิวินาที ส่วน `lazy import pandas` ใช้เวลาราว 13 มิลลิวินาที สำหรับยูทิลิตีบรรทัดคำสั่งที่รันเพื่อดู `--help` ความต่างนี้มากมาย

```
lazy import pandas as pd
lazy from expenses.report import build_report

def main() -> None:
    ...
    df = pd.read_csv(path)   # โหลด pandas จริงตรงนี้
```

แทนที่จะโหลดโมดูลทันที ชื่อจะถูกผูกกับออบเจกต์พร็อกซี (`types.LazyImportType`) และการ import จริงเกิดขึ้นเมื่อมีการเข้าถึงชื่อนั้นครั้งแรก หากไม่มีการเข้าถึงเลย ก็ไม่เสียเวลาเลย

### ข้อจำกัดของ lazy import

- ใช้ได้เฉพาะระดับบนสุดของโมดูล ภายในฟังก์ชัน คลาส หรือ `try/except` จะเกิด `SyntaxError`
- ห้ามใช้ `lazy from x import *`
- `ModuleNotFoundError` จะปรากฏตอนใช้งานครั้งแรก ไม่ใช่ตอนเริ่มโปรแกรม
- ผลข้างเคียงของโมดูล (เช่น การลงทะเบียนปลั๊กอิน) จะเลื่อนไปตามเวลาที่โหลดจริง

หากต้องการให้ทุก import เป็น lazy พร้อมกันใช้แฟล็ก `-X lazy_imports=all` หรือตัวแปรสภาพแวดล้อม `PYTHON_LAZY_IMPORTS=all` ส่วนการยกเว้นบางโมดูลทำได้ด้วย `sys.set_lazy_imports_filter()` สำหรับโค้ดที่ต้องรันบนเวอร์ชันเก่าด้วย มีตัวแปรระดับโมดูล `__lazy_modules__ = ["pandas"]` Python 3.15 จะทำให้โมดูลที่ระบุเป็น lazy ส่วนเวอร์ชันเก่าจะ import ตามปกติ ดูว่าเวลาหายไปตอนเริ่มโปรแกรมที่ไหนได้ด้วย `python -X importtime -c "import pandas"` ประโยชน์จะเห็นชัดที่สุดในโปรแกรมที่รันบ่อยแต่แต่ละครั้งสั้น ๆ ส่วนเซิร์ฟเวอร์ที่ทำงานต่อเนื่องแทบไม่ได้ประโยชน์

## ข้อผิดพลาดที่พบบ่อย

- `ModuleNotFoundError: No module named 'requests'` แสดงว่าไลบรารีถูกติดตั้งลงใน Python อีกตัว หรือยังไม่ได้เปิดใช้งานสภาพแวดล้อม ตรวจด้วย `python -m pip show requests` โดยใช้ตัวแปลภาษาตัวเดียวกัน
- ตั้งชื่อไฟล์ของตัวเองว่า `json.py` `random.py` หรือ `csv.py` โฟลเดอร์ของสคริปต์จะอยู่หน้าสุดใน `sys.path` และบดบังโมดูลมาตรฐาน Python 3.13 ขึ้นไปจะแนะนำให้เปลี่ยนชื่อในข้อความผิดพลาด ให้เปลี่ยนชื่อไฟล์และลบโฟลเดอร์ `__pycache__`
- import วนกลับ (circular import) เช่น `a.py` import `b.py` และ `b.py` import `a.py` ให้แยกส่วนที่ใช้ร่วมกันออกเป็นโมดูลที่สาม หรือ import ไว้ภายในฟังก์ชัน
- รัน pip ภายในตัวแปลภาษา Python (`>>> pip install`) จะได้ `SyntaxError` คำสั่ง pip ต้องรันในเทอร์มินัล

## สรุป

โมดูลคือไฟล์ แพ็กเกจคือโฟลเดอร์ของโมดูล และไลบรารีคือชุดที่พร้อมใช้ `import` จะค้นหาโมดูลในแคชและใน `sys.path` แล้วรันหนึ่งครั้ง ไลบรารีภายนอกควรติดตั้งด้วย pip หรือ uv ลงในสภาพแวดล้อม venv เท่านั้น เพื่อไม่ให้โปรเจกต์รบกวนกันและทำซ้ำได้ง่าย หัวข้อนี้ถูกสอนแบบลงมือทำในคอร์ส Python โดยโปรแกรมติดตามค่าใช้จ่ายจะถูกแปลงเป็นแพ็กเกจที่มี dependency และเริ่มทำงานได้เร็วขึ้นด้วย lazy import

## FAQ

**ต้องสร้าง venv สำหรับทุกโปรเจกต์หรือไม่?**

ควรทำ เพราะแต่ละโปรเจกต์มีเวอร์ชันไลบรารีของตัวเอง การแยกสภาพแวดล้อมป้องกันความขัดแย้งและไม่ทำให้ Python ของระบบรกไปด้วยแพ็กเกจ

**pip ต่างจาก uv อย่างไร?**

pip ติดตั้งแพ็กเกจอย่างเดียว ส่วน uv ยังสร้างสภาพแวดล้อม บันทึก dependency ลงใน pyproject.toml และล็อกเวอร์ชันที่แน่นอนในไฟล์ lock โดยเร็วกว่ามาก

**จะดูรายการไลบรารี Python ที่ติดตั้งแล้วได้อย่างไร?**

รัน python -m pip list ในสภาพแวดล้อมที่ต้องการ ส่วน pip freeze จะแสดงรายการเดียวกันในรูปแบบของ requirements.txt

**โมดูลที่ติดตั้งแล้วถูกเก็บไว้ที่ไหน?**

ในโฟลเดอร์ site-packages ของสภาพแวดล้อมนั้น ดูพาธของโมดูลได้จากแอตทริบิวต์ __file__ เช่น import requests แล้ว print(requests.__file__)

**ย้ายโฟลเดอร์ venv ไปเครื่องอื่นได้หรือไม่?**

ไม่ได้ สภาพแวดล้อมผูกกับพาธและตัวแปลภาษาของเครื่องนั้น ให้ย้ายรายการ dependency แทนแล้วสร้างสภาพแวดล้อมใหม่


---
Source: Skillaria.Top (https://skillaria.top)
