ใน
บทที่แล้วได้พูดถึงการประยุกต์ใช้เดคอเรเตอร์กับการแต่งคลาสไปแล้ว ส่วนในบทนี้จะพูดถึงฟังก์ชัน 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
สรุปเนื้อหา เมธอดที่นิยามภายในคลาสประกอบด้วย ๓ ชนิดคือเมธอดของอินสแตนซ์, เมธอดของคลาส และเมธอดสถิต ต้องเข้าใจความแตกต่างและเลือกใช้ให้ถูกวิธี
อ้างอิง