Python สำหรับมือใหม่ #3: ฟังก์ชัน List และ Dictionary
·~7 นาที
แก้ไขล่าสุดเมื่อ

Python สำหรับมือใหม่ #3: ฟังก์ชัน List และ Dictionary


ใน Part 2 เราเขียนโค้ดจริงกันแล้วเนอะ ทั้งตัวแปร if/else แล้วก็ for/while แต่เชื่อไหมครับ พอโค้ดเริ่มยาวขึ้นอีกนิด ปัญหาเดิมจะมาเลยคือ ก๊อปโค้ดซ้ำไปซ้ำมา จะแก้ทีก็ต้องไล่แก้หลายที่ ลืมที่เดียวก็พังแล้ว

Part นี้เลยจะชวนมารู้จักของ 3 อย่างที่จะช่วยชีวิตเราได้เยอะมากครับ คือ ฟังก์ชัน (function) เอาไว้เก็บโค้ดเป็นก้อนเรียกใช้ซ้ำได้ list เอาไว้เก็บของหลายๆ ชิ้น แล้วก็ dict เอาไว้เก็บข้อมูลแบบมีชื่อกำกับ สุดท้ายเราจะเอาทั้ง 3 อย่างมาทำโปรแกรมตัดเกรดทั้งห้องกัน


ฟังก์ชัน (Function) คืออะไร?

พูดง่ายๆ ฟังก์ชันก็เหมือน กล่องเก็บโค้ด ครับ เราเอาโค้ดยัดใส่กล่อง ตั้งชื่อให้มัน เวลาจะใช้ก็แค่เรียกชื่อ ไม่ต้องเขียนซ้ำ

วิธีสร้างก็ไม่ยาก ใช้คำว่า def แล้วตามด้วยชื่อ วงเล็บ แล้วก็ : จากนั้นก็ indent เข้าไปเหมือนตอนเขียน if ใน Part 2 เลยครับ

indent คืออะไร? แวะอธิบายหน่อย

indent ก็คือ การย่อหน้าหรือเคาะเว้นข้างหน้า นี่แหละครับ ปกติเรากด Tab หรือเคาะ Space 4 ที Python จะใช้ตรงนี้แหละบอกว่า “โค้ดบรรทัดนี้อยู่ข้างในกล่องไหน”

ภาษาอื่นเขาใช้ปีกกา { } ครอบ แต่ Python ใช้ช่องว่างแทนครับ บรรทัดไหนย่อหน้าเท่ากันก็แปลว่าอยู่กลุ่มเดียวกัน บรรทัดไหนไม่ย่อก็คือหลุดออกมาข้างนอกแล้ว

def greet():
print("ข้างใน") # ย่อหน้า 4 ช่อง = อยู่ในฟังก์ชัน
print("ข้างในเหมือนกัน") # ย่อหน้าเท่ากัน = อยู่กลุ่มเดียวกัน
print("ข้างนอก") # ไม่ย่อ = หลุดออกมาข้างนอกแล้ว
ผลลัพธ์
ข้างใน
ข้างในเหมือนกัน
ข้างนอก

มือใหม่โดนกันบ่อย 2 แบบครับ:

  • ลืมย่อ หลัง def ...: แล้วเขียนชิดซ้ายเลย อันนี้ได้ IndentationError ทันที Python มันจะบอกว่า expected an indented block
  • ย่อไม่เท่ากัน บรรทัดนึง 4 ช่อง อีกบรรทัด 2 ช่อง หรือปน Tab กับ Space มั่วๆ กัน อันนี้ก็พังเหมือนกันครับ ตั้งใน VS Code ให้กด Tab แล้วแปลงเป็น 4 Space จะสบายสุด
def greet():
print("แบบนี้พังนะ ลืมย่อ") # IndentationError

จำง่ายๆ ครับ เจอ : เมื่อไหร่ บรรทัดต่อไปต้องย่อเข้าไปก่อนเสมอ ไม่ว่าจะเป็น def if หรือ for ที่เราเจอใน Part 2 ก็กฎเดียวกันหมดเลย

def ชื่อฟังก์ชัน():
# โค้ดตรงนี้ยังไม่ทำงานนะ แค่บอกไว้ก่อนว่ากล่องนี้มีอะไรบ้าง

ลองดูแบบไม่ใช้ฟังก์ชันก่อน ถ้าอยากทักทาย 3 รอบก็ต้องเขียน 3 บรรทัด:

