เจาะลึก classes และ objects ใน Python 3
Object Oriented Programming (OOP) เป็นหลักการที่ขยายพันธุ์ไปในหลายๆภาษาอย่างรวดเร็ว ราวกับหมู่อะมีบาในหน้าฝน Python ก็เป็นหนึ่งในภาษาที่โดนวิญญาณอะมีบาเข้าสิง เราจึงได้เห็นความเป็น OOP ฉบับ Python อย่างที่มีและไม่มีในภาษาอื่น
ใกล้เกินโควต้าอ่าน 7 บรรทัดต่อปีแล้วใช่ไหม? ตัดบท Intro หน่อมแน้ม แล้วไปลุยกับหลักการ OOP ใน Python 3 กันเถอะ
Dunder Methods
ทุกรูปนามใน Python ล้วนเป็นอ็อบเจ็กต์ ไม่ว่าจะเป็น built-in types อย่าง int และ float หรือจะเป็นชนิดข้อมูลที่เราสร้างขึ้นมาใหม่ ไม่ว่าสิ่งนั้นจะเป็นอะไร ทั้งหมดทั้งมวลล้วนเป็นอ็อบเจ็กต์
[1, 2, 3, 4] เป็นลิสต์ในภาษา Python จากข้อมูลข้างต้นเราจึงสรุปได้ว่าลิสต์ย่อมเป็นอ็อบเจ็กต์เช่นกัน
อาศัยความรู้จาก OOP ภาษาอื่น หากเราต้องการหาความยาว (length หรือ len) ของลิสต์ เราก็ต้องเรียก instance method ชื่อ len จากลิสต์ดังกล่าว
1[1, 2, 3, 4].len()
ช่างโชคร้าย เทพอะมีบาไม่ตอบรับคำขอของคุณ AttributeError: 'list' object has no attribute 'len'
จึงเป็นข้อความผิดพลาดที่คุณได้รับ นั่นเพราะลิสต์ไม่มีเมธอด len ยังไงหละ!
Pythonistas ขาร็อกทุกคนทราบกันดีว่าเมื่อเราต้องการหาความยาวลิสต์เราต้องใช้ฟังก์ชัน len ดังนี้
1len([1, 2, 3, 4]) # ผลลัพธ์คือ 4
ทำไม Python ต้องทำอะไรให้ยุ่งยากด้วย? แทนที่ลิสต์จะมีเมธอดชื่อ len กลับต้องมาใช้ฟังก์ชันอนาถา อะไรคือ OOP จ๋าของภาษา Python?
ภาษา Python จะมีกลุ่มของเมธอดพิเศษ เมธอดเหล่านี้จะขึ้นต้นและลงท้ายด้วย __
เช่น __len__
บางคนอาจอ่านออกเสียงว่า ดับเบิ้ลอันเดอร์สกอร์ len ดับเบิ้ลอันเดอร์สกอร์
โอ๊ยแค่จะออกเสียงที ลมปราณเข้าแทรก ลมตดออกปู๊ด ไม่ไหวๆ
เมธอดพิเศษเหล่านี้มีชื่อเรียกกิ๊บเก๋ที่เรานิยมกันคือ dunder methods ครับ เช่น __len__
เราก็จะอ่านว่า ดันเดอ len
หรือใครจะใส่เสียงอีสานซะหน่อยก็จะเป็น ดันเด้อออ len
Dunder methods นี้ถือเป็นข้อกำหนดที่จะสร้างสีสันให้กับ Python ของเราเช่น หากอ็อบเจ็กต์ไหนต้องการให้ถูกเรียกผ่านฟังก์ชัน len ได้ อ็อบเจ็กต์นั้นต้องมีการประกาศเมธอด len ขึ้นมาเสียก่อน ถึงตอนนี้พอเดากันได้แล้วใช่ไหมครับว่าทำไมเราถึงเรียก len จาก [1, 2, 3, 4] ได้ นั่นเพราะลิสต์ดังกล่าวมีเมธอด len อยู่อย่างไรหละ
1[1, 2, 3, 4].__len__() # คำตอบคือ 4
อันความ dunder จึงทำให้เราออกแบบอ็อบเจ็กต์บนข้อกำหนดเดียวกันได้ เช่น เรามีอ็อบเจ็กต์ของคลาสเวกเตอร์ แน่นอนว่าเวกเตอร์แต่ละตัวสามารถบวกกันได้ ภายใต้ผลลัพธ์ของการบวกเราจะได้เวกเตอร์ตัวใหม่ออกมา
1class Vector:2 def __init__(self, x, y):3 self.x = x4 self.y = y56 # นี่คือ dunder add7 def __add__(self, vector):8 # การบวกเวกเตอร์คือการสร้างเวกเตอร์ตัวใหม่9 # โดยเวกเตอร์ผลลัพธ์จะมีจุดเริ่มต้นที่ (0, 0)10 # แต่มีจุดปลาย ค่า x อยู่ที่ผลบวกของเวกเตอร์ทั้งสอง11 # ค่า y ก็เช่นกัน12 return Vector(self.x + vector.x, self.y + vector.y)1314vector1 = Vector(3, 4)15vector2 = Vector(5, 6)1617# เมื่อเราต้องการให้เวกเตอร์ + กันได้18# เราจึงต้องกำหนด __add__ ภายใต้คลาส Vector ของเรา19vector3 = vector1 + vector22021print(vector3.x, vector3.y) # 8 10
จากตัวอย่างก่อนหน้าเพื่อนๆจะพบว่า ความ dunder ทำให้เราสามารถใช้ของที่มีอยู่แล้วคือเครื่องหมายบวก ให้เป็นมาตรฐานของการบวกในแบบเวกเตอร์โดยไม่ต้องนิยามเมธอดใหม่ขึ้นมา ลั๊ลลา~ อะมีบาถูกใจ
การเท่ากันของอ็อบเจ็กต์
ตัวแปรที่เราประกาศขึ้นมานั้นไม่ได้เก็บออบเจ็กต์ครับ หากแต่เก็บ address ของอ็อบเจ็กต์นั้น เราจึงกล่าวได้ว่าออบเจ็กต์ก็เหมือนขวดยา ที่มีตัวยาคือ attributes อยู่ข้างใน แต่หากเราอยากให้ขวดยาถูกอ้างถึงได้ เราก็ต้องมีฉลากยานั่นคือมีตัวแปรมาแปะอยู่หน้าขวดในฐานะของฉลากที่เป็นตัวบ่งชี้ว่าคือขวดยาอะไรนั่นเอง
1list1 = [1, 2, 3]2list2 = list1
จากตัวอย่างที่แสดงข้างต้น การประกาศตัวแปร list1 เสมือนเราแปะฉลากยาให้กับออบเจ็กต์ดังกล่าว เมื่อเราให้ตัวแปร list2 มีค่าเท่ากับ list1 จึงเป็นการกล่าวอ้างว่าออบเจ็กต์ดังกล่าวมีฉลากแปะอยู่สองใบ โดยทั้งสองฉลากนั้นต่างอ้างถึงขวดยาคือออบเจ็กต์เดียวกัน
เราสามารถตรวจสอบความเท่ากันของออบเจ็กต์ได้สองวิธีครับ วิธีแรกคือการใช้เครื่องหมาย ==
และอีกวิธีคือการใช้ is
1list1 = [1, 2, 3]2list2 = list134print(list1 == list2) # True5print(list1 is list2) # True
เนื่องจาก list1 และ list2 ต่างเป็นฉลากของขวดยาเดียวกันคือ [1, 2, 3]
ผลลัพธ์จากการเปรียบเทียบจึงเท่ากัน
ทว่าการใช้ ==
และ is
ไม่ได้ให้ผลลัพธ์เดียวกันเสมอ ใครจะไปสร้างสองสิ่งที่เหมือนกันให้ดูซับซ้อนเล่นๆเป็นดราม่าวุ้นเส้น-เจนนี่หละจริงไหม?
นอกจากชนิดข้อมูลและค่าของออบเจ็กต์แล้ว id ยังใช้เป็นตัวบ่งชี้ความแตกต่างของออบเจ็กต์ได้อีกด้วย
1list1 = [1, 2, 3]2list2 = [1, 2, 3]34print(id(list1)) # 1404447711918165print(id(list2)) # 140444780750216
list1 และ list2 แม้จะเก็บลิสต์ที่มีข้อมูลภายในเหมือนๆกัน แต่แท้จริงแล้ว list1 และ list2 ต่างชี้ไปยังคนละออบเจ็กต์
ที่นี้เราลองมาเปรียบเทียบ list1 และ list2 ผ่าน ==
และ is
กันครับ
1list1 = [1, 2, 3]2list2 = [1, 2, 3]34print(list1 == list2) # True5print(list1 is list2) # False
หืม ==
และ is
ให้ผลลัพธ์ไม่เหมือนกันแฮะ?
สมชายและสมศรีต่างเป็นมนุษย์ทั้งคู่ เราจึงกล่าวได้ว่าสมชายและสมศรีมีความเป็นคนเท่ากัน (==
) แม้สมศรีจะไปอยู่ดูไบแต่สมชายก็ยังคิดถึง แม้เขาทั้งคู่เป็นคนเหมือนกัน แต่สมชายและสมศรีไม่ได้เป็น (is
) คนคนเดียวกันครับ (ถึงชื่อจะขึ้นต้นด้วย ป.ปลา เอ๊ย ส.เสือ เหมือนกันก็เถอะ)
เช่นเดียวกัน list1 และ list2 ต่างเป็นลิสต์และมีค่าข้อมูลเท่ากัน เราจึงกล่าวได้ว่า list1 == list2
แต่ทั้งสองลิสต์นั้นเป็นคนละออบเจ็กต์กัน เราจึงกล่าวได้ว่า list1 is not list2
เบื้องหลังการถ่ายทำ การใช้งาน is
Python จะทำการเรียก id()
อีกทีนึง ดังนั้นออบเจ็กต์ที่ต่างกันจึงมี id ที่แปลกแยก ผิดกับการใช้ ==
ที่ Python จะทำการเรียก __eq__
ดังนั้นการเรียก list1 == list2
จึงมีความหมายเดียวกับ list1.__eq__(list2)
นั่นเอง
เพราะว่า __eq__
ของลิสต์นั้นจะคืนค่า True เมื่อทั้งสองลิสต์ที่นำมาเปรียบเทียบมีค่าข้อมูลเท่ากัน เราจึงได้ว่าการใช้งาน ==
ในตัวอย่างนี้จึงให้ผลลัพธ์เป็น Truemove H 4G+
Classes และ Attributes
หลักการของการสร้างคลาสในภาษา Python ก็ไม่ได้พิสดารอะไรไปจากภาษาอื่นครับ คลาสยังคงเป็นการประกาศชนิดข้อมูลใหม่และเรายังคงสามารถสร้างออบเจ็กต์หรืออินสแตนท์จากชนิดข้อมูลใหม่นี้ได้
1# ประกาศคลาสก็เหมือนประกาศชนิดข้อมูลใหม่2class SavingAccount:3 pass45# เราสามารถสร้างอินสแตนท์จากชนิดข้อมูล SavingAccount ได้6account = SavingAccount()78print(type(account)) # <class 'SavingAccount'>
ในส่วนของ constructor นั้น ภาษา Python ใช้เมธอดพิเศษคือ __init__
พร้อมรับค่า self ที่หมายถึงออบเจ็กต์ที่ถูกสร้างขณะนั้นเข้ามาด้วย
1class SavingAccount:2 def __init__(self, balance):3 # ในจังหวะที่เราสร้างออบเจ็กต์ acc4 # self จะหมายถึง acc5 # เป็นผลให้ balance ถูกตั้งค่าติดไว้กับออบเจ็กต์ acc นั่นเอง6 self.balance = balance78acc = SavingAccount(500)
คลาสของ Python ยังสามารถสร้างเมธอดได้เช่นเดียวกับภาษาอื่นๆ ทำนองเดียวกับ __init__
เรามีความจำเป็นต้องรับ self เข้ามาเป็นพารามิเตอร์ตัวแรกด้วย
1class SavingAccount:2 def __init__(self, balance):3 self.balance = balance45 def withdraw(self, amount):6 self.balance -= amount78 def deposit(self, amount):9 self.balance += amount1011acc = SavingAccount(500)12acc.deposit(100) # ฝากเพิ่ม 1001314print(acc.balance) # 600
เราทราบกันดีจากหัวข้อก่อนหน้าว่าออบเจ็กต์แต่ละตัวก็จะมีพื้นที่เก็บแตกต่างกัน
1acc1 = SavingAccount(500)2acc2 = SavingAccount(500.0)34print(id(acc1)) # 1396456984950405print(id(acc2)) # 13964569849851267print(acc1.balance is acc2.balance) # False
เมื่อ acc1 และ acc2 เป็นขวดยาคนละขวดกัน attributes ต่างๆที่เสมือนเป็นตัวยาของขวดยานั้นๆจึงต่างกัน กล่าวคือ acc1.balance จะเป็นคนละตัวกับ acc2.balance แม้ว่าทั้งสองจะมีค่าเท่ากันคือ 500 ก็ตาม
Bound Methods
Attributes นั้นเป็นตัวยาที่จำเพาะกับขวดยานั้นๆ มันจึงถูกจัดเก็บไว้ในพื้นที่ของขวดยานั้นๆแยกจากยาขวดอื่นๆ แต่สำหรับเมธอดมันคือสิ่งที่สามารถใช้ร่วมกันได้จากหลายๆออบเจ็กต์ เมื่อเป็นเช่นนี้ Python จึงเลือกเก็บเมธอดไว้ในอ้อมกอดของคลาส
ในภาษา Python เราสามารถใช้ __dict__
ตรวจสอบ attributes ภายในออบเจ็กต์ได้ครับ เราจะใช้ __dict__
เพื่อยืนยันว่าภายใต้อินสแตนท์ออบเจ็กต์ acc1 และ acc2 ไม่ได้เก็บเมธอดไว้กับตัวเอง แต่เมธอดทั้งหลายถูกจัดเก็บในคลาสต่างหาก
1print(acc1.__dict__) # {'balance': 500}2print(acc2.__dict__) # {'balance': 500.0}3print(SavingAccount.__dict__)45# {6# '__module__': 'builtins',7# '__init__': <function SavingAccount.__init__ at 0x7ff92e319620>,8# 'withdraw': <function SavingAccount.withdraw at 0x7ff92e3196a8>,9# 'deposit': <function SavingAccount.deposit at 0x7ff92e319730>,10# '__dict__': <attribute '__dict__' of 'SavingAccount' objects>,11# '__weakref__': <attribute '__weakref__' of 'SavingAccount' objects>,12# '__doc__': None13# }
จากผลลัพธ์ของการเรียก __dict__
เราจะพบว่าภายใต้อินสแตนท์ทั้ง acc1 และ acc2 ต่างไม่มีการจัดเก็บเมธอดไว้กับตัวเองเลย แสดงว่า Python ต้องมีกลไกบางอย่างที่ทำให้เมื่อเราเรียก acc1.deposit แล้วอ้างอิงถึง SavingAccount.deposit แทน นั่นคือ SavingAccount.deposit ควรเท่ากับ acc1.deposit และเท่ากับ acc2.deposit ใช่ไหมหนอ?
1print(SavingAccount.deposit == acc1.deposit) # False2print(acc1.deposit == acc2.deposit) # False
วั๊ย ตายแล้ว~ ไม่เห็นจะเท่ากันตรงไหนเลย มีแต่ False แบบนี้ อย่าหวังเลยว่าจะได้ค่าแรงขั้นต่ำ 700
เพื่อไขความกระจ่างของปัญหานี้ เรามาดูกันซิว่าอะไรคือปัญหาของเรา
1print(SavingAccount.__dict__['deposit'] is SavingAccount.deposit) # True2print(SavingAccount.deposit) # <function SavingAccount.deposit at 0x7fa1e7b60730>3print(acc1.deposit) # <bound method SavingAccount.deposit of <SavingAccount object at 0x7fa1e7b4ba90>>
ผลลัพธ์จากการทำงานทำให้เราทราบว่า SavingAccount.deposit นั้นเป็นฟังก์ชัน ในขณะที่ acc1.deposit นั้นเป็น bound method
ตามหลักการแล้วฟังก์ชันหมายถึงการประกาศที่ขึ้นต้นด้วย def
SavingAccount.deposit จึงมีสถานะเป็นฟังก์ชัน แต่เมื่อไหร่ก็ตามที่ฟังก์ชันนั้นอยู่ภายใต้คลาสเป็นผลให้เราสามารถเข้าถึงฟังก์ชันนี้ได้ผ่านออบเจ็กต์ เราจะเรียกฟังก์ชันนี้ว่า method เช่น acc1.deposit(100)
หากเราสังเกตซักนิด การประกาศเมธอดของเราจะรับ self เข้ามาเป็นพารามิเตอร์ตัวแรก เมื่อเราพิจารณาจากรูปแบบของเมธอด เราจึงสามารถส่งออบเจ็กต์เข้าไปโดยตรงสู่เมธอดได้ในฐานะของ self
1class SavingAccount:2 def __init__(self, balance):3 self.balance = balance45 def withdraw(self, amount):6 self.balance -= amount78 def deposit(self, amount):9 self.balance += amount1011acc1 = SavingAccount(500)12acc2 = SavingAccount(500.0)1314print(SavingAccount.deposit(acc1, 100)) # ฝากเงินเข้าบัญชี acc115print(acc1.balance) # 600
SavingAccount.deposit(acc1, 100) นั้นเป็นวิธีการเรียกที่เราต้องส่ง acc1 เข้าไปโดยตรงในฐานะของ self แน่นอนว่าวิธีการนี้เป็นอะไรที่ยุ่งยาก Python จึงมีอีกวิธีในการจัดการกับเมธอด นั่นคือทำการจับคู่ออบเจ็กต์ให้เป็นค่า self โดยอัตโนมัติ นี่หละคือสิ่งที่เรียกว่าเป็น bound method ไงหละ
acc1.deposit เป็น bound method นั่นเพราะ Python จัดการผูกค่า acc1 ให้เป็นค่าของ self สำหรับเมธอด deposit เป็นที่เรียบร้อย
ถ้าเรายังจำกันได้ acc1.__dict__
นั้นไม่มีค่าของ deposit อยู่เลย เมื่อเราเรียก acc1.deposit ทำไมเราจึงเรียกใช้มันได้ Python มีกลไกพิเศษอันใดหนอถึงเสกควายให้เป็นคนได้
หลังฉาก AV ย่อมมีกองถ่าย Python นั้นเรียก __getattribute__()
เมื่อเราเรียก attribute ใดๆจากออบเจ็กต์นั้น ฉะนั้นแล้วการเรียก acc1.deposit
จึงหมายถึง acc1.__getattribute__('deposit')
นั่นเอง
1print(acc1.__getattribute__('deposit')) # <bound method SavingAccount.deposit of <SavingAccount object at 0x7fccc3942b38>>
เมื่อเราทราบแล้วว่ากลไกมหัศจรรย์นั้นอยู่เบื้องหลัง __getattribute__
เราจึงควรศึกษาซักนิดว่าเจ้าเมธอดนี้ทำหน้าที่อย่างไรกันแน่
getattribute และ get
เบื้องหลังการทำงานของ __getattribute__
นั้นค่อนข้างซับซ้อนครับ สมมติว่าตอนนี้เราทำการเรียก acc1.deposit(100)
จะมีกลไกแบบนี้เกิดขึ้น
- เจ้าตัวจะเริ่มมองหา attribute คือ deposit จากออบเจ็กต์ acc1 ก่อน มองหาจากไหนนะรึ ก็จาก
acc1.__dict__
ยังไงละ - ช่างโชคร้ายที่เมธอดไม่ได้จัดเก็บอยู่ในออบเจ็กต์ Python จึงคาดเดาว่า deposit ต้องเก็บอยู่ใต้คลาสแน่ๆเลย ฉลาดนะเราเนี่ย
- Python จึงเรียก
acc1.__class__.__dict__['deposit']
เพื่อเข้าถึง deposit จากในคลาส - โชคดีอิ๊บอ๋ายที่ deposit อยู่ในคลาสพอดี เรามาถูกที่แล้วหละ จากนั้น Python จะเช็คผลลัพธ์จากข้อ 3 ว่ามี
__get__
ไหม - ปรากฎว่ามีเแฮะ ผลลัพธ์จากการ
__get__
เราจะได้ method wrapper ซึ่งชื่อมันก็บอกอยู่แล้วว่าคืออะไร เราจึงสามารถส่ง acc1 ในฐานะของออบเจ็กต์เข้าไปใน method wrapper นี้ได้ผ่านacc1.__class__.__dict__['deposit'].__get__(acc1)
- สิ้นสุดกระบวนการที่ 5 เราก็จะได้ bound method ของ deposit ออกมา
1print(acc1.__class__.__dict__['deposit'].__get__(acc1))2# <bound method SavingAccount.deposit of <SavingAccount object at 0x7fbef54eaa90>>
นี่หละครับคือกลไกอันซับซ้อนของ __getattribute__
Class Attributes
Attributes แบบปกตินั้นเราสามารถเข้าถึงได้ผ่านอินสแตนท์
1class SavingAccount:2 def __init__(self, balance):3 self.balance = balance45 def withdraw(self, amount):6 self.balance -= amount78 def deposit(self, amount):9 self.balance += amount1011acc = SavingAccount(500)12acc.balance # เข้าถึง attribute ผ่านออบเจ็กต์
ทว่าก็มีบางกรณีที่เราอยากให้ attribute ถูกเข้าถึงได้ผ่านชื่อคลาส เช่น เรากำหนดให้บัญชีเงินฝากออมทรัพย์เปิดได้สูงสุดที่ 10 บัญชี ลักษณะแบบนี้เราต้องเอา attribute ไปฝากไว้กับคลาสแล้วละ
1class SavingAccount:2 amount = 1034 def __init__(self, balance):5 self.balance = balance67 def withdraw(self, amount):8 self.balance -= amount910 def deposit(self, amount):11 self.balance += amount1213# วันดีคืนดีอยากเปลี่ยนให้จำนวนสูงสุดเป็น 20 ก็ย่อมทำได้14SavingAccount.amount = 201516print(SavingAccount.amount) # 20
แต่ class attributes ไม่ได้เข้าถึงได้จากเฉพาะคลาสเท่านั้น!
1class SavingAccount:2 amount = 2034 def __init__(self, balance):5 self.balance = balance67 def withdraw(self, amount):8 self.balance -= amount910 def deposit(self, amount):11 self.balance += amount1213acc = SavingAccount(500)1415print(acc.amount) # 20
จากตัวอย่างข้างต้นเราพบว่าแม้ amount จะเป็น class attribute แต่ acc ซึ่งเป็นอินสแตนท์ก็ยังคงเข้าถึงได้ นั่นเพราะการเรียก attribute ใดๆจากออบเจ็กต์คือการเรียกใช้ __getattribute__
นั่นเอง
เมื่อ acc.amount หมายถึง acc.__getattribute__('amount')
มันจึงเข้าสู่กระบวนการที่ผมกล่าวไว้ในหัวข้อก่อนหน้า เพราะว่าบน acc ไม่มี amount อยู่ Python จึงวิ่งไปดู amount จาก __class__
ปรากฎว่าเจอด้วยแฮะ จึงนำค่านี้มาใช้โดยพลัน
สำหรับการอ่านค่าเป็นไปตามกฎเกณฑ์ข้างต้น แต่ไม่ใช่สำหรับการเขียนค่าทับ
1class SavingAccount:2 amount = 034 def __init__(self, balance):5 self.balance = balance67 def withdraw(self, amount):8 self.balance -= amount910 def deposit(self, amount):11 self.balance += amount1213acc = SavingAccount(500)14acc.amount = 201516print(acc.amount) # 2017print(SavingAccount.amount) # 018print(acc.__dict__) # {'balance': 500, 'amount': 20}
การที่เรากำหนดค่า amount ให้กับ acc มันไม่ใช่การกำหนดค่าให้ amount ของคลาสนะครับ ผลลัพธ์ที่ได้จึงเป็นการสร้าง attribute ใหม่ให้กับ acc ในชื่อของ amount นั่นเอง
Class Methods
เมื่อมี class attributes แล้ว จะไม่ให้มี class methods ก็กระไรอยู่
เราสามารถสร้าง class methods ได้ด้วยการใช้ decorator ที่มีชื่อว่า classmethod
ครับ
สมมติว่าบัญชีออมทรัพย์มีดอกเบี้ยไม่คงตัว เราต้องการสร้าง class method ชื่อ get_interest_rate
เพื่อใช้เรียกเมื่อต้องการทราบค่าของดอกเบี้ยเงินฝาก เราก็สามารถทำผ่าน decorator ได้ดังนี้
1class SavingAccount:2 @classmethod3 def get_interest_rate(cls):4 print(cls) # <class 'SavingAccount'>5 return 0.00000167acc = SavingAccount()89print(SavingAccount.get_interest_rate()) # 1e-0610print(acc.get_interest_rate()) # 1e-06
แหมะช่างเป็นเกณฑ์ดอกเบี้ยที่น่าตบหัวทิ่มมาก!
โปรดสังเกตนะครับ class methods เราจะมีการรับ class
เข้ามาเป็นพารามิเตอร์ด้วย เหมือนที่เรารับ self ที่เป็นอินสแตนท์เข้ามาในเมธอดธรรมดา
สรุป
Python นั้นมีกลไกของ OOP ที่น่าสนใจครับ รายละเอียดก็ค่อนข้างเยอะจนผมใส่ในบทความเดียวไม่หมด เอาเป็นว่าผมจะนำเสนอฟีเจอร์อื่นๆทางการโปรแกรมเชิงวัตถุด้วยภาษา Python ในบทความถัดๆไปแล้วกัน ตอนนี้ตายแผล็บ พิมพ์จนนิ้วหงิกกันทีเดียว~
เอกสารอ้างอิง
Luciano Ramalho. (2015). Fluent Python Clear, Concise, and Effective Programming. Boston: O'Reilly Media.
Leonardo Giordani (2014). Python 3 OOP Part 2 - Classes and members'. Retrieved September, 12, 2017, from http://blog.thedigitalcatonline.com/blog/2014/08/20/python-3-oop-part-2-classes-and-members/#.WbZUzcgjHIV
สารบัญ
- Dunder Methods
- การเท่ากันของอ็อบเจ็กต์
- Classes และ Attributes
- Bound Methods
- getattribute และ get
- Class Attributes
- Class Methods
- สรุป
- เอกสารอ้างอิง