
ใน 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 ข้อเองครับที่เราจะรู้สึกได้ทันที:
- เขียนรอบเดียวพอ อยากใช้กี่รอบก็เรียกเอา
- แก้ที่เดียว เปลี่ยนในฟังก์ชัน ทุกที่ที่เรียกก็ได้ของใหม่หมด
- อ่านง่าย ตั้งชื่อดีๆ อย่าง
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 ก็จะได้ 8print(result)print(result + 10) # เอาไปบวกต่อได้สบายๆ818ลองเทียบกับแบบที่ใช้แค่ 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): # แบบนี้พังนะ SyntaxErrorscope: ตัวแปรแต่ละตัวมีที่ของมัน
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 1print(len(scores)) # len() เอาไว้นับว่ามีกี่ตัว85703list มันยืดหยุ่นครับ เพิ่มทีหลัง ลบทีหลังได้ มีเครื่องมือติดมาให้ใช้เลย:
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"]) # เรียกด้วย keyprint(student["score"])
student["grade"] = "A" # ยังไม่มี key นี้ก็คือเพิ่มใหม่ มีแล้วก็คืออัปเดตstudent["score"] = 90print(student)lynn85{'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 = lynnscore = 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มาไล่ดูทีละท่อนกันครับว่าทำอะไรบ้าง:
def get_grade(score)ตรงนี้คือที่เก็บเกณฑ์ที่เดียวเลย วันหลังอยากเปลี่ยนเช่น A ต้อง 85 ก็แก้ตรงนี้ที่เดียวจบ ไม่ต้องไล่แก้ทั้งไฟล์students = [...]คือ list ของ dict ครับ แต่ละก้อนคือข้อมูลนักเรียน 1 คน อยากเพิ่มใครก็เติม dict ไปอีกก้อนแค่นั้นเองfor s in studentsวนทีละคน ไม่ต้องนับ index เองให้เมื่อยget_grade(s["score"])ดึงคะแนนออกมาจาก dict ด้วย key"score"แล้วโยนเข้าฟังก์ชัน ได้เกรดกลับมาทางreturnf"..."คือ 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วนด้วยfordictเก็บแบบ key-value เรียกด้วย key ใช้.get()กันพัง วนด้วย.items()- list ครอบ dict + function คือท่าที่ใช้จริงบ่อยมาก จำโปรแกรมตัดเกรดวันนี้ไว้ดีๆ ได้ใช้แน่ครับ
ใน Part 4 เราจะไปต่อกันที่ การอ่าน/เขียนไฟล์ กับดัก error ด้วย try/except ครับ จะได้เขียนโปรแกรมที่เซฟข้อมูลไว้ใช้รอบหน้าได้จริง ไม่ต้องมานั่งกรอกใหม่ทุกครั้งที่รัน
ติดตามต่อใน Part 4 นะครับ
บทความที่เกี่ยวข้อง
Python สำหรับมือใหม่ #2: ตัวแปร ชนิดข้อมูล เงื่อนไข และ loop
บทความตอนที่ 2 ในซีรีส์ Python สำหรับมือใหม่ จะพาคุณเขียนโค้ดจริงครั้งแรก ด้วยตัวแปร ชนิดข้อมูล โครงสร้างควบคุม if/else และการทำซ้ำด้วย for/while loop
Python สำหรับมือใหม่ #1
บทความนี้จะพาคุณไปรู้จักกับภาษา Python ว่าคืออะไร มีประโยชน์อย่างไร และเตรียมตัวอย่างไรก่อนเริ่มเขียนโปรแกรม Python