print("สวัสดีครับ")
print("สวัสดีครับ")
print("สวัสดีครับ")

วันไหนอยากเปลี่ยนเป็น “สวัสดีตอนเช้า” ก็ต้องไล่แก้ทั้ง 3 ที่ ลืมสักที่ก็ตลกเลย แต่ถ้าใส่กล่องไว้ แก้ที่เดียวจบครับ:

def greet():
print("สวัสดีครับ")
greet() # เรียกใช้รอบแรก
greet() # เรียกใช้อีกรอบ
ผลลัพธ์
สวัสดีครับ
สวัสดีครับ

มี 2 อย่างที่อยากให้จำไว้ครับ:

  • ตอน def คือแค่ บอกไว้ก่อน โค้ดข้างในยังไม่ทำงานนะ
  • มันจะทำงานก็ตอนเรา เรียก นี่แหละ ต้องมีวงเล็บด้วยนะ greet() ถ้าเขียนแค่ greet เฉยๆ Python มันจะไม่เรียกให้ครับ มันนึกว่าเราชี้ไปที่กล่องเฉยๆ

แล้วทำไมต้องใช้ล่ะ? เอาจริงๆ มีแค่ 3 ข้อเองครับที่เราจะรู้สึกได้ทันที:

  1. เขียนรอบเดียวพอ อยากใช้กี่รอบก็เรียกเอา
  2. แก้ที่เดียว เปลี่ยนในฟังก์ชัน ทุกที่ที่เรียกก็ได้ของใหม่หมด
  3. อ่านง่าย ตั้งชื่อดีๆ อย่าง get_grade คนมาอ่านก็เดาได้เลยว่าทำอะไร ไม่ต้องไล่ดูโค้ดข้างในทั้งหมด

parameter กับ argument: ส่งค่าเข้าไปให้ฟังก์ชันหน่อย

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

ตรงนี้มีศัพท์ 2 คำที่ชอบสลับกันครับ parameter คือตัวรอรับตอนเราสร้างฟังก์ชัน ส่วน argument คือค่าที่เราส่งเข้าไปตอนเรียกใช้ จำแบบนี้ก็ได้ครับ ตอนสร้างเรียก parameter ตอนใช้เรียก argument

def greet(name): # name ตรงนี้คือ parameter รอรับค่า
print("สวัสดีครับ", name)
greet("lynn") # "lynn" ตรงนี้คือ argument ส่งเข้าไปจริงๆ
greet("มิ้น")
ผลลัพธ์
สวัสดีครับ lynn
สวัสดีครับ มิ้น

จะรับหลายค่าก็ได้ครับ แค่คั่นด้วยจุลภาค , แต่ต้องระวังเรื่องลำดับนิดนึง ค่าแรกจะเข้าตัวแรก ค่าที่สองเข้าตัวที่สอง สลับกันเมื่อไหร่งงทันที:

def introduce(name, age):
print(f"{name} อายุ {age} ปี")
introduce("lynn", 25)
introduce(25, "lynn") # สลับกันดูสิ ความหมายเพี้ยนเลย
ผลลัพธ์
lynn อายุ 25 ปี
25 อายุ lynn ปี

ถ้ากลัวสลับก็ระบุชื่อไปเลยก็ได้ครับ เรียกว่า keyword argument สลับลำดับได้ไม่เป็นไร:

introduce(age=25, name="lynn")

return: อยากได้ค่ากลับไปใช้ต่อ

ตรงนี้เป็นจุดที่หลายคนงงสุดครับ ระหว่าง print กับ return

print คือแค่ โชว์บนจอ ให้เราดูเฉยๆ ส่วน return คือ ส่งค่ากลับ ให้คนเรียกเอาไปทำอย่างอื่นต่อได้ แล้วพอเจอ return ปุ๊บ ฟังก์ชันจะหยุดตรงนั้นเลยครับ ไม่ทำต่อแล้ว

def add(a, b):
return a + b # ส่งผลบวกกลับไปให้คนเรียก
result = add(5, 3) # result ก็จะได้ 8
print(result)
print(result + 10) # เอาไปบวกต่อได้สบายๆ
ผลลัพธ์
8
18

ลองเทียบกับแบบที่ใช้แค่ print ดูครับ จะเห็นเลยว่าต่างกัน:

def add_wrong(a, b):
print(a + b) # แค่โชว์ ไม่ได้ส่งอะไรกลับ
x = add_wrong(5, 3)
print(x) # จะได้ None เพราะฟังก์ชันไม่ได้คืนอะไรให้เลย
# print(x + 10) # บรรทัดนี้พังนะ None + 10 ทำไม่ได้

