งูต้นฉบับกำลังแบ่งด้ายจากหลอดเดียวเป็นสองทาง โดยทางช้าค่อย ๆ มีกองด้ายสำรองสะสม

Python itertools.tee: แยก iterator ได้ แต่ต้องรู้ว่า buffer โตตรงไหน

พี่โชว์Engineering

Iterator อ่านได้รอบเดียว แต่บางงานต้องส่ง stream เดียวให้ consumer สองตัว itertools.tee สร้าง iterator อิสระหลายทางโดยอ่าน source ร่วมกันและเก็บ backlog ให้ทางที่ช้าครับ

from __future__ import annotations

import unittest
from itertools import tee


class TeeTests(unittest.TestCase):
    def test_source_is_pulled_once_and_slow_reader_catches_up(self) -> None:
        pulled: list[int] = []

        def source():
            for value in range(3):
                pulled.append(value)
                yield value

        fast, slow = tee(source(), 2)
        self.assertEqual(next(fast), 0)
        self.assertEqual(next(fast), 1)
        self.assertEqual(pulled, [0, 1])
        self.assertEqual(next(slow), 0)
        self.assertEqual(pulled, [0, 1])


if __name__ == "__main__":
    unittest.main()

ให้นึกถึงร้านที่ถ่ายสำเนาใบคิวให้แถวสองแถว ถ้าแถวแรกเดินเร็วกว่า ใบคิวที่แถวสองยังไม่อ่านต้องกองรออยู่ ความจำของ tee จึงขึ้นกับระยะห่างระหว่าง consumer ไม่ใช่แค่จำนวน iterator หากทางหนึ่งกิน stream จนจบ แต่อีกทางยังไม่เริ่ม buffer อาจโตใกล้ขนาดข้อมูลทั้งหมด

เอกสารยังระบุว่า tee ไม่ thread-safe อย่าให้ iterator ลูกถูกใช้พร้อมกันข้าม thread โดยไม่มีการประสาน หากรู้ว่าจะอ่านข้อมูลทั้งหมดหลายรอบและข้อมูลพอดี memory การสร้าง list(source) อาจตรงไปตรงมาและคาดการณ์ต้นทุนง่ายกว่า

Recap

  • ใช้เมื่อ consumer เดินใกล้กันและ source สร้างซ้ำแพง
  • เฝ้าระยะห่าง เพราะ backlog คือ memory
  • ไม่ใช้ iterator ลูกพร้อมกันข้าม thread
  • ใช้ list เมื่อจะเก็บทั้งหมดอยู่แล้ว