Tutorial

Instance Creation บน Python ไส้มันเป็นยังไง

By Arnon Puitrakul - 19 พฤศจิกายน 2021

Instance Creation บน Python ไส้มันเป็นยังไง

หลังจากเราเอาเรื่องของ Dunder Method มาเล่ากันเมื่อไม่นานมานี้ ทำให้มีคนถามเราเข้ามาในเรื่องของ new และ init ที่เป็นหนึ่งใน Dunder Method ที่เราใช้ในในการสร้าง Instnace หรือ Object บน Python นั่นเอง วันนี้เลยจะมาอธิบายเพิ่มเติมกันว่า ทั้ง 2 Methods นี้มันต่างกันอย่างไร การสร้าง Instance ใน Python มันทำยังไง และเราจะใช้งานมันได้อย่างไรบ้าง

__init__()

เริ่มจากตัวที่เราใช้งานกันบ่อย ๆ ก่อนอย่าง Init หรือบางครั้งเราก็จะเรียกมันว่า Constructor ก็ได้เหมือนกัน โดยที่ Method นี้มันจะถูกเรียกเป็น Method แรกหลังจากที่ Instance ของเราถูกสร้างเรียบร้อยแล้ว โดยที่เราอาจจะมีการเรียกคำสั่งสำหรับการ Init ค่าอะไรบางอย่างก็ได้ เช่นการเพิ่ม Attribute ต่าง ๆ ลงไป

class Customer :
    def __init__ (self, name, surname) :
        self.name = name
        self.surname = surname
        
        print(self.__dict__)

ตัวอย่างด้านบน เราทำการสร้าง Class Customer ขึ้นมา โดยที่เราเรียก init ให้ทำการรับค่าเข้ามาเป็น name และ surname เข้ามา แล้วให้มันเอาไปใส่ใน Attribute ของมัน จากนั้น เพื่อเป็นการเช็คว่า มันเข้าไปอยู่ใน Attribute จริง ๆ เราก็เลยเรียกคำคำสั่ง Dict ออกมา

{'name': 'My name', 'surname' : 'My Surname'}

คำสั่ง Dict นี้ก็เป็น Dunder Method ตัวหนึ่งเหมือนกันที่โดยค่าเริ่มต้น มันจะพ่น Attribute ออกมาเป็น Dictionary ออกมาให้เราได้เลย จากตัวอย่าง เราทดลองสร้าง Object ออกมา เราก็จะเห็นได้เลยว่า มันก็จะพ่น Dictionary ออกมาให้เรา แปลว่า Constructor มันทำงานแน่นอน

แต่เราอยากให้สังเกตตรงที่ว่า init สิ่งที่มันทำจริง ๆ คือ มันจะทำการ Mutate ตัวเอง โดยที่ไม่ได้มีการคืนค่าอะไรกลับไปเลย เราไม่เคยเห็น init ที่คืนค่ากลับไปแน่ ๆ ทำให้เกิดคำถามว่า เอ๊ะ แล้วถ้า Init มันไม่ได้ร้าง Object ออกมา แล้วใครเป็นใครสร้างละ

__new__()

นั่นทำให้เราจะต้องมาทำความรู้จัก Dunder Method อีกหนึ่งตัวคือ new นั่นเอง โดยที่มันมี Signature คือ มันจะต้อง Return Object ของ Class นั้น ๆ กลับไป พร้อมกับมีการรับ Argument เข้ามาด้วย

def __new__(cls, *args, **kwargs):
        obj = object.__new__(cls)
        return obj

จากด้านบน เป็นตัวอย่างที่ง่ายที่สุดสำหรับการ Override new Method ด้วย Default Method นี่แหละง่าย ๆ เลย สั้น ๆ คือ เรารับเข้ามาว่า Class ที่เราจะสร้างมันคืออะไร ส่วน Argument อื่น ๆ เราเขียนเข้ามาเฉย ๆ เป็น Pattern ยังไม่ต้องไปสนใจนะ เดี๋ยวจะ งง จากนั้น เราก็จะเข้าไปสร้าง Instance ขึ้นมาจริง ๆ ละ ผ่าน new Method ของ object อีกที หรือจริง ๆ แล้ว เพื่อให้ อ๋อ เข้าไปอีก เราสามารถแทนที่มันได้ด้วย super().__new__(cls) (เราจะบอกว่าเขียนแบบนี้ดีกว่านะ เพราะว่า ไม่ว่ายังไง แม่มันก็คือ object แน่นอน แต่ถ้าเราเขียนแบบในตัวอย่าง วันนึงคน Maintain Python บอกว่า ไม่เอาละ ไม่อยากให้ชื่อ object แล้วอยากเปลี่ยนเป็นชื่ออื่น เขียนแบบตัวอย่างแตกได้ ใช้แบบที่เราบอกเมื่อครู่รอดง่ายกว่าเยอะ) ได้ เอ๊ะอะไรมั้ยฮ่ะ ใช่แล้ว จริง ๆ แล้ว Object ทั้งหมดที่เราเห็นใน Python มันมีแม่คนเดียวกัน คือ Class ที่ชื่อว่า object

