Python total_ordering: ลด comparison boilerplate แต่เก็บกฎลำดับไว้จุดเดียว
value object ที่ต้อง sort มักเขียน __lt__, __le__, __gt__ และ __ge__ ซ้ำจนกฎเริ่มไม่ตรงกันครับ functools.total_ordering ให้เราประกาศ __eq__ กับ comparison หลักเพียงหนึ่งตัว แล้วเติมวิธีที่เหลือจากกฎเดียวกัน
เหมือนวางทางลาดเพียงเส้นเดียว: เมื่อรู้ว่าก้อนไหนอยู่ก่อนและก้อนไหนเท่ากัน เราอนุมานความสัมพันธ์อื่นได้ ไม่ต้องสร้างไม้บรรทัดสี่อันที่อาจตั้งศูนย์ไม่ตรงกัน
from functools import total_ordering
import unittest
@total_ordering
class Priority:
def __init__(self, rank: int):
self.rank = rank
def __eq__(self, other):
if not isinstance(other, Priority):
return NotImplemented
return self.rank == other.rank
def __lt__(self, other):
if not isinstance(other, Priority):
return NotImplemented
return self.rank < other.rank
class OrderingTests(unittest.TestCase):
def test_all_relations_come_from_one_rule(self):
low, high = Priority(1), Priority(3)
self.assertTrue(low < high)
self.assertTrue(high > low)
self.assertTrue(low <= Priority(1))
self.assertTrue(high >= Priority(3))
def test_unrelated_type_is_not_silently_ordered(self):
with self.assertRaises(TypeError):
_ = Priority(1) < 1
if __name__ == "__main__":
unittest.main()
NotImplemented ไม่ใช่ False
เมื่อชนิดอีกฝั่งเปรียบเทียบกันไม่ได้ ควรคืน NotImplemented เพื่อให้ Python ลอง reflected operation หรือยก TypeError อย่างถูกต้อง การคืน False จะซ่อนความผิดพลาดและอาจทำให้ทั้ง a < b กับ a > b เป็นเท็จโดยไม่มีคำอธิบาย
เอกสารเตือนว่า decorator เพิ่ม stack frame และวิธีที่สร้างอาจช้ากว่าการเขียนครบเอง งานทั่วไปแลกความเร็วเล็กน้อยกับกฎที่รวมศูนย์ได้คุ้ม แต่ hot loop ที่พิสูจน์จาก profiler แล้วค่อยเขียน comparator ครบและเพิ่ม property tests ตรวจ transitivity อีกชั้น ที่สำคัญคือ equality และ ordering ต้องใช้ identity เดียวกัน หาก __eq__ ดูทั้ง rank กับชื่อ แต่ __lt__ ดู rank อย่างเดียว sorted container อาจมี behavior ชวนสับสน
Recap
- นิยาม
__eq__และ comparator หนึ่งตัวจาก key เดียวกัน - คืน
NotImplementedเมื่อชนิดเปรียบเทียบกันไม่ได้ - ทดสอบ equal, less, greater และชนิดที่ไม่เกี่ยวข้อง
- optimize comparator เองเมื่อ profiler ชี้ว่าจำเป็นจริง