เจาะลึก classes และ objects ใน Python 3

Nuttavut Thongjor

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 จากลิสต์ดังกล่าว

Python
1[1, 2, 3, 4].len()

ช่างโชคร้าย เทพอะมีบาไม่ตอบรับคำขอของคุณ AttributeError: 'list' object has no attribute 'len' จึงเป็นข้อความผิดพลาดที่คุณได้รับ นั่นเพราะลิสต์ไม่มีเมธอด len ยังไงหละ!

Pythonistas ขาร็อกทุกคนทราบกันดีว่าเมื่อเราต้องการหาความยาวลิสต์เราต้องใช้ฟังก์ชัน len ดังนี้

Python
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 อยู่อย่างไรหละ

Python
1[1, 2, 3, 4].__len__() # คำตอบคือ 4

อันความ dunder จึงทำให้เราออกแบบอ็อบเจ็กต์บนข้อกำหนดเดียวกันได้ เช่น เรามีอ็อบเจ็กต์ของคลาสเวกเตอร์ แน่นอนว่าเวกเตอร์แต่ละตัวสามารถบวกกันได้ ภายใต้ผลลัพธ์ของการบวกเราจะได้เวกเตอร์ตัวใหม่ออกมา

Python
1class Vector:
2 def __init__(self, x, y):
3 self.x = x
4 self.y = y
5
6 # นี่คือ dunder add
7 def __add__(self, vector):
8 # การบวกเวกเตอร์คือการสร้างเวกเตอร์ตัวใหม่
9 # โดยเวกเตอร์ผลลัพธ์จะมีจุดเริ่มต้นที่ (0, 0)
10 # แต่มีจุดปลาย ค่า x อยู่ที่ผลบวกของเวกเตอร์ทั้งสอง
11 # ค่า y ก็เช่นกัน
12 return Vector(self.x + vector.x, self.y + vector.y)
13
14vector1 = Vector(3, 4)
15vector2 = Vector(5, 6)
16
17# เมื่อเราต้องการให้เวกเตอร์ + กันได้
18# เราจึงต้องกำหนด __add__ ภายใต้คลาส Vector ของเรา
19vector3 = vector1 + vector2
20
21print(vector3.x, vector3.y) # 8 10

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

การเท่ากันของอ็อบเจ็กต์

ตัวแปรที่เราประกาศขึ้นมานั้นไม่ได้เก็บออบเจ็กต์ครับ หากแต่เก็บ address ของอ็อบเจ็กต์นั้น เราจึงกล่าวได้ว่าออบเจ็กต์ก็เหมือนขวดยา ที่มีตัวยาคือ attributes อยู่ข้างใน แต่หากเราอยากให้ขวดยาถูกอ้างถึงได้ เราก็ต้องมีฉลากยานั่นคือมีตัวแปรมาแปะอยู่หน้าขวดในฐานะของฉลากที่เป็นตัวบ่งชี้ว่าคือขวดยาอะไรนั่นเอง

Variables

Python
1list1 = [1, 2, 3]
2list2 = list1

จากตัวอย่างที่แสดงข้างต้น การประกาศตัวแปร list1 เสมือนเราแปะฉลากยาให้กับออบเจ็กต์ดังกล่าว เมื่อเราให้ตัวแปร list2 มีค่าเท่ากับ list1 จึงเป็นการกล่าวอ้างว่าออบเจ็กต์ดังกล่าวมีฉลากแปะอยู่สองใบ โดยทั้งสองฉลากนั้นต่างอ้างถึงขวดยาคือออบเจ็กต์เดียวกัน

Reference

เราสามารถตรวจสอบความเท่ากันของออบเจ็กต์ได้สองวิธีครับ วิธีแรกคือการใช้เครื่องหมาย == และอีกวิธีคือการใช้ is

Python
1list1 = [1, 2, 3]
2list2 = list1
3
4print(list1 == list2) # True
5print(list1 is list2) # True

เนื่องจาก list1 และ list2 ต่างเป็นฉลากของขวดยาเดียวกันคือ [1, 2, 3] ผลลัพธ์จากการเปรียบเทียบจึงเท่ากัน

