งูต้นฉบับเปิดกล่องข้อมูลหลายชั้นแล้วเรียงสำเนาชิ้นส่วนลงถาด tuple ทางขวา

Python dataclasses.astuple: แปลงเป็น tuple พร้อมรู้ต้นทุน deep copy

พี่โชว์Engineering

เวลาส่ง dataclass เข้า API ที่ต้องการ tuple เราอาจคิดว่า dataclasses.astuple แค่หยิบ field มาเรียง แต่ฟังก์ชันนี้เดินข้อมูลซ้อนและคัดลอกวัตถุอื่นด้วย deepcopy ครับ ผลลัพธ์จึงแยกจากต้นฉบับ แต่มีต้นทุนที่ต้องรู้

WHY: snapshot ปลอดการแก้ร่วม แต่ไม่ฟรี

เหมือนถ่ายสำเนาแฟ้มทุกซองก่อนส่งออก ไม่ใช่แค่เขียนรายการชื่อแฟ้ม ถ้าต้นฉบับเปลี่ยนภายหลัง snapshot ไม่เปลี่ยนตาม เหมาะกับ boundary บางจุด แต่ object graph ใหญ่หรือมี resource ที่คัดลอกไม่ได้อาจช้าและล้มเหลว

import unittest
from dataclasses import astuple, dataclass


@dataclass
class Job:
    name: str
    tags: list[str]


class AsTupleTests(unittest.TestCase):
    def test_nested_list_is_copied(self):
        tags = ["daily", "safe"]
        job = Job("publish", tags)

        snapshot = astuple(job)
        tags.append("changed")

        self.assertEqual(snapshot, ("publish", ["daily", "safe"]))
        self.assertEqual(job.tags, ["daily", "safe", "changed"])

    def test_custom_tuple_factory(self):
        job = Job("verify", ["content"])
        snapshot = astuple(job, tuple_factory=lambda values: tuple(values))
        self.assertEqual(snapshot[0], "verify")


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

ถ้าต้องการ shallow tuple ตาม field จริง ๆ ใช้ tuple(getattr(obj, field.name) for field in dataclasses.fields(obj)) แล้วเขียนชื่อให้ชัดว่า shallow การเลือกขึ้นกับ contract: snapshot แยกขาดใช้ astuple; view เบา ๆ ที่ยอมแชร์ object ใช้ shallow conversion

Recap

  • astuple เดิน dataclass, dict, list และ tuple แบบ recursive
  • วัตถุอื่นถูกส่งผ่าน deepcopy
  • เหมาะกับ snapshot ที่ boundary มากกว่าการเรียกใน hot loop
  • เขียน shallow conversion เองเมื่อเจตนาคือแชร์ object ซ้อน