ใน
บทที่แล้วได้พูดถึงการประยุกต์ใช้เดคอเรเตอร์กับการแต่งคลาสไปแล้ว ส่วนในบทนี้จะพูดถึงฟังก์ชัน
classmethod
และ
staticmethod
ซึ่งเป็นฟังก์ชันมาตรฐานที่นิยมนำมาใช้เพื่อตกแต่งเมธอดภายในคลาส
การใช้ ๒ ฟังก์ชันนี้มาตกแต่งจะทำให้เราได้เมธอดที่มีคุณสมบัติแตกต่างไปจากเมธอดธรรมดาที่ประกาศขึ้นเฉยๆโดยไม่ได้ใช้เดคอเรเตอร์
classmethod ดังที่ได้พูดถึงไปใน
บทที่ ๒๒ และในตัวอย่างที่แล้ว ฟังก์ช้นที่ประกาศภายในคลาสจะกลายเป็นเมธอดของออบเจ็กต์ที่เป็นอินสแตนซ์ของ คลาสนั้น แต่ไม่ใช่เมธอดของตัวคลาสเอง เมธอดนั้นจะไม่สามารถใช้กับตัวคลาสเองได้
หากต้องการจะสร้างเมธอดที่สามารถใช้กับตัวคลาสได้จะต้องทำอะไรบางอย่างเพิ่มเติม นั่นคือการใช้ฟังก์ชันที่ชื่อ
classmethod
วิธีการใช้ก็คือประกาศเมธอดตามปกติด้วย
def
จากนั้นก็ใช้
classmethod
โดย
ชื่อเมธอด = classmethod(ชื่อเมธอด)
ซึ่งการทำแบบนี้ก็คือการใช้ฟังก์ชัน
classmethod
เป็นเดคอเรเตอร์ ดังนั้นโดยทั่วไปแล้วจะนิยมใช้
@
มากกว่า นั่นคือ
@classmethod
def ชื่อเมธอด(self,พารามิเตอร์ที่เหลือ):
พารามิเตอร์ตัวแรกจะแทนคลาส ส่วนตัวที่เหลือก็คืออาร์กิวเมนต์หรือคีย์เวิร์ดที่ต้องป้อนค่าตอนเรียกใช้
เมธอดของคลาสสามารถใช้ได้กับทั้งคลาสและออบเจ็กต์ที่เป็นอินสแตนซ์ของคลาส อย่างไรก็ตามผลจากการใช้จะไม่ต่างกัน คือเป็นเมธอดที่กระทำต่อตัวคลาส และ
self
ก็จะแทนตัวคลาสเสมอ แม้ว่าเมธอดจะถูกเรียกใช้จากอินสแตนซ์ก็ตาม
ตัวอย่างการใช้
class Cla:
a = 'แอตทริบิวต์ a ของคลาส Cla'
@classmethod
def metamon(self):
return [self,self.a]
print(Cla.metamon()) # ได้ [<class '__main__.Cla'>, 'แอตทริบิวต์ a ของคลาส Cla']
ในตัวอย่างนี้สร้างเมธอดชื่อ
metamon
ขึ้นมาเป็นเมธอดของคลาส
Cla
ทำงานโดยคืนตัวคลาสและแอตทริบิวต์
a
ออกมา
จากนั้นหากเราลองสร้างอินสแตนซ์ของคลาสนี้ออกมาแล้วใช้เมธอดเดียวกันนี้ดู
c = Cla()
c.a = 'แอตทริบิวต์ a ของอินสแตนซ์ c'
print(Cla.a) # ได้ แอตทริบิวต์ a ของคลาส Cla
print(c.a) # ได้ แอตทริบิวต์ a ของอินสแตนซ์ c
print(c.metamon()) # ได้ [<class '__main__.Cla'>, 'แอตทริบิวต์ a ของคลาส Cla']
จะเห็นว่าพอใช้เมธอด
metamon
ผลที่ได้ก็ยังคงออกมาเป็นตัวคลาสและแอตทริบิวต์
a
ของคลาส แทนที่จะเป็นตัวอินสแตนซ์และแอตทริบิวต์
a
ของอินสแตนซ์
แต่ถ้าหากเมธอดนี้ถูกประกาศโดยที่ไม่ใส่
@classmethod
ละก็ผลที่ได้ก็จะเป็นของตัวอินสแตนซ์เองแทน
ขอยกตัวอย่างการใช้งานเพื่อให้เห็นภาพยิ่งขึ้น
ใน
บทที่ ๒๒ ได้ลองสร้างคลาส
ผู้กล้า
ขึ้นมาเพื่อใช้เป็นตัวอย่างมาแล้ว คราวนี้เราจะลองสร้างคล้ายๆกันขึ้นมาแต่จะเพิ่มเมธอดของคลาสลงไป
class ผู้กล้า:
ทุกคน = []
def __init__(self,ชื่อ,เลเวล,hpสูงสุด):
self.ชื่อ = ชื่อ
self.เลเวล = เลเวล
self.hpสูงสุด = hpสูงสุด
self.hp = hpสูงสุด
self.__class__.ทุกคน += [self]
@classmethod
def ฟื้นhpทุกคน(self):
for แต่ละคน in self.ทุกคน:
แต่ละคน.hp = แต่ละคน.hpสูงสุด
เมธอด
ฟื้นhpทุกคน
มีไว้ฟื้น
hp
ให้ทุกคนกลับมาเต็มหมด
โดยจะเห็นว่ามีการสร้างแอตทริบิวต์ชื่อ
ทุกคน
เอาไว้ นี่เป็นแอตทริบิวต์ของคลาส ทำหน้าที่เก็บออบเจ็กต์ที่เป็นผู้กล้าทั้งหมด โดยจะเห็นว่าในเมธอด
__init__
มี
self.__class__.ทุกคน += [self]
นั่นคือเมื่อมีการสร้างผู้กล้าคนใหม่ขึ้นจะถูกเก็บลงในลิสต์นี้
เมื่อเรียกใช้เมธอด
ฟื้นhpทุกคน
ก็จะดึงออบเจ็กต์จากลิสต์
ทุกคน
เพื่อมาทำการฟื้นเลือดโดยแก้ค่า
hp
ให้เท่ากับค่า
hpสูงสุด
ลองใช้ดู
ผู้เล่น1 = ผู้กล้า('นายก',10,20) # สร้างผู้เล่น
ผู้เล่น2 = ผู้กล้า('นายข',11,22)
ผู้เล่น1.hp -= 10 # โดนอัด
ผู้เล่น2.hp -= 18
print(ผู้กล้า.ทุกคน) # แสดงลิสต์ที่เก็บผู้เล่นทุกคน
print('hp ของผู้เล่น1: %d'%ผู้เล่น1.hp) # แสดง hp ที่ลดไปแล้ว
print('hp ของผู้เล่น2: %d'%ผู้เล่น2.hp)
ผู้กล้า.ฟื้นhpทุกคน() # ใช้เมธอดเพื่อฟื้นพลังทุกคน
print('==ฟื้นพลัง==')
print('hp ของผู้เล่น1: %d'%ผู้เล่น1.hp) # แสดง hp หลังฟื้นพลัง
print('hp ของผู้เล่น2: %d'%ผู้เล่น2.hp)
ผลลัพธ์
[<__main__.ผู้กล้า object at 0x1123f5940>, <__main__.ผู้กล้า object at 0x1123f54e0>]
hp ของผู้เล่น1: 10
hp ของผู้เล่น2: 4
==ฟื้นพลัง==
hp ของผู้เล่น1: 20
hp ของผู้เล่น2: 22
staticmethod มีฟังก์ชันเดคอเรเตอร์อีกตัวที่มีลักษณะคล้ายกับ
classmethod
นั่นคือ
staticmethod
staticmethod
มีไว้สร้างสิ่งที่เรียกว่าเมธอดสถิต การใช้งาน
staticmethod
นั้นคล้ายกับ
classmethod
คือเป็นเดคอเรเตอร์ที่ใช้กับเมธอดที่ประกาศในคลาส
การใส่หรือไม่ใส่
classmethod
และ
staticmethod
นั้นทำให้เมธอดที่ประกาศในคลาสมีสถานะที่ต่างกันออกไป ซึ่งก็จะแบ่งออกได้เป็น ๓ รูปแบบคือ
- ประกาศแบบปกติ ได้เป็นเมธอดของอินสแตนซ์
- เติม
@classmethod
ได้เป็นเมธอดของคลาส
- เติม
@staticmethod
ได้เป็นเมธอดสถิต
เพื่อให้เห็นความแตกต่างขอเริ่มจากยกตัวอย่าง
class ClariS:
def instance_me(*arg):
return arg
@classmethod
def class_me(*arg):
return arg
@staticmethod
def static_me(*arg):
return arg
print(ClariS().instance_me('อ1','อ2')) # ได้ (<__main__.ClariS object at 0x11240f3c8>, 'อ1', 'อ2')
print(ClariS().class_me('อ1','อ2')) # ได้ (<class '__main__.ClariS'>, 'อ1', 'อ2')
print(ClariS().static_me('อ1','อ2')) # ได้ ('อ1', 'อ2')
ในตัวอย่างนี้ได้สร้างเมธอดขึ้นมา ๓ อัน ซึ่งเป็น ๓ ชนิดที่ได้กล่าวไปแล้ว แต่ละตัวเขียนขึ้นมาให้ทำงานเหมือนกันคือคืนค่าอาร์กิวเมนต์ทุกตัวที่รับเข้ามา
จากนั้นพอลองนำมาใช้ก็จะเห็นความแตกต่าง นั่นคือ
- เมธอดของอินสแตนซ์จะมีพารามิเตอร์แรกเป็นตัวอินสแตนซ์ ที่เหลือเป็นอาร์กิวเมนต์ที่ใส่เพิ่มเข้ามา
(อินสแตนซ์, อาร์1, อาร์2, ...)
- เมธอดของคลาสจะมีพารามิเตอร์แรกเป็นตัวคลาส ที่เหลือเป็นอาร์กิวเมนต์ที่ใส่เพิ่มเข้ามา
(คลาส, อาร์1, อาร์2, ...)
- เมธอดสถิตไม่มีพารามิเตอร์อะไรเพิ่มมาเป็นพิเศษ มีแต่อาร์กิวเมนต์ที่ใส่
(อาร์1, อาร์2, อาร์3, ...)
ซึ่งถ้าจะว่ากันไปแล้วก็คือจะเห็นว่าเมธอดสถิตนั้นก็ไม่ต่างจากฟังก์ชันธรรมดา เพราะมันไม่ได้มีอะไรที่กระทำต่อตัวคลาสหรืออินสแตนซ์โดยตรงเลย
ประโยชน์การใช้งานของเมธอดสถิตอาจจะใช้เมื่อต้องการให้มีฟังก์ชันติดมากับตัวออบเจ็กต์หรือคลาส เช่นเดียวกับตัวแปรหรือค่าคงที่ต่างๆ
ในภาษาซีหรือจาวาจะมีแค่สิ่งที่เทียบเท่ากับ
staticmethod
แต่ไม่มี
classmethod
ส่วนภาษารูบีมีสิ่งที่เทียบเท่ากับ
classmethod
แต่ไม่มี
staticmethod
แสดงให้เห็นว่าภาษาไพธอนมีความยืดหยุ่นในการใช้มากกว่า
ลองดูตัวอย่างเพิ่มเติมเพื่อให้เห็นถึงความแตกต่างระหว่างการใช้
classmethod
กับ
staticmethod
class Clara:
@classmethod
def classme_x(self,x):
self.x = x
@staticmethod
def staticme_y(y):
Clara.y = y
ในนี้ได้สร้าง
classme_x
เป็นเมธอดของคลาสสำหรับป้อนค่า
x
และ
staticme_y
เป็นเมธอดสถิตสำหรับป้อนค่า
y
ความแตกต่างที่จะเห็นก็คือ
classme_x
มีพารามิเตอร์มากกว่า โดยเพิ่ม
self
มาเป็นตัวแรก ส่วน
staticme_y
ไม่ต้องมี
และตรงส่วนที่ป้อนค่านั้น
classme_x
ใช้
self
ส่วน
staticme_y
ต้องป้อนชื่อคลาส
Clara
เข้าไป
จากนั้นมาลองทดสอบใช้แล้วดูผลที่ได้
Clara.classme_x('x ของคลาส')
Clara.staticme_y('y ของคลาส')
print('Clara.x: '+Clara.x) # ได้ Clara.x: x ของคลาส
print('Clara.y: '+Clara.y) # ได้ Clara.y: y ของคลาส
จะเห็นว่าไม่ว่าจะใช้อันไหนก็ทำให้ผลเหมือนกันได้ เพียงแต่วิธีการเขียนจะต่างกัน เมธอดสถิตจำเป็นจะต้องอ้างอิงชื่อคลาสโดยใส่เข้าไปเอง ไม่ต่างอะไรกับการเรียกด้วยฟังก์ชันธรรมดาที่นิยามนอกคลาสเลย
ลองนิยามฟังก์ชันนี้ขึ้นมาใหม่จากภายนอกคลาส แล้วเรียกใช้ดู
def func_z(z):
Clara.z = z
func_z('z ของคลาส')
print('Clara.z: '+Clara.z) # ได้ Clara.z: z ของคลาส
ผลที่ได้ไม่ต่างจาก
Clara.staticme_y
เลย ดังนั้นเมธอดสถิตจึงเหมือนแค่เป็นการเอาฟังก์ชันธรรมดาไปยัดไว้ในคลาส
ความแตกต่างระหว่างเมธอดของคลาสและเมธอดสถิตอาจยิ่งเห็นผลชัดในกรณีที่มีการรับทอด
class SubClara(Clara):
0
SubClara.classme_x('x ของซับคลาส')
SubClara.staticme_y('y ของซับคลาส')
print('Clara.x: '+Clara.x) # ได้ Clara.x: x ของคลาส ***ไม่เปลี่ยนแปลง
print('Clara.y: '+Clara.y) # ได้ Clara.y: y ของซับคลาส ***มีการเปลี่ยนแปลง
print('SubClara.x: '+SubClara.x) # ได้ SubClara.x: x ของซับคลาส
print('SubClara.y: '+SubClara.y) # ได้ SubClara.y: y ของซับคลาส
ในนี้สร้าง
SubClara
เป็นซับคลาสของ
Clara
ซึ่งเมธอดทั้ง ๒ ของ
Clara
ก็จะนำมาใช้ใน
SubClara
ได้ด้วย
จากนั้นลองใช้เมธอดทั้งสองเพื่อเปลี่ยนค่า
x
และ
y
จากนั้นลองมาดูผลจะพบความแตกต่างตรงที่กรณีของ
y
นั้นค่า
y
ของ
Clara
กลายเป็น
y
ของซับคลาสไปด้วย
สาเหตุที่
y
มีการเปลี่ยนแปลงไปด้วยเพราะ
staticme_y
นั้นมีการใช้ชื่อ
Clara
แทนที่จะเป็น
self
ดังนั้นต่อให้รับทอดมาแล้วเมธอดนี้ก็ยังกระทำต่อคลาส
Clara
อยู่ดี แทนที่จะทำกับ
SubClara
ที่รับทอดมา
ดังนั้นค่า
y
ของ
Clara
จึงมีการเปลี่ยนแปลงไป และค่านี้จะกลายเป็นค่า
y
ของ
SubClara
ไปด้วย
ในขณะที่
classme_x
ใช้
self
ดังนั้นเมื่อรับทอดไปแล้ว
self
ตัวนี้ก็จะหมายถึงตัวคลาสใหม่ ไม่ใช่ตัวคลาสเก่าแล้ว เมื่อใช้จึงเป็นการเปลี่ยนค่า
y
ของ
SubClara
สรุปเนื้อหา เมธอดที่นิยามภายในคลาสประกอบด้วย ๓ ชนิดคือเมธอดของอินสแตนซ์, เมธอดของคลาส และเมธอดสถิต ต้องเข้าใจความแตกต่างและเลือกใช้ให้ถูกวิธี
อ้างอิง