ในบทที่ผ่านมาได้พูดถึงการใช้เดคอเรเตอร์ตกแต่งฟังก์ชันไปแล้ว แต่นอกจากจะใช้กับฟังก์ชันแล้วเดคอเรเตอร์ยังใช้กับคลาสได้ด้วย
และนอกจากนี้ก็ยังใช้กับเมธอดภายในคลาสได้ด้วย การตกแต่งด้วยเดคอเรเตอร์ทำให้คลาสสามารถทำอะไรได้เพิ่มขึ้นอีกมาก
ใช้เดคอเรเตอร์แต่งคลาส เวลาที่ประกาศสร้างคลาสสร้างขึ้นหากบรรทัดก่อนหน้ามีการใส่
@
ตามด้วยชื่อฟังก์ชัน ฟังก์ชันนั้นก็จะกลายเป็นเดคอเรเตอร์ของคลาสที่จะประกาศขึ้นมานี้
ตัวอย่าง
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 อ้างอิง