ในบทที่ผ่านมาได้พูดถึงการใช้เดคอเรเตอร์ตกแต่งฟังก์ชันไปแล้ว แต่นอกจากจะใช้กับฟังก์ชันแล้วเดคอเรเตอร์ยังใช้กับคลาสได้ด้วย
และนอกจากนี้ก็ยังใช้กับเมธอดภายในคลาสได้ด้วย การตกแต่งด้วยเดคอเรเตอร์ทำให้คลาสสามารถทำอะไรได้เพิ่มขึ้นอีกมาก
ใช้เดคอเรเตอร์แต่งคลาส เวลา ที่ประกาศสร้างคลาสสร้างขึ้นหากบรรทัดก่อนหน้ามีการใส่ @ ตามด้วยชื่อฟังก์ชัน ฟังก์ชันนั้นก็จะกลายเป็นเดคอเรเตอร์ของคลาสที่จะประกาศขึ้นมานี้
ตัวอย่าง
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
ในตัวอย่างนี้เป็นเดคอเรเตอร์ที่ช่วยเพิ่มเงินเดือน ๕๐๐๐ แต่เราอาจลองสร้างเดคอเรเตอร์ที่ยืดหยุ่นขึ้นซึ่งเพิ่มเท่าไหร่ก็ได้โดยทำ เป็นฟังก์ชันสร้างเดคอเรเตอร์
def phoemngoenduean(ngoen):
def phoem(c):
c.ngoenduean += ngoen
return c
return phoem
@phoemngoenduean(17000)
class Phuchatkan:
ngoenduean = 56000
print(Phuchatkan.ngoenduean) # ได้ 73000
ในตัวอย่างนี้ฟังก์ชัน เพิ่มเงินเดือน เป็นฟังก์ชันสำหรับสร้างเดคอเรเตอร์ที่เพิ่มเงินเดือนตามตัวเลขที่ใส่เข้าไป
เดคอเรเตอร์ของเมธอดภายในคลาส ฟังก์ชัน ที่นิยามภายในโครงสร้างคลาสจะเป็นเมธอดของอินสแตนซ์ของคลาสนั้น เมธอดนั้นสามารถถูกแต่งด้วยเดคอเรเตอร์เช่นเดียวกับฟังก์ชันทั่วไป
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 อ้างอิง