Python zip(strict=True): ให้ข้อมูลคนละความยาวล้มตรงจุด ไม่เงียบหาย
zip() แบบปกติหยุดเมื่อ iterable ที่สั้นที่สุดหมดครับ พฤติกรรมนี้ดีเมื่อเราตั้งใจตัดส่วนเกิน แต่เป็นบั๊กเงียบในงานจับคู่ชื่อกับคะแนน, คอลัมน์ CSV หรือ ID กับ payload เพราะ record ด้านท้ายหายโดยไม่มี exception
WHY: ความเงียบแพงกว่าการล้มเร็ว
สมมติมีชื่อลูกค้า 100 คนแต่ยอดชำระ 99 ค่า zip(names, amounts) จะคืนเพียง 99 คู่ คนสุดท้ายหายจากรายงานโดยโค้ดยัง exit 0 strict=True เปลี่ยน contract เป็น “ทุก input ต้องหมดพร้อมกัน” และยก ValueError เมื่อพบความยาวไม่ตรง เหมือนรูดซิปสองฝั่งแล้วบังคับให้ฟันทุกซี่ประกบกัน
เอกสาร Python แนะนำ strict เมื่อ iterable ถูกคาดว่ามีความยาวเท่ากัน ค่า default ยังเป็น False เพื่อรักษาความเข้ากันได้ จึงต้องประกาศเจตนาใน call site เอง
import unittest
def pair_records(ids, amounts):
return list(zip(ids, amounts, strict=True))
class StrictZipTests(unittest.TestCase):
def test_equal_lengths(self):
self.assertEqual(pair_records(["a", "b"], [10, 20]), [("a", 10), ("b", 20)])
def test_empty_inputs(self):
self.assertEqual(pair_records([], []), [])
def test_mismatch_fails_instead_of_truncating(self):
with self.assertRaises(ValueError):
pair_records(["a", "b"], [10])
if __name__ == "__main__":
unittest.main()
จุดที่ต้องระวังกับ iterator
zip ทำงานแบบ lazy จึงอาจยังไม่พบ mismatch ตอนสร้าง object ต้อง iterate จนถึงปลายเหมือนตัวอย่างที่แปลงเป็น list หาก consumer หยุดอ่านก่อน ความต่างด้านท้ายก็ยังไม่ถูกตรวจ นอกจากนี้ iterable อาจมี side effect และถูกดึงค่าทีละรอบจากซ้ายไปขวา อย่าใช้ strict เป็นข้ออ้างให้เอา network stream สองเส้นมาล็อกจังหวะกันโดยไม่มี timeout
ถ้าเจตนาคือเติมค่าที่ขาด ใช้ itertools.zip_longest พร้อม sentinel ที่แยกจากข้อมูลจริง หากเจตนาคือตัดส่วนเกิน ให้คง zip ปกติแล้วใส่ comment หรือ test ให้เห็นว่าเป็นการเลือก ไม่ใช่ความบังเอิญ
Recap
- ใช้
strict=Trueเมื่อความยาวเท่ากันเป็น invariant - exception เกิดตอน iterate ไม่ใช่ตอนสร้าง
zip - ใช้
zip_longestเมื่อต้องเก็บส่วนเกิน - ทดสอบกรณีว่าง, เท่ากัน และต่างกันทั้งสองด้าน