มารู้จัก Data classes กันครับ

Yothin Muangsommuk
3 min readJul 16, 2018

--

เกือบจะไม่ได้มาเขียนต่อกันละสำหรับ Part 2 ของ dataclass ตอนที่หนึ่งอยู่ที่ โพสนี้ นะครับ แต่เพื่อไม่ให้เป็นการเสียเวลาไปมากกว่านี้ เราไปต่อกันที่ Part 2 เลยครับ

ตอนที่แล้วเราได้รู้จัก dataclass ได้รู้ถึงเหตุผลว่าทำไมถึงเกิดมันขึ้นมา ตอนนี้เรามาจะมาโฟกัสกันว่า เราจะใช้ dataclass กับโค้ดเรายังไงครับ

ตัวอย่างการใช้งาน dataclass

อย่างแรกที่เราเห็นเลยคือ เราไม่ต้องเขียน __init__ method กันอีกแล้วครับ ตัว dataclass จะ generate โค้ดส่วนนั้นให้ และอีกอย่างที่เราสังเกตุเห็นเลยคือ dataclass ใช้ประโยชน์จาก Type hint เต็มๆ อย่างที่เราเห็นการประกาศ Attribute ข้างต้นจะมีการประกาศ Type ประกบด้วยทุกตัว ทำให้เราสามารถเข้าใจ Attribute แต่ละตัวได้ทันทีว่าใช้ Type อะไรโดยไม่ต้องเดา

อย่างที่สองครับ Representation ตัว dataclass เนี่ยนอกจากจะ generate __init__ ให้เราแล้วอีกตัวที่ทำให้คือ __repr__ ครับ ซึ่งตัว dataclass เนี่ยจะใช้ข้อมูลจาก attribute ที่เรา define ไว้มาสร้างให้ซึ่งช่วยเพิ่มความสะดวกให้เราและทำให้เราเลิกคิดไปได้หนึ่งเรื่อง หน้าตาก็จะประมาณนี้ครับ

ตัวอย่าง repr ที่ปริ้นต์ออกมาเมื่อใช้และไม่ใช้งาน dataclass

รวมถึงถ้าเราจะดู Type ของแต่ละ Attribute ก็สามารถดูได้ผ่าน method __annotations__ ครับ

ตัวอย่างผลลัพธ์ที่ได้จาก method __annotations__

ถามว่ามีไว้ทำไม ลองคิดภาพว่าเราต้อง pdb เข้าไป debug แล้ว __repr__ ไม่ได้ implement หรือเราไม่รู้ type ของแต่ละ attribute ดูสิครับ เราต้องมานั่งเดาว่า Instance ไหนคือตัวไหน มีค่าอะไร หรือแต่ละค่าเป็น type อะไร แต่พอเรามี Method พวกนี้มาให้แล้ว เราไม่ต้องเดาแล้วครับ

นอกจากสร้าง representation ให้แล้ว ตัว data class ยังสร้าง comparison method ให้ด้วยเช่น __lt__ , __le__ , __gt__ and __ge__ แต่ว่าเราต้องเพิ่ม argument order=True ใน decorator ของ dataclass ด้วยนะครับ มันถึงจะสร้าง method พวกนี้ให้

ตัวอย่างการเพิ่ม order ทำให้ dataclass compare ค่าใน instance ได้

โดยมันจะอ่านค่าจาก attribute ของ instance เราแล้วเอามา compare กับ instance อื่นได้ทั้ง <, <=, >, >= ส่วน == นั้นทำได้อยู่แล้วครับจาก dataclass ไม่ต้องมาเปิดผ่าน order

แต่ของที่ generate ขึ้นมารึจะสู้เราเขียนเอง ข้อเสียของ comparison method ที่มัน generate ให้คือมันไม่ฉลาดครับ ถ้าเราเอา class Team ที่เขียนข้างบนไปใช้ เราจะพบกับความประหลาด จนสงสัยว่ามันเทียบอะไร ยังไงของมัน นั่นทำให้เราต้องระบุค่าของแต่ละ attribute ผ่าน field() API ที่ dataclass สร้างมาให้ครับ

field() API นี่ สำหรับคนเคยใช้ ORM มาจะรู้สึกคุ้นหน้าคุ้นตามาก แต่ไม่เหมือนซะทีเดียว เพราะ field API นี่จะใช้ในการระบุ behavior ของ attribute ของ class แต่ละตัวว่าจะเอาไปใส่ใน representation รึเปล่า จะใช้ compare ด้วยมั้ย จะให้ attribute นี้เป็น immutable รึเปล่าหรือแม้กระทั่งใส่ metadata ไว้อธิบายว่า field นี้เอาไว้ทำอะไรก็ได้ครับ หน้าตาก็จะประมาณนี้

ตัวอย่างการใช้ field() API สำหรับกำหนดพฤติกรรมของ attribute

เรื่อง immutable นี่ ถ้าเราอยากให้ทั้ง dataclass เราไม่สามารถแก้ไขข้อมูลได้เช่นเอาไว้เก็บ constant หรือ setting ต่างๆ เราสามารถระบุได้ใน decorator argument ว่า frozen=True ครับ อารมณ์เดียวกับ namedTuple เลย

ตัวอย่างการใช้ frozen ทำให้ dataclass เป็น immutable object

เคสต่อมาคือ เราอยากให้หลังจากที่เรา instantiate dataclass แล้วอยากให้มันทำการคำนวนบางอย่าง ถ้าเป็นสมัยก่อนเราต้องสร้าง method ซักอย่างขึ้นมาใน class แล้วเรียกมันใน __init__ ใช่มั้ยครับ ในเคสของ dataclass เราสามารถกำหนดพฤติกรรมตรงนั้นได้ผ่าน method ที่ชื่อว่า __post_init__ ครับ

ตัวอย่างการใช้ __post_init__ กับ dataclass

ก็ประมาณนี้ครับ dataclass น่าจะครอบคลุมการใช้งานพื้นฐาน ถ้าอยากจะใช้งานลึกซึ้งมากกว่านี้เช่น Inheritance, __slot__ หรืออยากเข้าใจที่มาที่ไปมากกว่านี้ผมแนะนำ Talk ของ Raymond Hettinger ที่นี่เลยครับ

หรือถ้าขี้เกียจฟังไปอ่าน Blog ที่ผมสรุปเนื้อหาที่เข้าใจได้ที่นี่ครับ

และเหมือนเดิมครับ ผมยังเชียร์ให้อ่าน Official Document ครับที่นี่

ถ้าชอบเรื่องราวหรือข่าวสารต่างๆ ของภาษา Python ฝากติดตามได้ที่เพจ เขียนงูให้วัวกลัว นะครับขอบคุณครับ

--

--

Yothin Muangsommuk

Pythonista @ProntoTools ♥ Python, Django, Vim and Star Trek 🖖