Tutorial

รวมมิตร 5 Dunder/Magic Method ใน Python ที่ใช้บ่อย

By Arnon Puitrakul - 03 พฤศจิกายน 2021 - 1 min read min(s)

รวมมิตร 5 Dunder/Magic Method ใน Python ที่ใช้บ่อย

สำหรับคนที่เขียน Python มาก่อนน่าจะรู้กันดีกว่า ภายในภาษามีของเล่นให้เราเล่นเยอะมาก ๆ บางทีก็เยอะจนปวดหัวเลย หนึ่งในของเล่นที่ทรงพลังมาก ๆ คือ Dunder Method หรือบางคนอาจจะเรียกว่า Magic Method ที่ทำให้การเขียน Script ทำได้ดูดี และ ง่ายขึ้นหลายขุมมาก ๆ ถ้านึกไม่ออกว่ามันคืออะไร ให้เรานึงถึงตอนเราสร้าง Class แล้วเราจะต้องกำหนด Constructor นั่นแหละ Init นั่นเลยก็เป็นหนึ่งใน Dunder Method ซึ่งถ้าเราเข้าไปดูใน Document จริง ๆ มันเยอะมาก ๆ ถ้าจะให้ยกมาหมดเลย เราว่า เปลี่ยนจากบทความเป็นหนังสือเลยเถอะ เขียนได้หลายบทเลย ดังนั้น วันนี้เราจะยกตัวที่เราใช้บ่อย ๆ มาให้ดูกันว่าจะมีอะไรบ้าง และ ทำอะไรได้บ้าง

__init__

class Post :
    def __init__ (self, message:str, source:str, destination:str):
        self.message = message
        self.source = source
        self.destination = destination

ตัวแรก แบบ Basic สุด ๆ เลย มั่นใจว่า หลาย ๆ คนต้องผ่านมาบ้างแล้วละ มันเป็น Method สำหรับการประกาศ Object หรือก็คือ Constructor นั่นเอง โดยปกติตัว Python มันก็จะเข้าไปเรียกให้เราเองเลย โดยที่เราไม่ต้องบอกอะไรมันเลย แต่ ๆ ถ้าเกิดว่า เรามีการ Extend Class หรือก็คือ Inherit Class ลงมา Python มันจะไม่ไปเรียก Parent Class ให้เราเองนะ เราจำเป็นที่จะต้องทำการ Initiate เอง ผ่านคำสั่ง super.__init__()

__str__

class Post :
    def __init__ (self, message:str, source:str, destination:str):
        self.message = message
        self.source = source
        self.destination = destination
    
    def __str__ (self) :
        return self.message

คำสั่งจำพวกต่อไป เป็นพวก Caster ที่ไม่ใช่ Game Caster ผ่ามมม แต่เป็น Dunder Method ที่จะถูกเรียกเมื่อเราทำการแปลง Object ของเราเป็น Class ต่าง ๆ อย่าง str ที่เรายกขึ้นมา ก็ตามชื่อเลย เมื่อเราทำการ Cast เป็น String เราจะให้มันแปลงยังไง เดิมที Python มันมี Default Value ให้เราอยู่แล้ว แต่ถ้าเราต้องการจะ Override ก็สามารถทำได้ง่าย ๆ เหมือนใน Code ด้านบนเลย ก็คือ เราให้มันกลายร่างเป็นแค่ Message ไปเลย เมื่อเรา Cast เป็น String

ถ้าเราลองสังเกตดี ๆ คำสั่ง print() ที่เราใช้เยอะมาก ๆ สิ่งที่มันเอาออกมาทางหน้าจอมันต้องเป็น String แน่ ๆ แต่บางที เราก็ยัด Data Type อื่น ๆ เข้าไป เช่น ตัวเลข ต่าง ๆ แล้วเอ๋ ทำไมมันถึงออกมาเป็น String ได้ละ หรือแม้แต่ format() ที่เราใช้ในการจัดการรูปร่างของ String ที่เราต้องการทำให้มันแปลงได้ละ นั่นก็เพราะ Python มันมี Default อยู่แล้ว ที่คืนค่ากลับมาเป็นตัวเลขที่เป็น String นั่นเอง เลยทำให้ออกมาเป็นแบบที่เราเห็นเลย

