🇯🇵 日本語 | 🇺🇸 English | 🇪🇸 Español | 🇵🇹 Português | 🇹🇭 ไทย | 🇨🇳 中文

ไม่ต้องกังวลกับ KeyError อีกต่อไป! ยกระดับโค้ดของคุณด้วยโมดูล collections ของ Python

หากต้องการเรียกใช้ Python ผ่าน Command Prompt หรือ PowerShell บนคอมพิวเตอร์ของคุณ คุณจำเป็นต้องดาวน์โหลดและติดตั้ง Python ก่อน
หากคุณยังไม่ได้ติดตั้ง กรุณาดูบทความ การติดตั้ง Python และการตั้งค่าสภาพแวดล้อมการพัฒนา เพื่อทำการติดตั้ง Python

สวัสดีครับ! ผมคือคนเดียวกันกับที่สร้างเว็บไซต์ 2 แห่งได้ในไม่กี่เดือนโดยอาศัยความช่วยเหลือจาก AI ครับ

วันนี้ ผมจะมาอธิบายเกี่ยวกับโมดูล "collections" ที่มีประโยชน์สุดๆ ใน Python ซึ่งเป็นฟีเจอร์ที่ผมหวังว่าตัวเองจะรู้จักเร็วกว่านี้ในช่วงแรกๆ ของการเรียนเขียนโปรแกรมครับ ผมจะอธิบายแบบง่ายๆ พร้อมแบ่งปันเรื่องราวความผิดพลาดของตัวเองด้วยครับ

บทความนี้จะเป็นประโยชน์อย่างยิ่งสำหรับใครก็ตามที่เคยเจอปัญหา `KeyError` กับ dictionary (`dict`) หรือรู้สึกว่าการประมวลผล list (`list`) มันช้าๆ ครับ เมื่ออ่านบทความนี้จบ โค้ด Python ของคุณจะดูดีขึ้นอย่างแน่นอน! ผมพยายามเลี่ยงศัพท์เทคนิคและเตรียมโค้ดที่สามารถคัดลอกไปวางแล้วใช้งานได้ทันทีไว้เยอะเลย มาลองสัมผัสประสบการณ์ที่ทำให้โค้ด "ทำงานได้" ไปด้วยกันนะครับ!


ว่าแต่... โมดูล collections คืออะไรกันแน่?

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

ในกล่องเครื่องมือนี้มีเครื่องมือพิเศษมากมายที่ทำให้โครงสร้างข้อมูลพื้นฐานของ Python อย่าง `dict` (dictionary) และ `list` (list) ทรงพลังและใช้งานง่ายยิ่งขึ้น ในบทความนี้ ผมจะหยิบเครื่องมือ 2 ตัวที่ผมประทับใจเป็นพิเศษมาแนะนำ นั่นก็คือ `defaultdict` และ `deque` ครับ


`defaultdict`: กล่องวิเศษที่ช่วยป้องกันความผิดพลาดเล็กๆ น้อยๆ ใน Dictionary

ถ้าไม่มี `defaultdict` มันจะลำบากขนาดไหน... (จากเรื่องราวความล้มเหลวของผม)

คุณเคยใช้ dictionary (`dict`) แล้วเจอกับ error แบบนี้ไหมครับ?

# พยายามเข้าถึงคีย์ที่ไม่มีอยู่จริงใน dictionary ทั่วไป
word_counts = {}
# คีย์ 'apple' ยังไม่มีอยู่
word_counts['apple'] += 1 # error ตรงนี้!

# ผลการรัน
# Traceback (most recent call last):
#   File "<stdin>", line 1, in <module>
# KeyError: 'apple'

นี่คือ `KeyError` ซึ่งเป็นวิธีที่ Python บอกเราว่า "เฮ้! ไม่มีคีย์นี้อยู่ใน dictionary นะ" ตอนที่ผมสร้างเว็บไซต์ ผมก็เคยติดอยู่กับ error นี้เป็นชั่วโมงๆ ตอนที่พยายามประมวลผลข้อมูลที่ผู้ใช้ป้อนเข้ามาครับ

เราสามารถป้องกัน error นี้ได้โดยการใช้ `if` เพื่อตรวจสอบว่ามีคีย์อยู่หรือไม่ หรือใช้เมธอด `get()` แต่มันก็จะทำให้โค้ดของเรายาวขึ้นใช่ไหมล่ะครับ


และแล้วผู้ช่วยชีวิตก็ปรากฏตัว: `defaultdict`!

`defaultdict` คือสิ่งที่มาช่วยแก้ปัญหา `KeyError` นี้ได้ในทันทีครับ มันเป็น dictionary วิเศษที่ "จะสร้าง 'ค่าเริ่มต้น' ให้โดยอัตโนมัติเมื่อเราพยายามเข้าถึงคีย์ที่ไม่มีอยู่"

