Comprehension ใน Python: list, dict, set และ generator
บทเรียนนี้แปลจากต้นฉบับภาษาอังกฤษRead in EnglishLeer en españolЧитать на русскомLer em portuguêsแจ้งข้อผิดพลาดในการแปล
ลองนึกภาพว่าคุณมีลิสต์ของตัวเลข 100 ตัว และต้องการลิสต์ใหม่ที่มีเฉพาะเลขคู่ โดยแต่ละตัวคูณ 2 ถ้าใช้ลูปธรรมดาต้องเขียนสี่บรรทัด แต่ถ้าใช้ comprehension ของ Python (การสร้างคอลเลกชันแบบย่อ) ใช้แค่ บรรทัดเดียว
comprehension เป็นหนึ่งในความสามารถของ Python ที่ผู้คนชื่นชอบมากที่สุด ช่วยให้คุณสร้างลิสต์ ดิกชันนารี และเซตใหม่ได้ด้วยการบอกว่า ต้องการอะไร แทนที่จะเขียนคำสั่งทีละขั้นว่าจะสร้าง อย่างไร
ในบทเรียนนี้ คุณจะได้เรียน comprehension ครบทั้งสี่แบบ ได้แก่ list, dict, set และ generator รวมถึงการกรองข้อมูล การใช้เงื่อนไข และคำถามสำคัญว่าเมื่อไร ไม่ควร ใช้ comprehension
List Comprehension: รูปแบบที่ใช้บ่อยที่สุด
list comprehension (การสร้างลิสต์แบบย่อ) สร้างลิสต์ใหม่โดยนำนิพจน์ไปใช้กับสมาชิกแต่ละตัวในลำดับข้อมูลที่มีอยู่ รูปแบบไวยากรณ์คือ [expression for item in iterable]
ให้คิดว่าเป็นลูป for แบบย่อที่สร้างลิสต์ขึ้นมา แทนที่จะสร้างลิสต์ว่างแล้ว append สมาชิกทีละตัวในลูป คุณอธิบายผลลัพธ์ที่ต้องการได้ในบรรทัดเดียว
numbers = [1, 2, 3, 4, 5]
squares = []
for n in numbers:
squares.append(n ** 2)
print(squares) # [1, 4, 9, 16, 25]numbers = [1, 2, 3, 4, 5]
squares = [n ** 2 for n in numbers]
print(squares) # [1, 4, 9, 16, 25][1, 4, 9, 16, 25] ['ALICE', 'BOB', 'CHARLIE'] [5, 5, 6, 2, 3]
การเพิ่มเงื่อนไขใน Comprehension
คุณเพิ่มส่วน if เพื่อกรองสมาชิกได้ เฉพาะสมาชิกที่ผ่านเงื่อนไขเท่านั้นที่จะถูกใส่ลงในลิสต์ใหม่ รูปแบบไวยากรณ์คือ [expression for item in iterable if condition]
[2, 4, 6, 8, 10] ['hello', 'world', 'python']
คุณยังใช้นิพจน์ if-else เพื่อแปลงสมาชิกแต่ละตัวให้ต่างกันตามเงื่อนไขได้ด้วย แต่สังเกตว่าไวยากรณ์เปลี่ยนไป if-else ต้องอยู่ ก่อน for ไม่ใช่หลัง
['odd', 'even', 'odd', 'even', 'odd', 'even'] [85, 100, 92, 100, 78]
Dict comprehension: สร้างดิกชันนารีแบบย่อ
dict comprehension ทำงานเหมือน list comprehension แต่สร้างดิกชันนารี (dictionary) แทน ใช้วงเล็บปีกกาและนิพจน์แบบ key: value รูปแบบไวยากรณ์คือ {key: value for item in iterable}
{1: 1, 2: 4, 3: 9, 4: 16, 5: 25}
{'Alice': 95, 'Bob': 87, 'Charlie': 72}คุณกรองด้วย if ได้เหมือนกับ list comprehension วิธีนี้เหมาะมากสำหรับการสร้างดิกชันนารีที่มีเพียงบางส่วนของดิกชันนารีเดิม
{'apple': 1.5, 'banana': 0.75}
{1.5: 'apple', 0.75: 'banana', 3.0: 'cherry', 5.0: 'date'}Set comprehension: สร้างเซตแบบย่อ
set comprehension สร้างเซต ซึ่งเป็นคอลเลกชันที่ ไม่มีสมาชิกซ้ำ ไวยากรณ์ใช้วงเล็บปีกกาเหมือน dict comprehension แต่ไม่มีเครื่องหมายโคลอน รูปแบบไวยากรณ์คือ {expression for item in iterable}
{'B', 'C', 'A'}
[3, 4, 5]ความแตกต่างสำคัญจาก list comprehension คือเซตจะลบสมาชิกที่ซ้ำออกให้โดยอัตโนมัติ แม้ "Alice" และ "Anna" จะขึ้นต้นด้วย "A" ทั้งคู่ แต่เซตก็มี "A" อยู่ตัวเดียว
Generator Expression: Comprehension ที่ประหยัดหน่วยความจำ
generator expression หน้าตาเหมือน list comprehension แต่ใช้ วงเล็บกลม แทนวงเล็บเหลี่ยม ความแตกต่างสำคัญคือ generator expression ไม่ได้สร้างสมาชิกทั้งหมดในครั้งเดียว แต่สร้างทีละตัวเมื่อมีการขอใช้
เรื่องนี้สำคัญเมื่อคุณทำงานกับข้อมูลขนาดใหญ่มาก list comprehension ที่มีหนึ่งล้านสมาชิกจะสร้างลิสต์หนึ่งล้านสมาชิกไว้ในหน่วยความจำ ส่วน generator expression แทบไม่ใช้หน่วยความจำเลย เพราะคำนวณสมาชิกแต่ละตัวเฉพาะตอนที่คุณขอเท่านั้น
[0, 1, 4, 9, 16] <class 'list'> <generator object <genexpr> at 0x4fc5898> <class 'generator'> 0 1 4 9 16
generator expression มักใช้ภายในฟังก์ชันอย่าง sum(), max() และ min() และถ้า generator เป็นอาร์กิวเมนต์ตัวเดียวของฟังก์ชัน คุณไม่ต้องใส่วงเล็บซ้อนอีกชั้น
Sum of squares: 385 Has negative: False All positive: True
Comprehension ซ้อนกัน
คุณใส่ส่วน for หนึ่งไว้ภายในอีกส่วนหนึ่งเพื่อจัดการลูปซ้อนกันได้ ลูปนอกมาก่อน แล้วตามด้วยลูปใน วิธีนี้มีประโยชน์สำหรับการแปลงลิสต์ซ้อนให้เป็นลิสต์เดียว (flatten) หรือการสร้างตาราง
[1, 2, 3, 4, 5, 6, 7, 8, 9] [1, 2, 3, 4, 5, 6, 7, 8, 9]
[(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)]
เมื่อไรที่ไม่ควรใช้ Comprehension
comprehension ทรงพลังมาก แต่ก็ไม่ใช่ทางเลือกที่ถูกต้องเสมอไป ถ้า comprehension อ่านยาก ลูปธรรมดาจะดีกว่า ความชัดเจนของโค้ดสำคัญกว่าความแพรวพราวเสมอ
หลีกเลี่ยง comprehension เมื่อ: ตรรกะซับซ้อนและมีหลายเงื่อนไข ต้องการผลข้างเคียง (side effect) เช่น การแสดงผลภายในลูป หรือ comprehension ยาวเกินประมาณ 80 ตัวอักษร
result = [x * 2 if x > 0 else -x for x in data
if x != 0 and x % 3 == 0]result = []
for x in data:
if x != 0 and x % 3 == 0:
if x > 0:
result.append(x * 2)
else:
result.append(-x)นอกจากนี้ อย่าใช้ comprehension เพียงเพื่อสร้างผลข้างเคียง ถ้าคุณเรียก print() หรือแก้ไขตัวแปรภายนอก ให้ใช้ลูปธรรมดาแทน comprehension ควรมีไว้เพื่อ สร้างคอลเลกชันใหม่ เสมอ
แบบฝึกหัด
ได้เวลาฝึกแล้ว! แบบฝึกหัดเหล่านี้ครอบคลุม comprehension ทั้งสี่แบบ ตั้งแต่พื้นฐานไปจนถึงรูปแบบที่ท้าทายขึ้น
ใช้ list comprehension สร้างลิสต์ชื่อ doubled ที่มีตัวเลขแต่ละตัวจากลิสต์ที่กำหนดให้คูณด้วย 2
ผลลัพธ์ที่ควรได้:
[2, 4, 6, 8, 10]ใช้ list comprehension ที่มีส่วน if สร้างลิสต์ชื่อ evens ที่มีเฉพาะเลขคู่จากลิสต์ที่กำหนดให้
ผลลัพธ์ที่ควรได้:
[4, 12, 8, 20, 6]อ่านโค้ดนี้อย่างละเอียดแล้วทายว่าจะแสดงผลอะไร สังเกตตำแหน่งของ if-else ให้ดี
พิมพ์ผลลัพธ์ให้ตรงทุกตัวอักษร
ใช้ dict comprehension สร้างดิกชันนารีชื่อ name_lengths ที่จับคู่ชื่อแต่ละชื่อกับความยาวของชื่อนั้น
ผลลัพธ์ที่ควรได้:
{'Alice': 5, 'Bob': 3, 'Charlie': 7}ใช้ set comprehension หาสระภาษาอังกฤษทั้งหมดที่ไม่ซ้ำกันในคำที่กำหนดให้ เก็บผลลัพธ์ไว้ในตัวแปรชื่อ vowels_found แล้วแสดงผลเป็นลิสต์ที่ เรียงลำดับแล้ว
ผลลัพธ์ที่ควรได้:
['a', 'e', 'i', 'o']โค้ดนี้พยายามสร้างลิสต์ที่ตัวเลขมากกว่า 5 ได้ป้ายกำกับ 'high' และตัวอื่นได้ป้ายกำกับ 'low' แต่ไวยากรณ์ผิด โปรแกรมจึงหยุดทำงาน แก้ไขให้ถูกต้อง
ผลลัพธ์ที่ควรได้:
['low', 'high', 'low', 'high', 'low', 'high']ปรับโครงสร้างโค้ด (refactor) ลูปต่อไปนี้ให้เป็น list comprehension เพียงตัวเดียว โดยผลลัพธ์ต้องเหมือนเดิมทุกประการ
ผลลัพธ์ที่ควรได้:
[1, 9, 25, 49, 81]ใช้ list comprehension ที่มี for สองส่วน เพื่อแปลงเมทริกซ์ที่กำหนดให้ (ลิสต์ของลิสต์) เป็นลิสต์เดียวที่ไม่ซ้อนกัน จากนั้นแสดงผลรวมของสมาชิกทั้งหมดโดยใช้ generator expression ภายใน sum()
ผลลัพธ์ที่ควรได้:
[1, 2, 3, 4, 5, 6, 7, 8, 9]
45สรุป: สิ่งที่คุณได้เรียนเกี่ยวกับ Comprehension ใน Python
สรุปทุกสิ่งที่คุณรู้แล้วตอนนี้:
| แบบ | ไวยากรณ์ | สิ่งที่ได้ |
|---|---|---|
| List comprehension | [expr for x in items] | ลิสต์ |
| ลิสต์แบบกรอง | [expr for x in items if cond] | ลิสต์ที่กรองแล้ว |
| ลิสต์แบบแปลงค่า | [a if cond else b for x in items] | ลิสต์ที่แปลงค่าแล้ว |
| Dict comprehension | {k: v for x in items} | ดิกชันนารี |
| Set comprehension | {expr for x in items} | เซต (ไม่มีตัวซ้ำ) |
| Generator expression | (expr for x in items) | generator ที่สร้างค่าเมื่อถูกขอ (lazy) |
| Comprehension ซ้อนกัน | [expr for a in X for b in a] | ลิสต์เดียวที่ไม่ซ้อนกัน |
comprehension เป็นหนึ่งในความสามารถที่เป็นเอกลักษณ์ของ Python ช่วยให้โค้ดสั้นลง อ่านง่ายขึ้น (เมื่อใช้อย่างเหมาะสม) และมักเร็วกว่าลูปที่ทำงานแบบเดียวกัน
บทต่อไปคืออะไร
ตอนนี้คุณสร้างและแปลงคอลเลกชันได้อย่างคล่องแคล่วแล้ว ก็พร้อมไปเรียน ทูเพิลใน Python ซึ่งเป็นลำดับข้อมูลที่เปลี่ยนแปลงไม่ได้ เหมาะกับข้อมูลที่คงที่ เช่น พิกัด ระเบียนข้อมูล และค่าที่ฟังก์ชันคืนกลับมา
คุณจะได้เรียนว่าทำไมการเปลี่ยนแปลงไม่ได้ (immutability) จึงมีประโยชน์ การรวมและแยกค่าในทูเพิล (packing และ unpacking) ทำงานอย่างไร และเมื่อไรควรเลือกใช้ทูเพิลแทนลิสต์