φυβλαςのβλογ
บล็อกของ phyblas



ภาษา python เบื้องต้น บทที่ ๓๒: การตกแต่งคลาสด้วยเดคอเรเตอร์
เขียนเมื่อ 2016/05/01 12:37
แก้ไขล่าสุด 2024/02/22 11:09
 

ในบทที่ผ่านมาได้พูดถึงการใช้เดคอเรเตอร์ตกแต่งฟังก์ชันไปแล้ว แต่นอกจากจะใช้กับฟังก์ชันแล้วเดคอเรเตอร์ยังใช้กับคลาสได้ด้วย

และนอกจากนี้ก็ยังใช้กับเมธอดภายในคลาสได้ด้วย การตกแต่งด้วยเดคอเรเตอร์ทำให้คลาสสามารถทำอะไรได้เพิ่มขึ้นอีกมาก



ใช้เดคอเรเตอร์แต่งคลาส

เวลาที่ประกาศสร้างคลาสสร้างขึ้นหากบรรทัดก่อนหน้ามีการใส่ @ ตามด้วยชื่อฟังก์ชัน ฟังก์ชันนั้นก็จะกลายเป็นเดคอเรเตอร์ของคลาสที่จะประกาศขึ้นมานี้

ตัวอย่าง
def sai_y(c):
    c.y = 'วาย'
    return c

@sai_y
class mi_x:
    x = 'เอ็กซ์'

print(mi_x.y) # ได้ วาย 

จะเห็นว่าคลาส mi_x ถูกสร้างขึ้นโดยมีแอตทริบิวต์แค่ x เท่านั้น แต่เพราะแต่งด้วย sai_y ซึ่งเป็นเดคอเรเตอร์สำหรับใส่แอตทริบิวต์ชื่อ y เข้าไปให้กับคลาส ดังนั้นก็เลยมีแอตทริบิวต์ y

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

นอกจากนี้ตัวคลาสที่คืนกลับมาก็เป็นตัวคลาสนั้นอยู่แล้วจึงไม่จำเป็นต้องใช้ฟังก์ชัน functools.wraps ช่วย

นอกจากเพิ่มแอตทริบิวต์เข้าไปแล้วจะปรับค่าแอตทริบิวต์ที่มีอยู่เดิมก็ได้

เช่นลองสร้างเดคอเรเตอร์สำหรับเพิ่มเงินเดือน
def phoemngoenduean5000(c):
    c.ngoenduean += 5000
    return c

@phoemngoenduean5000
class Phanakngan:
    ngoenduean = 26000

print(Phanakngan.ngoenduean) # ได้ 31000

ในตัวอย่างนี้เป็นเดคอเรเตอร์ที่ช่วยเพิ่มเงินเดือน 5000 แต่เราอาจลองสร้างเดคอเรเตอร์ที่ยืดหยุ่นขึ้นซึ่งเพิ่มเท่าไหร่ก็ได้โดยทำ เป็นฟังก์ชันสร้างเดคอเรเตอร์
def phoemngoenduean(ngoen):
    def phoem(c):
        c.ngoenduean += ngoen
        return c
    return phoem

@phoemngoenduean(17000)
class Phuchatkan:
    ngoenduean = 56000

print(Phuchatkan.ngoenduean) # ได้ 73000 

ในตัวอย่างนี้ฟังก์ชัน phoemngoenduean (เพิ่มเงินเดือน) เป็นฟังก์ชันสำหรับสร้างเดคอเรเตอร์ที่เพิ่มเงินเดือนตามตัวเลขที่ใส่เข้าไป



เดคอเรเตอร์ของเมธอดภายในคลาส

ฟังก์ชันที่นิยามภายในโครงสร้างคลาสจะเป็นเมธอดของอินสแตนซ์ของคลาสนั้น เมธอดนั้นสามารถถูกแต่งด้วยเดคอเรเตอร์เช่นเดียวกับฟังก์ชันทั่วไป
def deko(m):
    def boko(*arg):
        return '<~~' + m(*arg) + '~~>'
    return boko

class Kiradeko:
    soba = '=~~.'
    @deko
    def kira(self,xkira):
        return self.soba + xkira + ''.join(reversed(self.soba))

l = Kiradeko()
print(l.kira('แอลคิระ')) # ได้ <~~=~~.แอลคิระ.~~=~~> 

ฟังก์ชัน deko ในตัวอย่างนี้ก็คล้ายกับ deko ในบทที่ ๓๐ ในที่นี้ deko ทำหน้าที่เป็นเดคอเรเตอร์ให้กับเมธอด kira ภายในคลาส Kiradeko

ฟังก์ชันที่จะใช้แต่งเมธอดภายในคลาสนั้นจะนิยามขึ้นภายในคลาสนั้นเองก็ได้เช่นกัน เช่นหากเขียนแบบนี้ก็ให้ผลแบบเดียวกัน
class Kiradeko:
    soba = '=~~.'
    def deko(m):
        def boko(*arg):
            return '<~~' + m(*arg) + '~~>'
        return boko
    @deko
    def kira(self,xkira):
        return self.soba + xkira + ''.join(reversed(self.soba)) 



ใช้คลาสเป็นเดคอเรเตอร์

ที่ผ่านมาเราใช้ฟังก์ชันทำหน้าที่เป็นเดคอเรเตอร์ แต่ความจริงแล้วนอกจากฟังก์ชันเราสามารถใช้อะไรก็ได้ที่สามารถเติมวงเล็บ () ไว้ข้างหลังเพื่อเรียกใช้งานได้