>>> object
<class 'object'>

ถ้าเราลองเช็คว่า object มันเป็นอะไร มันก็จะบอกว่า อ่อ ชั้นเป็น Class แหละ ซึ่งจริง ๆ เราเรียกมันว่า แม่ทุกสถาบันได้เลย เพราะมันเป็นต้นกำเนินแห่ง Object ทั้งปวงใน Python ซึ่งคำสั่ง New ของมัน ก็จะรับ Class เข้าไป แล้วก็จะพ่น Object ของ Class ที่เราใส่เข้าไปกลับมาให้เรานั่นเอง ทำให้เราสามารถเอาตัวแปร obj ไปรับในตัวอย่างก่อนหน้า และ Return กลับไปได้เลยนั่นเอง

กับเราจะบอกว่า จริง ๆ แล้วลองไปเช็คดูได้ new ของ Object กับ new ใน Object ที่เราสร้างมันเป็นตัวเดียวกันเลย

มันทำงานสัมพันธ์กันยังไง ?

อ่านมาแล้ว เออ มันดูเหมือนจะเป็นคนละเรื่องกันเลยนะ อื้ม... ก็ใช่แหละ เพราะมันทำหน้าที่คนละอย่างกัน self เขาทำหน้าที่ในการ Init Object ของเราให้พร้อมใช้งานต่าง ๆ อะไรก็ว่ากันไป แต่ตัวที่สร้าง Object จริง ๆ มันคือ new แต่ถึงแม้ว่า มันจะทำงานกันคนละหน้าที่ แต่มันก็มีความสัมพันธ์กันอยู่อย่างแนบเนียน

เวลาเราเขียน Method ต่าง ๆ สงสัยมั้ยว่า self มันมาจากไหน ??? ทำไม เราไม่เขียนเราจะเรียก Method แล้วพัง มันจะบอกว่า Function นี้รับ Argument 2 ตัว เราใส่เข้ามาตัวเดียว ตอนเราใส่เราก็ใส่เข้ามาตัวเดียวเหมือนใน Signature ที่เราเขียนเลยนะ เอ๊ะอะไรของมัน !!!!

จริง ๆ แล้ว Self ก็ตามชื่อเลย มันก็คือตัว Object เอง ที่พ่นออกมาจาก new Method นี่ละ ตัว Python มันจะต้อง Inject self เข้าไปในทุก ๆ ที่ เพื่อให้เรายังคงเรียกตัวเองได้ ซึ่งมันก็คือตัวเดียวกับ Object ที่เราเรียกจากข้างนอกเลย ตัวอย่างง่าย ๆ

class Employee :
     def __init__ (self, name:str) :
         self.name = name
 
     def get_me (self) :
         return self
 
 emp_a = Employee("Alice")
 print(id(emp_a), id(emp_a.get_me()))

เราสร้าง Class ง่าย ๆ ขึ้นมาตัวนึง โดยที่เราสร้าง Method ตัวนึงคือ get_me สิ่งที่มันทำคือ เราขอ Self ออกมาเลย แล้วเราก็เอา Class นี้แหละไปสร้างเป็น Object ออกมา เราขอ ID ของ emp_a ซึ่งก็คือ Object ที่เราสร้างจาก Class Employee และ self ที่อยู่ใน Object ของ emp_a ผ่าน Method get_me ที่เราสร้างไว้ก่อนหน้านี้

4337806688 4337806688

ผลที่ได้ เราจะเห็นว่า มันเป็นตัวเลขชุดเดียวกันเลย นั่นแปลว่าจริง ๆ แล้ว Object ที่เราเรียกอยู่ข้างนอกนั่นทั้งหมด มันคือตัวเดียวกับ Self ที่อยู่ใน Class ที่เราอ้างถึง 100% เลย แน่นอน เพราะมันคือตัวเดียวกันยังไงละ นั่นแปลว่าอะไร ?