ทริคจำง่ายๆ ครับ อยากโชว์ใช้ print อยากได้ค่าไปใช้ต่อใช้ return

แล้วก็มีทริคเล็กๆ ที่ใช้บ่อยครับ คือเอา return มาตัดจบเร็วๆ จะได้ไม่ต้องซ้อน if เยอะ:

def is_adult(age):
if age < 18:
return False # เจอเด็กก็จบเลย ไม่ต้องมี else
return True

ค่า default กับ scope

default parameter: ตั้งค่าเผื่อไว้กันลืม

บางที parameter มันมีค่าที่เราใช้บ่อยๆ อยู่แล้ว เราตั้งเผื่อไว้ได้เลยครับ ถ้าคนเรียกไม่ส่งมา Python ก็ใช้ค่านี้ให้เอง สะดวกดี

def greet(name="เพื่อน"):
print("สวัสดีครับ", name)
greet() # ไม่ส่งอะไรมา ก็ได้ "เพื่อน"
greet("lynn") # ส่งมาก็ใช้ค่าที่ส่งมา
ผลลัพธ์
สวัสดีครับ เพื่อน
สวัสดีครับ lynn

มีที่ต้องระวังนิดเดียวครับ ตัวที่มี default ต้องอยู่หลังตัวที่ไม่มี ไม่งั้น Python มันงง ไม่รู้จะจับคู่ยังไง

def greet(greeting, name="เพื่อน"): # แบบนี้ถูก
print(greeting, name)
# def greet(name="เพื่อน", greeting): # แบบนี้พังนะ SyntaxError

scope: ตัวแปรแต่ละตัวมีที่ของมัน

scope พูดง่ายๆ ก็คือ “ตัวแปรตัวนี้ใช้ได้ถึงตรงไหน” ครับ ตัวที่สร้างข้างในฟังก์ชันจะใช้ได้แค่ข้างในนั้น เรียกว่า local ส่วนตัวที่สร้างข้างนอกจะใช้ได้ทุกที่ เรียกว่า global ลองดูตัวอย่างนี้ครับ:

name = "lynn" # สร้างข้างนอก ใช้ได้ทุกที่
def change():
name = "มิ้น" # ถึงชื่อจะซ้ำ แต่คนละตัวกับข้างนอกนะ ใช้ได้แค่ในนี้
print("ในฟังก์ชัน:", name)
change()
print("นอกฟังก์ชัน:", name)
ผลลัพธ์
ในฟังก์ชัน: มิ้น
นอกฟังก์ชัน: lynn

ทำไมต้องแยกแบบนี้? ลองคิดดูครับ ถ้าทุกฟังก์ชันแก้ตัวแปรข้างนอกได้หมด โปรแกรมใหญ่ๆ ขึ้นมาจะไล่ไม่เจอเลยว่าใครแอบมาเปลี่ยนค่าตอนไหน บั๊กกระจายแน่นอน

เอาแบบที่ผมแนะนำสำหรับตอนนี้นะครับ จำแค่นี้พอ:

  • อยากเอาค่าข้างนอกเข้าไปใช้ในฟังก์ชัน ส่งผ่าน parameter ดีกว่าดึงมาใช้ตรงๆ
  • อยากได้ผลออกมาข้างนอก ส่งผ่าน return ดีกว่าสร้างตัวแปรข้างนอกในฟังก์ชัน
  • เขียนแบบนี้โค้ดจะสะอาด เทสต์ง่าย ย้ายไปใช้ที่อื่นก็ง่ายครับ

List — เก็บของหลายชิ้นตามลำดับ

ก่อนหน้านี้ตัวแปรนึงเก็บได้ค่าเดียวใช่ไหมครับ แต่ของจริงเราต้องเก็บหลายค่า เช่น คะแนนทั้งห้อง รายชื่อผลไม้ list นี่แหละคำตอบ ครอบด้วย [ ] สมาชิกคั่นด้วยจุลภาค แล้วเวลาจะหยิบใช้ก็จิ้มด้วย index ซึ่งเริ่มนับจาก 0 นะครับ ไม่ใช่ 1 ตรงนี้โดนกันทุกคน ผมก็โดนมาแล้ว

scores = [85, 70, 92]
print(scores[0]) # ตัวแรกคือ index 0 นะ
print(scores[1]) # ตัวที่สองคือ index 1
print(len(scores)) # len() เอาไว้นับว่ามีกี่ตัว
ผลลัพธ์
85
70
3

