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