สิบปากว่าไม่เท่าตาเห็นครับ เรามาดูโค้ดกันเลยดีกว่า

from collections import defaultdict

# สร้าง defaultdict ที่มีค่าเริ่มต้นเป็น 0 โดยการระบุ int
word_counts = defaultdict(int)

# คีย์ 'apple' ยังไม่มีอยู่... แต่ไม่เป็นไร!
print(f"ก่อนเข้าถึง: {word_counts}") # ก่อนเข้าถึงจะยังว่างอยู่
word_counts['apple'] += 1 # ไม่เกิด error! มันจะสร้าง word_counts['apple'] = 0 ให้โดยอัตโนมัติ แล้วบวก 1
print(f"หลังเข้าถึง: {word_counts}")
print(f"จำนวน apple: {word_counts['apple']}")
print(f"จำนวน orange: {word_counts['orange']}") # 'orange' ก็ยังไม่มีอยู่ แต่เมื่อเข้าถึงจะคืนค่าเริ่มต้น 0 กลับมา

เจ๋งไปเลยใช่ไหมครับ? แค่เขียน `defaultdict(int)` ก็เหมือนกับเรากำลังบอกว่า "ถ้าคีย์ไหนไม่มีอยู่ ช่วยตั้งค่าเริ่มต้นให้เป็น `int()` หรือก็คือ `0` โดยอัตโนมัติด้วยนะ" ทำให้เราเริ่มคำนวณได้ทันทีโดยไม่ต้องกังวลเรื่อง `KeyError` เลยครับ
พฤติกรรมนี้มีอธิบายไว้ในเอกสารทางการของ Python ว่า "คืนค่าเริ่มต้นสำหรับคีย์ที่ไม่มีอยู่" ซึ่งทำให้มันเป็นฟีเจอร์ที่น่าเชื่อถือมากครับ


[คัดลอกไปใช้ได้เลย] ตัวอย่างการประยุกต์ใช้: การนับจำนวนคำในประโยค

`defaultdict` จะมีประโยชน์อย่างยิ่งเมื่อเราต้องการนับจำนวนของสิ่งต่างๆ ตัวอย่างเช่น เราสามารถนับจำนวนครั้งที่แต่ละคำปรากฏในประโยคได้อย่างง่ายดายแบบนี้ครับ:

from collections import defaultdict

sentence = "apple banana apple orange banana apple"
words = sentence.split() # แบ่งประโยคออกเป็น list ของคำต่างๆ

# defaultdict ที่มีค่าเริ่มต้นเป็น 0 โดยการระบุ int
word_counts = defaultdict(int)

for word in words:
  word_counts[word] += 1

# แสดงผลลัพธ์
for word, count in word_counts.items():
  print(f"'{word}': {count} ครั้ง")

# เรายังสามารถแปลงกลับเป็น dict ธรรมดาเพื่อตรวจสอบเนื้อหาได้
print(f"\nเนื้อหาสุดท้ายของ dictionary: {dict(word_counts)}")

ลองคัดลอกโค้ดนี้ไปรันดูสิครับ คุณจะเห็นว่ามันนับจำนวนคำได้อย่างถูกต้องโดยไม่มี `if` แม้แต่บรรทัดเดียว นั่นแหละคือพลังของ `defaultdict`!


`deque`: คิวความเร็วสูงที่มาแก้ปัญหาความ "ช้า" ของ list

การเพิ่มและลบข้อมูลจากด้านหน้าของ list จริงๆ แล้วไม่มีประสิทธิภาพ

ตัวต่อไปคือ `deque` (อ่านว่า "เด็ค") ครับ มันคล้ายกับ list (`list`) มาก แต่มีคุณสมบัติพิเศษคือ การเพิ่มและลบข้อมูลจากด้านหน้านั้นเร็วสุดๆ

ความจริงก็คือ `list` ของ Python นั้นเก่งเรื่องการเพิ่มข้อมูลต่อท้าย (ด้านขวา) ด้วย `append` แต่ไม่เก่งเรื่องการเพิ่มข้อมูลที่ด้านหน้า (ด้านซ้าย) ด้วย `insert(0, ...)` หรือการลบออกจากด้านหน้าด้วย `pop(0)` ครับ

นั่นก็เพราะว่าเมื่อเราเพิ่มหรือลบข้อมูลที่ด้านหน้าของ list ข้อมูลที่เหลือทั้งหมดจะต้องถูกขยับทีละตัว มันเหมือนกับการแทรกคิวหรือการที่คนหน้าสุดออกจากแถวไป แล้วทุกคนข้างหลังต้องขยับตาม ยิ่งมีข้อมูลเยอะ การ "ขยับ" นี้ก็จะยิ่งใช้เวลาและทำให้การประมวลผลช้าลงครับ


