φυβλαςのβλογ
บล็อกของ 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
ภาษา mongol
ภาษาศาสตร์
maya
ความน่าจะเป็น
บันทึกในญี่ปุ่น
บันทึกในจีน
-- บันทึกในปักกิ่ง
-- บันทึกในฮ่องกง
-- บันทึกในมาเก๊า
บันทึกในไต้หวัน
บันทึกในยุโรปเหนือ
บันทึกในประเทศอื่นๆ
qiita
บทความอื่นๆ

บทความแบ่งตามหมวด



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

  ค้นหาบทความ

  บทความแนะนำ

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

บทความแต่ละเดือน

2024年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2023年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2022年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2021年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2020年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

ค้นบทความเก่ากว่านั้น

ไทย

日本語

中文