list มันยืดหยุ่นครับ เพิ่มทีหลัง ลบทีหลังได้ มีเครื่องมือติดมาให้ใช้เลย:

scores = [85, 70, 92]
scores.append(60) # ต่อท้ายไปอีกตัว
print(scores)
scores.remove(70) # ลบค่าที่เจอตัวแรกออก
print(scores)
print(92 in scores) # เช็คว่ามีค่านี้ไหม ได้ True/False
ผลลัพธ์
[85, 70, 92, 60]
[85, 92, 60]
True

แล้วของคู่กันกับ list ก็คือ for ที่เราเรียนใน Part 2 นั่นแหละครับ วน list ได้ตรงๆ เลย ไม่ต้องมานั่งนับ index เองให้ปวดหัว:

scores = [85, 70, 92]
for s in scores:
print("คะแนน:", s)
ผลลัพธ์
คะแนน: 85
คะแนน: 70
คะแนน: 92

จุดที่พลาดกันบ่อยๆ:

  • มี 3 ตัว (index 0-2) แต่ดันไปจิ้ม scores[3] อันนี้ได้ IndexError แน่นอนครับ ทางแก้คือเช็ค len() ก่อน หรือวนด้วย for แทนไปเลยง่ายกว่า
  • append มันเพิ่มทีละตัวนะครับ ถ้าอยากเพิ่มหลายตัวใช้ extend หรือเอา + ต่อกันก็ได้ เช่น scores + [60, 50]

Dict — เก็บแบบมีชื่อกำกับ อ่านรู้เรื่อง

ถ้า list เหมาะกับ “ของหลายชิ้นแบบเดียวกันเรียงกัน” dict จะเหมาะกับ “ข้อมูลชุดเดียวกันแต่มีหลายช่อง” ครับ เช่น นักเรียน 1 คนมีทั้งชื่อทั้งคะแนน ถ้าเก็บเป็น ["lynn", 85] คนมาอ่านก็งงว่าอะไรคืออะไร สู้เก็บเป็น {"name": "lynn", "score": 85} ไม่ได้ เห็นปุ๊บรู้ปั๊บเลย

dict ครอบด้วย { } ข้างในเป็นคู่ key: value เวลาจะใช้ก็เรียกด้วย key ไม่ต้องสนลำดับครับ:

student = {"name": "lynn", "score": 85}
print(student["name"]) # เรียกด้วย key
print(student["score"])
student["grade"] = "A" # ยังไม่มี key นี้ก็คือเพิ่มใหม่ มีแล้วก็คืออัปเดต
student["score"] = 90
print(student)
ผลลัพธ์
lynn
85
{'name': 'lynn', 'score': 85, 'grade': 'A'}

ปัญหาคลาสสิกเลยคือไปอ่าน key ที่ไม่มี โปรแกรมจะพังด้วย KeyError ครับ กันไว้ได้ 2 วิธี:

student = {"name": "lynn", "score": 85}
# วิธีที่ 1: ใช้ .get() ถ้าไม่มีก็ได้ None ไม่พัง หรือใส่ค่ากันเหนียวเองก็ได้
print(student.get("grade"))
print(student.get("grade", "ยังไม่ตัดเกรด"))
# วิธีที่ 2: วนด้วย .items() ได้ทั้ง key ทั้ง value พร้อมกันเลย
for key, value in student.items():
print(key, "=", value)
ผลลัพธ์
None
ยังไม่ตัดเกรด
name = lynn
score = 85

สรุปสั้นๆ ให้จำคู่กันครับ:

  • ของหลายชิ้นแบบเดียวกัน เรียงๆ กัน → ใช้ list เช่น [85, 70, 92]
  • ข้อมูลชุดเดียวมีหลายช่อง แต่ละช่องมีความหมาย → ใช้ dict เช่น {"name": "lynn", "score": 85}
  • มีหลายชุด แต่ละชุดมีหลายช่อง → เอา list ครอบ dict ซึ่งเดี๋ยวเราจะทำกันในตัวอย่างสุดท้ายพอดีเลย

ลองรวมกัน: โปรแกรมตัดเกรดทั้งห้อง

ได้เวลารวมร่างครับ เอาทุกอย่างมาใช้ด้วยกัน — list เก็บนักเรียนหลายคน dict เก็บข้อมูลแต่ละคน function เก็บเกณฑ์ตัดเกรดไว้ที่เดียว:

