φυβλαςのβλογ
phyblas的博客



ภาษา python เบื้องต้น บทที่ ๓๓: เมธอดของคลาสและเมธอดสถิต
เขียนเมื่อ 2016/05/01 14:28
แก้ไขล่าสุด 2024/02/22 11:09
 

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



สรุปเนื้อหา

เมธอดที่นิยามภายในคลาสประกอบด้วย ๓ ชนิดคือเมธอดของอินสแตนซ์, เมธอดของคลาส และเมธอดสถิต ต้องเข้าใจความแตกต่างและเลือกใช้ให้ถูกวิธี



อ้างอิง




-----------------------------------------

囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧

ดูสถิติของหน้านี้

หมวดหมู่

-- คอมพิวเตอร์ >> เขียนโปรแกรม >> python

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

目录

从日本来的名言
模块
-- numpy
-- matplotlib

-- pandas
-- manim
-- opencv
-- pyqt
-- pytorch
机器学习
-- 神经网络
javascript
蒙古语
语言学
maya
概率论
与日本相关的日记
与中国相关的日记
-- 与北京相关的日记
-- 与香港相关的日记
-- 与澳门相关的日记
与台湾相关的日记
与北欧相关的日记
与其他国家相关的日记
qiita
其他日志

按类别分日志



ติดตามอัปเดตของบล็อกได้ที่แฟนเพจ

  查看日志

  推荐日志

ตัวอักษรกรีกและเปรียบเทียบการใช้งานในภาษากรีกโบราณและกรีกสมัยใหม่
ที่มาของอักษรไทยและความเกี่ยวพันกับอักษรอื่นๆในตระกูลอักษรพราหมี
การสร้างแบบจำลองสามมิติเป็นไฟล์ .obj วิธีการอย่างง่ายที่ไม่ว่าใครก็ลองทำได้ทันที
รวมรายชื่อนักร้องเพลงกวางตุ้ง
ภาษาจีนแบ่งเป็นสำเนียงอะไรบ้าง มีความแตกต่างกันมากแค่ไหน
ทำความเข้าใจระบอบประชาธิปไตยจากประวัติศาสตร์ความเป็นมา
เรียนรู้วิธีการใช้ regular expression (regex)
การใช้ unix shell เบื้องต้น ใน linux และ mac
g ในภาษาญี่ปุ่นออกเสียง "ก" หรือ "ง" กันแน่
ทำความรู้จักกับปัญญาประดิษฐ์และการเรียนรู้ของเครื่อง
ค้นพบระบบดาวเคราะห์ ๘ ดวง เบื้องหลังความสำเร็จคือปัญญาประดิษฐ์ (AI)
หอดูดาวโบราณปักกิ่ง ตอนที่ ๑: แท่นสังเกตการณ์และสวนดอกไม้
พิพิธภัณฑ์สถาปัตยกรรมโบราณปักกิ่ง
เที่ยวเมืองตานตง ล่องเรือในน่านน้ำเกาหลีเหนือ
ตระเวนเที่ยวตามรอยฉากของอนิเมะในญี่ปุ่น
เที่ยวชมหอดูดาวที่ฐานสังเกตการณ์ซิงหลง
ทำไมจึงไม่ควรเขียนวรรณยุกต์เวลาทับศัพท์ภาษาต่างประเทศ