__len__

class PostBox :
   def __init__ (self) :
       self.box = []
   
   def __len__ (self) :
       return len(self.box)
   
   def addPost (self, new_post: Post) :
       self.box.append(new_post)

len ก็ตรงตัวเหมือนกันเลย คือเราจะต้องให้ Object คืนค่ากลับมาเป็น จำนวนอะไรบางอย่าง ในตัวอย่างด้านบน เราจะเป็น Class สำหรับกล่องจดหมายที่ในนั้นเราจะมีจดหมายอยู่ ทำให้ใน len เราเลยให้มันคืนกลับไปเป็นจำนวนของจดหมายในกล่องเลย

box = PostBox()
my_post = Post("Hello World", "My Source", "My Destination")

box.addPost(my_post)
print(len(box))
OUTPUT:
1

ในการใช้งานจริงก็อย่างที่ด้านบนเป็นเลย เราก็ทำการสร้างกล่องจดหมายที่เราสร้างไว้ และ สมมุติว่า เรามี Function สำหรับการเพิ่มจดมหาย โดยการใส่ Object ของจดหมายเข้าไป จากเดิม ถ้าเราเรียกตอนที่เรายังไม่หย่อนจดหมายเข้าไป เราก็น่าจะได้ 0 และ เมื่อเราหย่อนจดหมายไป 1 ใบ เราก็จะได้เลข 1 กลับมาเหมือนด้านบนนั่นเอง

การที่เราใช้ Dunder อย่าง len ทำให้การเขียนโปรแกรมของเรามันอ่านแล้วเข้าใจได้ง่ายขึ้น ไม่จำเป็นที่จะต้องไปสร้างชื่อ Method เพิ่มให้ปวดหัว เราใช้ Built-in Method ที่ Python เตรียมมาให้อยู่แล้วดีกว่า คนที่เขียน Python ส่วนใหญ่ยังไง ๆ ก็ต้องรู้จัก len() มาไม่มากก็น้อย ทำให้เข้าใจ Script ได้ง่ายขึ้นเยอะ

__contains__

class PostBox :
   def __init__ (self) :
       self.box = []
   
   def __len__ (self) :
       return len(box)
       
   def __contains__ (self, key):
       return key in self.box
   
   def addPost (new_post: Post) :
       self.box.append(new_post)

ไหน ๆ เราก็พูดถึงการที่ใน Class เราบรรจุข้อมูลหลาย ๆ ชิ้นอย่าง PostBox แล้ว มันก็จะมีเคสที่เราอยากจะหาว่า ในกล่อง หรือ Object ของเรามีของที่เราต้องการอยู่หรือไม่ ซึ่งจริง ๆ แล้วเราก็อาจจะเขียน Method ใหม่ในการจัดการแหละ แต่ถ้าเราใช้ Dender Method เราก็สามารถใช้ contains ในการสั่งให้มันเข้าไปหาในชุดข้อมูลได้ จากตัวอย่าง เรา ก็สร้าง contains Method ขึ้นมา โดยรับ Key เข้ามา (ในที่นี้ก็จะเป็น Object ของจดหมาย) และให้เข้าไปเช็คว่า ในกล่อง มันมีจดหมายที่เราตามหาอยู่มั้ย ถ้าเราจำได้การเขียนแบบนี้ มันจะได้ค่ากลับไปเป็น Boolean ไม่ True ก็ False

>>> my_post in box
True

ทำให้เราสามารถใช้ Expression in ในการค้นหาได้ตรง ๆ เลยว่า ในกล่องนี้มีจดหมายที่เราค้นหาอยู่มั้ย โดยที่เราไม่ต้องไปสร้าง Method ในการค้นหาเพิ่มที่ซับซ้อนเลย

__eq__

class Post :
    def __init__ (self, message:str, source:str, destination:str):
        self.message = message
        self.source = source
        self.destination = destination
    
    def __str__ (self) :
        return self.message
   
   def __eq__ (self, other) :
       if isinstance(other, self.__class__):
           return str(self) == str(other) and self.source == other.source and self.destination == other.destination
       return False

