Python itertools.compress: ให้ boolean mask กับข้อมูลเดินพร้อมกันแบบ lazy
เมื่อมี iterable ของข้อมูลกับ iterable ของ selector ที่ตรงตำแหน่งกัน เราอาจเขียน list comprehension ด้วย zip ได้ itertools.compress สื่อ intent ว่า “คืนข้อมูลเฉพาะตำแหน่งที่ selector เป็น truthy” และทำงานแบบ lazy จึงต่อกับ stream ขนาดใหญ่ได้โดยไม่ materialize ผลทั้งหมดครับ
import unittest
from itertools import compress
def enabled_names(names, selectors):
return list(compress(names, selectors))
class CompressTests(unittest.TestCase):
def test_selects_truthy_positions(self):
self.assertEqual(
enabled_names(["search", "billing", "export"], [1, 0, True]),
["search", "export"],
)
def test_stops_at_shorter_input(self):
self.assertEqual(enabled_names(["a", "b", "c"], [True]), ["a"])
def test_accepts_generators(self):
selectors = (value >= 0.8 for value in [0.2, 0.9, 0.8])
self.assertEqual(enabled_names(["x", "y", "z"], selectors), ["y", "z"])
if __name__ == "__main__":
unittest.main()
จุดที่อาจเงียบเกินไป
compress หยุดเมื่อ data หรือ selectors ฝั่งใดฝั่งหนึ่งหมด ถ้า mask สั้นเพราะบั๊ก สมาชิกท้ายจะหายโดยไม่มี exception พฤติกรรมนี้เหมือน zip แบบปกติและเหมาะเมื่อทั้งสอง stream จบตามธรรมชาติ แต่ไม่เหมาะเมื่อความยาวต้องเท่ากันตามสัญญา ในกรณีนั้นให้ validate length ล่วงหน้าสำหรับ sequence หรือใช้ zip(..., strict=True) เพื่อสร้างคู่แล้วกรองอย่าง explicit
selector ไม่จำเป็นต้องเป็น bool; Python ใช้ truthiness ดังนั้น 0, None และ string ว่างเป็น false ถ้าข้อมูลมาจาก CSV หรือ environment ค่า string "false" กลับเป็น truthy เพราะไม่ว่าง ต้อง parse เป็น boolean ก่อน มิฉะนั้น mask ที่ดูถูกต้องในไฟล์จะเปิดทุกช่อง
เพราะผลเป็น iterator การประมวลผลจะเกิดตอน consume หาก generator ฝั่ง selector มี exception มันอาจเกิดไกลจากบรรทัดสร้าง compress อย่าส่ง iterator นี้ข้าม boundary โดยไม่มีเอกสาร lifecycle และควรแปลงเป็น list ณ จุดที่ต้องการ snapshot จริง
Recap
compress(data, selectors)กรองตามตำแหน่งแบบ lazy- มันหยุดที่ iterable ที่สั้นกว่าโดยไม่เตือน
- parse selector ภายนอกเป็น boolean อย่าใช้ truthiness ของ string ตรง ๆ
- materialize เฉพาะเมื่อ boundary ต้องการ snapshot ครับ