นั่นแปลว่า จริง ๆ แล้ว มันนำไปสู่การตอบคำถามเรื่อง ไก่กับไข่อะไรเกิดก่อนกัน ไม่ใช่ละ แต่ก็ใส่เคียง มันทำให้เราเห็นได้เลยว่าจริง ๆ แล้วในการทำงานของการสร้าง Instance จริง ๆ มันจะเริ่มไปเรียก new Method ที่อยู่ใน Class นั่นแหละ แล้วก็ไปเรียก new ที่อยู่ใน Class object เพื่อที่จะพ่น self หรือก็คือตัว Object จริง ๆ ออกมา จากนั้น Constructor อย่าง init ถึงจะทำงานเป็นไม้ต่อ เพื่อทำการประกาศค่าเริ่มต้นของ Object นั้น ๆ ตามที่เรากำหนดไว้นั่นเอง

สรุป

ดังนั้น ในวันนี้เราสามารถแยกความแตกต่างระหว่าง การสร้าง และ การ Init Object ได้แล้วผ่าน Dunder Method อย่าง new และ init ที่เราคุ้นเคยกันเป็นอย่างดี โดยที่ new จะทำหน้าที่ในการสร้าง instance ขึ้นมาจริง ๆ ผ่านการไปเรียก new จาก object หรือ super() ที่เป็น Class แม่ของมัน จากนั้นค่อยพ่น Class กลับมา แล้วค่อยไปเรียก init อีกรอบเพื่อทำการ Initiate ตามที่เราต้องการนั่นเอง

Read Next...

จัดการเรื่องแต่ละมื้อ แต่ละเดย์ด้วย Obsidian

จัดการเรื่องแต่ละมื้อ แต่ละเดย์ด้วย Obsidian

Obsidian เป็นโปรแกรมสำหรับการจด Note ที่เรียกว่า สารพัดประโยชน์มาก ๆ เราสามารถเอามาทำอะไรได้เยอะมาก ๆ หนึ่งในสิ่งที่เราเอามาทำคือ นำมาใช้เป็นระบบสำหรับการจัดการ Todo List ในแต่ละวันของเรา ทำอะไรบ้าง วันนี้เราจะมาเล่าให้อ่านกันว่า เราจัดการะบบอย่างไร...

Loop แท้ไม่มีอยู่จริง มีแต่ความจริงซึ่งคนโง่ยอมรับไม่ได้

Loop แท้ไม่มีอยู่จริง มีแต่ความจริงซึ่งคนโง่ยอมรับไม่ได้

อะ อะจ๊ะเอ๋ตัวเอง เป็นยังไงบ้างละ เมื่อหลายเดือนก่อน เราไปเล่าเรื่องกันขำ ๆ ว่า ๆ จริง ๆ แล้วพวก Loop ที่เราใช้เขียนโปรแกรมกันอยู่ มันไม่มีอยู่จริง สิ่งที่เราใช้งานกันมันพยายาม Abstract บางอย่างออกไป วันนี้เราจะมาถอดการทำงานของ Loop จริง ๆ กันว่า มันทำงานอย่างไรกันแน่ ผ่านภาษา Assembly...

Monitor การทำงาน MySQL ด้วย Prometheus และ Grafana

Monitor การทำงาน MySQL ด้วย Prometheus และ Grafana

นอกจากการทำให้ Application รันได้แล้ว อีกเรื่องที่สำคัญไม่แพ้กันคือการวางระบบ Monitoring ที่ดี วันนี้เราจะมาแนะนำวิธีการ Monitor การทำงานของ MySQL ผ่านการสร้าง Dashboard บน Grafana กัน...

เสริมความ"แข็งแกร่ง" ให้ SSH ด้วย fail2ban

เสริมความ"แข็งแกร่ง" ให้ SSH ด้วย fail2ban

จากตอนที่แล้ว เราเล่าในเรื่องของการ Harden Security ของ SSH Service ของเราด้วยการปรับการตั้งค่าบางอย่างเพื่อลด Attack Surface ที่อาจจะเกิดขึ้นได้ หากใครยังไม่ได้อ่านก็ย้อนกลับไปอ่านกันก่อนเด้อ วันนี้เรามาเล่าวิธีการที่มัน Advance มากขึ้น อย่างการใช้ fail2ban...