def get_grade(score):
# ฟังก์ชันนี้มีหน้าที่เดียว: รับคะแนนมา คืนเกรดกลับไป
if score >= 80:
return "A"
elif score >= 70:
return "B"
elif score >= 60:
return "C"
else:
return "F"
students = [
{"name": "lynn", "score": 85},
{"name": "มิ้น", "score": 72},
{"name": "โอม", "score": 55},
]
for s in students: # s คือข้อมูลนักเรียนทีละคน
grade = get_grade(s["score"]) # ดึงคะแนนส่งเข้าไป ได้เกรดกลับมา
print(f"{s['name']} ได้ {s['score']} คะแนน เกรด {grade}")
ผลลัพธ์
lynn ได้ 85 คะแนน เกรด A
มิ้น ได้ 72 คะแนน เกรด B
โอม ได้ 55 คะแนน เกรด F

มาไล่ดูทีละท่อนกันครับว่าทำอะไรบ้าง:

  1. def get_grade(score) ตรงนี้คือที่เก็บเกณฑ์ที่เดียวเลย วันหลังอยากเปลี่ยนเช่น A ต้อง 85 ก็แก้ตรงนี้ที่เดียวจบ ไม่ต้องไล่แก้ทั้งไฟล์
  2. students = [...] คือ list ของ dict ครับ แต่ละก้อนคือข้อมูลนักเรียน 1 คน อยากเพิ่มใครก็เติม dict ไปอีกก้อนแค่นั้นเอง
  3. for s in students วนทีละคน ไม่ต้องนับ index เองให้เมื่อย
  4. get_grade(s["score"]) ดึงคะแนนออกมาจาก dict ด้วย key "score" แล้วโยนเข้าฟังก์ชัน ได้เกรดกลับมาทาง return
  5. f"..." คือ f-string จาก Part 2 นั่นแหละ เอาตัวแปรมาแปะในข้อความด้วย { } ได้เลย สะดวกมาก

เทียบกับ Part 2 ที่ตัดได้ทีละคนด้วย if ยาวๆ ตอนนี้สั้นกว่า อ่านง่ายกว่า แถมรองรับทั้งห้องโดยไม่ต้องแก้โลจิกเลยครับ นี่แหละข้อดีของการแยกโค้ดเป็นฟังก์ชัน + เลือกที่เก็บข้อมูลให้ถูก

ลองเอาไปเล่นต่อเองดูนะครับ:

  • เพิ่มเพื่อนอีก 2 คนใน list แล้วลองรันดู
  • ลองเปลี่ยนเกณฑ์ใน get_grade แล้วดูว่าผลทั้งห้องเปลี่ยนตามโดยไม่ต้องแตะที่อื่นเลย
  • ลองเขียนฟังก์ชัน is_pass(score) ที่คืน True/False แล้วเอามาใช้คู่กับ get_grade ดูครับ

สรุป

Part 3 นี้เราได้ของสำคัญครบเลยครับ:

  • ฟังก์ชัน ด้วย def แยกโค้ดเป็นก้อน เขียนรอบเดียวเรียกได้หลายรอบ แก้ที่เดียวจบ
  • parameter/argument เอาไว้ส่งค่าเข้า return เอาไว้ส่งค่าออก อย่าสลับกับ print ที่แค่โชว์นะ
  • default parameter ตั้งค่าเผื่อไว้กันลืม scope ก็แค่จำว่าในกับนอกคนละตัวกัน ส่งผ่าน parameter/return จะสะอาดสุด
  • list เก็บของหลายชิ้นตามลำดับ จิ้มด้วย index เริ่มจาก 0 เพิ่มด้วย append วนด้วย for
  • dict เก็บแบบ key-value เรียกด้วย key ใช้ .get() กันพัง วนด้วย .items()
  • list ครอบ dict + function คือท่าที่ใช้จริงบ่อยมาก จำโปรแกรมตัดเกรดวันนี้ไว้ดีๆ ได้ใช้แน่ครับ

ใน Part 4 เราจะไปต่อกันที่ การอ่าน/เขียนไฟล์ กับดัก error ด้วย try/except ครับ จะได้เขียนโปรแกรมที่เซฟข้อมูลไว้ใช้รอบหน้าได้จริง ไม่ต้องมานั่งกรอกใหม่ทุกครั้งที่รัน

ติดตามต่อใน Part 4 นะครับ

บทความที่เกี่ยวข้อง