φυβλαςのβλογ
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
蒙古语
语言学
maya
概率论
与日本相关的日记
与中国相关的日记
-- 与北京相关的日记
-- 与香港相关的日记
-- 与澳门相关的日记
与台湾相关的日记
与北欧相关的日记
与其他国家相关的日记
qiita
其他日志

按类别分日志



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

  查看日志

  推荐日志

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