ถ้าเป็นการทำงานที่ปลายทั้งสองข้าง ปล่อยให้เป็นหน้าที่ของ `deque`!

`deque` ถูกสร้างขึ้นมาเพื่อแก้ปัญหานี้โดยเฉพาะครับ มันมีโครงสร้างภายในที่ชาญฉลาด (เรียกว่า doubly-linked list) ซึ่งทำให้การเพิ่มหรือลบข้อมูลจากปลายทั้งสองข้างทำได้ในทันที ไม่ว่าจะมีข้อมูลมากแค่ไหนก็ตาม

มาดูตัวอย่างในโค้ดกันครับ การทำงานที่คุณเคยทำกับ list สามารถทำได้ด้วย `deque` โดยใช้ชื่อเมธอดที่เข้าใจง่ายกว่าและเร็วกว่ามาก

from collections import deque

# สร้าง deque
tasks = deque(['task2', 'task3'])
print(f"สถานะเริ่มต้น: {tasks}")

# เพิ่มข้อมูลต่อท้าย (เหมือนกับ append ของ list)
tasks.append('task4')
print(f"เพิ่มต่อท้าย: {tasks}")

# เพิ่มข้อมูลที่ด้านหน้า (เร็วกว่า insert(0,...) ของ list)!
tasks.appendleft('task1')
print(f"เพิ่มที่ด้านหน้า: {tasks}")

# ลบข้อมูลจากด้านหน้า (เร็วกว่า pop(0) ของ list)!
first_task = tasks.popleft()
print(f"งานที่ถูกดึงออกมาจากด้านหน้า: {first_task}")
print(f"สถานะปัจจุบัน: {tasks}")

[คัดลอกไปใช้ได้เลย] ตัวอย่างการประยุกต์ใช้: รายการสินค้าที่ดูล่าสุด (พร้อมจำกัดจำนวนสูงสุด)

`deque` มีประโยชน์อย่างยิ่งสำหรับการจัดการงานหรือประวัติการใช้งาน ด้วยฟีเจอร์ `maxlen` คุณสามารถสร้าง list ที่ "เก็บข้อมูลล่าสุด N รายการไว้เสมอ" ได้อย่างง่ายดาย

ลองนึกภาพฟีเจอร์ "สินค้าที่ดูไปล่าสุด" บนเว็บไซต์ e-commerce ดูสิครับ

from collections import deque
import time

# สร้าง deque ที่เก็บประวัติได้สูงสุด 5 รายการ
history = deque(maxlen=5)

products = ['เสื้อยืด', 'รองเท้าผ้าใบ', 'หมวกแก๊ป', 'เสื้อฮู้ด', 'แจ็คเก็ต', 'กางเกงขาสั้น']

for product in products:
  print(f"กำลังดู '{product}'")
  history.append(product)
  print(f"ประวัติการดูล่าสุด: {list(history)}") # การแปลงเป็น list() ทำให้ดูง่ายขึ้น
  time.sleep(1) # รอ 1 วินาที

print("\n--- ประวัติการดูสุดท้าย (5 รายการล่าสุด) ---")
for item in history:
  print(item)

เมื่อคุณรันโค้ดนี้ คุณจะเห็นว่าเมื่อมีสินค้าใหม่ถูกเพิ่มเข้าไป สินค้าที่เก่าที่สุดจะถูกดันออกไปโดยอัตโนมัติ หากคุณจะทำสิ่งนี้ด้วย `list` คุณจะต้องมีการตรวจสอบเช่น `if len(list) > 5:` แต่ด้วย `deque(maxlen=N)` ก็ไม่จำเป็นต้องทำเช่นนั้น สะดวกใช่ไหมล่ะครับ!


[มาลองกัน!] โปรแกรมนับคำที่ทำงานได้บนเบราว์เซอร์ของคุณ

ผมได้เตรียมตัวอย่างเพื่อให้คุณได้สัมผัสกับความสะดวกสบายของ `defaultdict` ได้ทันทีบนเบราว์เซอร์ของคุณ คัดลอกโค้ด HTML ทั้งหมดด้านล่างไปวางในโปรแกรมแก้ไขข้อความ แล้วบันทึกเป็นไฟล์ชื่อ "test.html" จากนั้นเปิดไฟล์นั้นในเบราว์เซอร์ของคุณครับ

นี่ไม่ใช่โค้ด Python นะครับ แต่เป็นการจำลองแนวคิดของ `defaultdict` ที่ว่า "สร้างค่าเริ่มต้นให้โดยอัตโนมัติแม้ว่าคีย์จะไม่มีอยู่" โดยใช้ JavaScript ลองป้อนข้อความลงในกล่องข้อความแล้วกดปุ่มดูสิครับ!