นั่นเพราะหลักการทำงานของเดคอเรเตอร์ก็คือการแต่งเติมฟังก์ชันหรือคลาสโดยเป็นการแทน f = d(f) โดยที่ f คือฟังก์ชันหรือคลาสที่ถูกแต่ง ส่วน d คือเดคอเรเตอร์

ที่จริงแล้วนอกจากฟังก์ชันและคลาสแล้วออบเจ็กต์อื่นๆที่ถูกสร้างขึ้นให้มีคุณสมบัติถูก เรียกด้วย () ได้ก็สามารถเป็นเดคอเรเตอร์ได้เช่นกัน

ลองดูตัวอย่างง่ายๆที่อาจไม่มีประโยชน์ในการใช้งานจริงนัก แต่เพื่อให้เห็นภาพ
@type
def f1():
    return 1

print(f1) # ได้ <class 'function'>

ในที่นี้ใช้ type ซึ่งเป็นคลาสมาเป็นเดคอเรเตอร์ ผลที่ได้ก็คือเหมือนการที่นิยามฟังก์ชัน f1 ขึ้นเสร็จแล้วก็พิมพ์ f1 = type(f1) ต่อ ดังนั้น f1 ก็เลยถูกแทนด้วยคลาสของ f1 ก็คือ function

***type นั้นปกติมักถูกใช้ในฐานะฟังก์ชัน แต่จริงๆแล้ว type เป็นคลาสคลาสหนึ่ง อินสแตนซ์ของ type ก็คือคลาสหรือชนิดต่างๆของออบเจ็กต์ ดังนั้นเมื่อใช้ type(ออบเจ็กต์) แล้วเราได้ค่าออกมาเป็นตัวคลาสของออบเจ็กต์นั้น ที่จริงแล้วคลาสที่ได้มานั้นเป็นผลจากกระบวนการสร้างอินสแตนซ์ของ type

คราวนี้ลองใช้คลาสที่สร้างขึ้นมาเองเป็นเดคอเรเตอร์บ้าง
class Cla:
    def __init__(self,f):
        self.f = f

@Cla
def f2():
    return 'ff'

print(f2) # ได้ <__main__.Cla object at 0x1124f9ac8>
print(f2.f) # ได้ 
print(f2.f()) # ได้ ff

จะเห็นว่าพอใช้เดคอเรเตอร์เป็นคลาสแล้ว ฟังก์ชันที่ถูกตกแต่งจะกลายเป็นอินสแตนซ์ของคลาสนั้น เพราะว่าเหมือนการพิมพ์ f2 = Cla(f2) ซึ่งโดยปกติแล้วจะถือเป็นการสร้างอินสแตนซ์

ส่วนตัวฟังก์ชัน f2 เดิมนั้นจะถูกใช้เป็นพารามิเตอร์ f ในเมธอด __init__ แล้วก็กลายมาเป็นแอตทริบิวต์หนึ่งของ self ซึ่งก็คืออินสแตนซ์ที่จะได้ออกมา

ดังนั้น f2.f ก็เลยกลายเป็นตัวฟังก์ชัน f2 เดิม ซึ่งเมื่อเติม () ไปข้างหลังก็จะทำงานในฐานะฟังก์ชันได้ตามปกติและให้ค่า ff ออกมา

จะเห็นว่าจากเดิมที่ที่ควรจะได้ฟังก์ชัน กลายเป็นว่าได้คลาสที่ภายในมีตัวฟังก์ชันนี้อยู่แทน

แนวคิดนี้อาจทำให้เราสร้างฟังก์ชันขึ้นหลายอันจากการนิยามครั้งเดียวได้ เช่นลองทำแบบนี้
class Deka:
    def __init__(self,f):
        def f0(x):
            return '$$' + f(x) + '$$'
        def f1(x):
            return '<~^' + f(x) + '^~>'
        def f2(x):
            return '(((' + f(x) + ')))'
        self.deka = [f0,f1,f2]

@Deka
def kirei(x):
    return '=' + x + '='

print(kirei.deka[0]('kirei')) # ได้ $$=kirei=$$
print(kirei.deka[1]('kirei')) # ได้ <~^=kirei=^~>
print(kirei.deka[2]('kirei')) # ได้ (((=kirei=))) 

เท่านี้ก็จะได้ฟังก์ชันมาถึง ๓ อันจากการประกาศสร้างครั้งเดียว เพราะ Deka จะทำการแต่งให้ kirei กลายเป็นออบเจ็กต์ซึ่งภายในมีฟังก์ชัน (เมธอด) ซึ่งถูกนิยามขึ้น ๓ อันเก็บอยู่เป็นลิสต์ในตัวแปร deka



สรุปเนื้อหา

เดคอเรเตอร์สามารถใช้ได้กับทั้งฟังก์ชันและคลาส และยังใช้กับเมธอดภายในคลาสได้ด้วย ลักษณะการใช้ก็มีหลักการคล้ายๆกัน สามารถลองเลือกพลิกแพลงใช้ให้ดีได้

มีฟังก์ชันมาตรฐานอยู่ ๓ ตัวที่มักถูกใช้เพื่อเป็นเดคอเรเตอร์สำหรับการตกแต่งภายในคลาส ได้แก่ @property, @classmethod และ @staticmethod สำหรับ ๒ ตัวหลังจะกล่าวถึงในบทหน้า

ส่วน @property นั้นเนื่องจากค่อนข้างเข้าใจยากและไม่ได้ถึงขั้นจำเป็นจะขอแยกไปอยู่ใน เนื้อหาเสริม >> สร้างแอตทริบิวต์ที่มีคุณสมบัติพิเศษในคลาสด้วย property



อ้างอิง




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

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

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

หมวดหมู่

-- คอมพิวเตอร์ >> เขียนโปรแกรม >> 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月

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

ไทย

日本語

中文