จากก่อนหน้า เราบอกว่า เราพยายามจะหาจดหมายโดยการใช้ Dunder Method แต่การค้นหามันก็ติดที่ว่า เราจะต้องทำการเปรียบเทียบ ตัว Object ตรง ๆ เลย ถ้าเราอ่านมาลึกเรื่องนี้นิดนึง มันจะซับซ้อนมาก ๆ เพราะมันจะเข้าไปเปรียบเทียบ Instance ID ภายใน Python เทียบกันตรง ๆ เลย ทำให้ถ้าเราเกิดสร้าง Object ใหม่ ที่มาจาก Class เดียวกัน และมีข้อมูลภายในเหมือนกัน เอามาเทียบกันบางเคส เราจะได้ออกมาไม่เท่ากัน ทำให้เราเปรียบเทียบได้ยากมาก ๆ ทำให้ถ้าเราอยากจะเปรียบเทียบกัน เราอาจจะต้อง Override Method ในส่วนของการเทียบเข้าไป อย่างตัวที่เราเอามาให้ดูคือ eq หรือ equal หรือก็คือ เท่ากับ นั่นเอง

ถ้าเราลองเข้าไปอ่านในส่วนของการ Implement ของตัวอย่างด้านบน เราทำการ Implement บน Class ของ Post ตรง ๆ เลย เพื่อใช้ตรวจสอบจดหมายว่ามันคืออตัวเดียวกันมั้ย ทำให้ในตอนที่เราหาจดหมายในกล่อง เราจะได้ไม่เจอปัญหาที่เราเล่าไปว่า ถึงข้อมูลด้านในมันจะเหมือนกัน แต่ถ้าไม่ใช่ Instance ID เดียวกัน มันก็จะหาไม่เจอ ดังนั้น เราเลย เขียน เพื่อบอกให้มันเช็คเลยว่า ถ้า ต้นทาง, ปลายทาง และ Message เดียวกัน มันก็มีค่ามากพอแล้วที่จะเป็นจดหมายฉบับเดียวกันได้

โดยเราเริ่มจากการเช็คก่อนว่า ตัวมันเอง เรารู้อยู่แล้วว่าคือ Post และ อีกตัวคืออะไรสักอย่าง มันเป็นตัวเดียวกันมั้ย หรือพูดอีกนัยก็คือ อีกตัวที่เอามาเช็คมันเป็น Post มั้ยนั่นเอง ถ้าไม่ใช่ มันก็ควรจะหลุดไป return False เลย ก็คือไม่เท่านั่นเอง แต่ถ้าเท่า เราก็เข้าไปเช็คต่อว่า ข้อความ, ต้นทาง และ ปลายทาง มันเหมือนกันมั้ย ถ้าเหมือนกัน เราก็คืนกลับไปเป็น True ได้เลย

สรุป

Dunder Method ที่เรายกขึ้นมาในวันนี้ น่าจะเป็นตัวที่เราใช้บ่อยมาก ๆ ตัวนึงเลย มันทำให้การเขียน Python Script ของเราง่ายขึ้นเยอะ ไม่ต้องไปนั่งสรรหาชื่ออะไรให้ยุ่งยากเต็มไปหมด เพียงแค่เรา Override Dunder Method ที่มีอยู่แล้วใน Python ก็ทำให้เราสามารถทำงานได้เลย ผ่าน Syntax หรือคำสั่งที่คุ้นเคย เพราะจริง ๆ แล้วตัว Python เอง Object หลาย ๆ ตัว ที่มันมีความสามารถพวก len อะไรต่าง ๆ มันก็เกิดจากที่ไส้ของ Python เอง เขาก็ไป Override Dunder Method เช่นเดียวกัน ในตอนหน้าเราจะเอาตัวที่พีค และ ฮ่ากว่านี้มาโชว์กัน วันนี้ 5 ตัวที่ให้ไปเป็นน้ำจิ้มที่โหดมาก ๆ แล้ว ลองเอาไปใช้กันดูได้ แซ่บมากบอกเลย