<!DOCTYPE html>
<html lang="th">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>สาธิตการนับคำ</title>
  <style>
    body { font-family: sans-serif; background-color: #202124; color: #e8eaed; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; }
    .container { width: 90%; max-width: 600px; padding: 2rem; border: 1px solid #5f6368; border-radius: 8px; }
    h1 { color: #669df6; }
    textarea { width: 100%; height: 150px; background-color: #3c4043; color: #e8eaed; border: 1px solid #5f6368; border-radius: 4px; padding: 10px; font-size: 1rem; margin-bottom: 1rem; }
    button { background-color: #8ab4f8; color: #202124; border: none; padding: 10px 20px; border-radius: 4px; cursor: pointer; font-weight: bold; }
    button:hover { opacity: 0.9; }
    #result { margin-top: 1.5rem; background-color: #282a2d; padding: 1rem; border-radius: 4px; }
    pre { white-space: pre-wrap; word-wrap: break-word; }
  </style>
</head>
<body>
  <div class="container">
    <h1>โปรแกรมนับคำ</h1>
    <p>ป้อนข้อความในกล่องด้านล่างแล้วกดปุ่ม</p>
    <textarea id="text-input" placeholder="ป้อนข้อความที่นี่... (เช่น: apple banana apple orange)"></textarea>
    <button onclick="countWords()">นับจำนวนคำ</button>
    <div id="result">
      <pre>ผลลัพธ์จะแสดงที่นี่</pre>
    </div>
  </div>

  <script>
    function countWords() {
      const text = document.getElementById('text-input').value;
      const resultEl = document.getElementById('result');

      if (!text.trim()) {
        resultEl.innerHTML = '<pre>ยังไม่ได้ป้อนข้อความ</pre>';
        return;
      }

      // แบ่งเป็นคำด้วยช่องว่างหรือการขึ้นบรรทัดใหม่ และลบส่วนที่ว่างออก
      const words = text.toLowerCase().match(/\b(\w+)\b/g) || [];

      // จำลองการทำงานของ defaultdict(int)
      const counts = {}; 
      for (const word of words) {
        // หากคีย์ไม่มีอยู่ ให้ตั้งค่าเป็น 0 แล้วจึงเพิ่มค่า
        counts[word] = (counts[word] || 0) + 1;
      }

      // จัดรูปแบบและแสดงผลลัพธ์
      let resultText = '[ผลการนับ]\n';
      for (const [word, count] of Object.entries(counts)) {
        resultText += `"${word}": ${count} ครั้ง\n`;
      }
      
      resultEl.innerHTML = `<pre>${resultText}</pre>`;
    }
  </script>
</body>
</html>

ข้อควรระวังในการใช้ `collections`

สุดท้ายนี้ ผมขอพูดถึงข้อควรระวังเล็กๆ น้อยๆ ในการใช้เครื่องมือที่มีประโยชน์เหล่านี้ครับ


สรุป: เขียนโค้ดอย่างชาญฉลาดและยกระดับการเขียนโปรแกรมของคุณไปอีกขั้น

ในบทความนี้ เราได้แนะนำ `defaultdict` และ `deque` จากโมดูลมาตรฐาน "collections" ของ Python ซึ่งเป็นสองเครื่องมือที่มีประโยชน์อย่างยิ่งที่ควรเรียนรู้ตั้งแต่เนิ่นๆ ครับ

ตั้งแต่ผมได้รู้จักกับเครื่องมือเหล่านี้ โค้ดของผมก็ง่ายขึ้นและเวลาที่ใช้ในการแก้ error ก็ลดลงอย่างมากครับ ผมขอแนะนำให้คุณเริ่มจากการคัดลอกโค้ดในบทความนี้ไปลองรันดูเพื่อสัมผัสกับความสะดวกสบายด้วยตัวเอง แม้ว่าการใช้ `list` และ `dict` พื้นฐานให้คล่องแคล่วจะเป็นสิ่งสำคัญ แต่การใช้เครื่องมือที่มีประโยชน์อย่าง `collections` ในสถานการณ์ที่เหมาะสมจะช่วยให้คุณเขียนโค้ดที่มีประสิทธิภาพและอ่านง่ายขึ้นได้ครับ!

ขั้นตอนต่อไป

เมื่อคุณได้สัมผัสกับความสะดวกของโมดูล `collections` แล้ว ทำไมไม่ลองสร้างแอปพลิเคชันแบบ text-based ดูล่ะครับ? มาสนุกกับการสร้างสิ่งที่ใช้งานได้จริงโดยใช้โครงสร้างข้อมูลพื้นฐานกันเถอะ!

มาสร้างแอป To-Do List ด้วย Python กันเถอะ (แบบ Text-Based)