ทว่าการใช้ == และ is ไม่ได้ให้ผลลัพธ์เดียวกันเสมอ ใครจะไปสร้างสองสิ่งที่เหมือนกันให้ดูซับซ้อนเล่นๆเป็นดราม่าวุ้นเส้น-เจนนี่หละจริงไหม?

นอกจากชนิดข้อมูลและค่าของออบเจ็กต์แล้ว id ยังใช้เป็นตัวบ่งชี้ความแตกต่างของออบเจ็กต์ได้อีกด้วย

Python
1list1 = [1, 2, 3]
2list2 = [1, 2, 3]
3
4print(id(list1)) # 140444771191816
5print(id(list2)) # 140444780750216

list1 และ list2 แม้จะเก็บลิสต์ที่มีข้อมูลภายในเหมือนๆกัน แต่แท้จริงแล้ว list1 และ list2 ต่างชี้ไปยังคนละออบเจ็กต์

Object Identity

ที่นี้เราลองมาเปรียบเทียบ list1 และ list2 ผ่าน == และ is กันครับ

Python
1list1 = [1, 2, 3]
2list2 = [1, 2, 3]
3
4print(list1 == list2) # True
5print(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 ก็ไม่ได้พิสดารอะไรไปจากภาษาอื่นครับ คลาสยังคงเป็นการประกาศชนิดข้อมูลใหม่และเรายังคงสามารถสร้างออบเจ็กต์หรืออินสแตนท์จากชนิดข้อมูลใหม่นี้ได้

Python
1# ประกาศคลาสก็เหมือนประกาศชนิดข้อมูลใหม่
2class SavingAccount:
3 pass
4
5# เราสามารถสร้างอินสแตนท์จากชนิดข้อมูล SavingAccount ได้
6account = SavingAccount()
7
8print(type(account)) # <class 'SavingAccount'>

ในส่วนของ constructor นั้น ภาษา Python ใช้เมธอดพิเศษคือ __init__ พร้อมรับค่า self ที่หมายถึงออบเจ็กต์ที่ถูกสร้างขณะนั้นเข้ามาด้วย

Python
1class SavingAccount:
2 def __init__(self, balance):
3 # ในจังหวะที่เราสร้างออบเจ็กต์ acc
4 # self จะหมายถึง acc
5 # เป็นผลให้ balance ถูกตั้งค่าติดไว้กับออบเจ็กต์ acc นั่นเอง
6 self.balance = balance
7
8acc = SavingAccount(500)

คลาสของ Python ยังสามารถสร้างเมธอดได้เช่นเดียวกับภาษาอื่นๆ ทำนองเดียวกับ __init__ เรามีความจำเป็นต้องรับ self เข้ามาเป็นพารามิเตอร์ตัวแรกด้วย

Python
1class SavingAccount:
2 def __init__(self, balance):
3 self.balance = balance
4
5 def withdraw(self, amount):
6 self.balance -= amount
7
8 def deposit(self, amount):
9 self.balance += amount
10
11acc = SavingAccount(500)
12acc.deposit(100) # ฝากเพิ่ม 100
13
14print(acc.balance) # 600

เราทราบกันดีจากหัวข้อก่อนหน้าว่าออบเจ็กต์แต่ละตัวก็จะมีพื้นที่เก็บแตกต่างกัน

Python
1acc1 = SavingAccount(500)
2acc2 = SavingAccount(500.0)
3
4print(id(acc1)) # 139645698495040
5print(id(acc2)) # 139645698498512
6
7print(acc1.balance is acc2.balance) # False

เมื่อ acc1 และ acc2 เป็นขวดยาคนละขวดกัน attributes ต่างๆที่เสมือนเป็นตัวยาของขวดยานั้นๆจึงต่างกัน กล่าวคือ acc1.balance จะเป็นคนละตัวกับ acc2.balance แม้ว่าทั้งสองจะมีค่าเท่ากันคือ 500 ก็ตาม

Attributes

Bound Methods

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

Methods

ในภาษา Python เราสามารถใช้ __dict__ ตรวจสอบ attributes ภายในออบเจ็กต์ได้ครับ เราจะใช้ __dict__ เพื่อยืนยันว่าภายใต้อินสแตนท์ออบเจ็กต์ acc1 และ acc2 ไม่ได้เก็บเมธอดไว้กับตัวเอง แต่เมธอดทั้งหลายถูกจัดเก็บในคลาสต่างหาก

Python
1print(acc1.__dict__) # {'balance': 500}
2print(acc2.__dict__) # {'balance': 500.0}
3print(SavingAccount.__dict__)
4
5# {
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__': None
13# }

จากผลลัพธ์ของการเรียก __dict__ เราจะพบว่าภายใต้อินสแตนท์ทั้ง acc1 และ acc2 ต่างไม่มีการจัดเก็บเมธอดไว้กับตัวเองเลย แสดงว่า Python ต้องมีกลไกบางอย่างที่ทำให้เมื่อเราเรียก acc1.deposit แล้วอ้างอิงถึง SavingAccount.deposit แทน นั่นคือ SavingAccount.deposit ควรเท่ากับ acc1.deposit และเท่ากับ acc2.deposit ใช่ไหมหนอ?

Python
1print(SavingAccount.deposit == acc1.deposit) # False
2print(acc1.deposit == acc2.deposit) # False

วั๊ย ตายแล้ว~ ไม่เห็นจะเท่ากันตรงไหนเลย มีแต่ False แบบนี้ อย่าหวังเลยว่าจะได้ค่าแรงขั้นต่ำ 700

เพื่อไขความกระจ่างของปัญหานี้ เรามาดูกันซิว่าอะไรคือปัญหาของเรา

Python
1print(SavingAccount.__dict__['deposit'] is SavingAccount.deposit) # True
2print(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

Python
1class SavingAccount:
2 def __init__(self, balance):
3 self.balance = balance
4
5 def withdraw(self, amount):
6 self.balance -= amount
7
8 def deposit(self, amount):
9 self.balance += amount
10
11acc1 = SavingAccount(500)
12acc2 = SavingAccount(500.0)
13
14print(SavingAccount.deposit(acc1, 100)) # ฝากเงินเข้าบัญชี acc1
15print(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') นั่นเอง

Python
1print(acc1.__getattribute__('deposit')) # <bound method SavingAccount.deposit of <SavingAccount object at 0x7fccc3942b38>>

เมื่อเราทราบแล้วว่ากลไกมหัศจรรย์นั้นอยู่เบื้องหลัง __getattribute__ เราจึงควรศึกษาซักนิดว่าเจ้าเมธอดนี้ทำหน้าที่อย่างไรกันแน่

getattribute และ get

เบื้องหลังการทำงานของ __getattribute__ นั้นค่อนข้างซับซ้อนครับ สมมติว่าตอนนี้เราทำการเรียก acc1.deposit(100) จะมีกลไกแบบนี้เกิดขึ้น

  1. เจ้าตัวจะเริ่มมองหา attribute คือ deposit จากออบเจ็กต์ acc1 ก่อน มองหาจากไหนนะรึ ก็จาก acc1.__dict__ ยังไงละ
  2. ช่างโชคร้ายที่เมธอดไม่ได้จัดเก็บอยู่ในออบเจ็กต์ Python จึงคาดเดาว่า deposit ต้องเก็บอยู่ใต้คลาสแน่ๆเลย ฉลาดนะเราเนี่ย
  3. Python จึงเรียก acc1.__class__.__dict__['deposit'] เพื่อเข้าถึง deposit จากในคลาส
  4. โชคดีอิ๊บอ๋ายที่ deposit อยู่ในคลาสพอดี เรามาถูกที่แล้วหละ จากนั้น Python จะเช็คผลลัพธ์จากข้อ 3 ว่ามี __get__ ไหม
  5. ปรากฎว่ามีเแฮะ ผลลัพธ์จากการ __get__ เราจะได้ method wrapper ซึ่งชื่อมันก็บอกอยู่แล้วว่าคืออะไร เราจึงสามารถส่ง acc1 ในฐานะของออบเจ็กต์เข้าไปใน method wrapper นี้ได้ผ่าน acc1.__class__.__dict__['deposit'].__get__(acc1)
  6. สิ้นสุดกระบวนการที่ 5 เราก็จะได้ bound method ของ deposit ออกมา
Python
1print(acc1.__class__.__dict__['deposit'].__get__(acc1))
2# <bound method SavingAccount.deposit of <SavingAccount object at 0x7fbef54eaa90>>

นี่หละครับคือกลไกอันซับซ้อนของ __getattribute__

Class Attributes

Attributes แบบปกตินั้นเราสามารถเข้าถึงได้ผ่านอินสแตนท์

Python
1class SavingAccount:
2 def __init__(self, balance):
3 self.balance = balance
4
5 def withdraw(self, amount):
6 self.balance -= amount
7
8 def deposit(self, amount):
9 self.balance += amount
10
11acc = SavingAccount(500)
12acc.balance # เข้าถึง attribute ผ่านออบเจ็กต์

ทว่าก็มีบางกรณีที่เราอยากให้ attribute ถูกเข้าถึงได้ผ่านชื่อคลาส เช่น เรากำหนดให้บัญชีเงินฝากออมทรัพย์เปิดได้สูงสุดที่ 10 บัญชี ลักษณะแบบนี้เราต้องเอา attribute ไปฝากไว้กับคลาสแล้วละ

Python
1class SavingAccount:
2 amount = 10
3
4 def __init__(self, balance):
5 self.balance = balance
6
7 def withdraw(self, amount):
8 self.balance -= amount
9
10 def deposit(self, amount):
11 self.balance += amount
12
13# วันดีคืนดีอยากเปลี่ยนให้จำนวนสูงสุดเป็น 20 ก็ย่อมทำได้
14SavingAccount.amount = 20
15
16print(SavingAccount.amount) # 20

แต่ class attributes ไม่ได้เข้าถึงได้จากเฉพาะคลาสเท่านั้น!

Python
1class SavingAccount:
2 amount = 20
3
4 def __init__(self, balance):
5 self.balance = balance
6
7 def withdraw(self, amount):
8 self.balance -= amount
9
10 def deposit(self, amount):
11 self.balance += amount
12
13acc = SavingAccount(500)
14
15print(acc.amount) # 20

จากตัวอย่างข้างต้นเราพบว่าแม้ amount จะเป็น class attribute แต่ acc ซึ่งเป็นอินสแตนท์ก็ยังคงเข้าถึงได้ นั่นเพราะการเรียก attribute ใดๆจากออบเจ็กต์คือการเรียกใช้ __getattribute__ นั่นเอง

เมื่อ acc.amount หมายถึง acc.__getattribute__('amount') มันจึงเข้าสู่กระบวนการที่ผมกล่าวไว้ในหัวข้อก่อนหน้า เพราะว่าบน acc ไม่มี amount อยู่ Python จึงวิ่งไปดู amount จาก __class__ ปรากฎว่าเจอด้วยแฮะ จึงนำค่านี้มาใช้โดยพลัน

สำหรับการอ่านค่าเป็นไปตามกฎเกณฑ์ข้างต้น แต่ไม่ใช่สำหรับการเขียนค่าทับ

Python
1class SavingAccount:
2 amount = 0
3
4 def __init__(self, balance):
5 self.balance = balance
6
7 def withdraw(self, amount):
8 self.balance -= amount
9
10 def deposit(self, amount):
11 self.balance += amount
12
13acc = SavingAccount(500)
14acc.amount = 20
15
16print(acc.amount) # 20
17print(SavingAccount.amount) # 0
18print(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 ได้ดังนี้

Python
1class SavingAccount:
2 @classmethod
3 def get_interest_rate(cls):
4 print(cls) # <class 'SavingAccount'>
5 return 0.000001
6
7acc = SavingAccount()
8
9print(SavingAccount.get_interest_rate()) # 1e-06
10print(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
  • สรุป
  • เอกสารอ้างอิง