หากตามอ่านเนื้อหามาเรื่อยๆตั้งแต่บทแรกจนถึงตรงนี้คิดว่าหากไปดูโค้ดภาษาไพธอนที่คนอื่นเขียนไว้ก็คงพอจะอ่านเข้าใจได้คร่าวๆแล้ว อย่างน้อยก็รู้ไวยากรณ์ที่สำคัญไปเกือบหมดแล้ว น่าจะเข้าใจความหมายของสัญลักษณ์ส่วนใหญ่ที่ใช้
แต่หลายคนอาจเคยอ่านโค้ดแล้วไปเจอสัญลักษณ์
@ แล้วก็คงจะงงว่ามันมีไว้ทำอะไร
สัญลักษณ์ @ นั้นโดยทั่วไปเวลาคนเห็นคงจะนึกถึงอีเมล แต่ว่าในภาษาไพธอนนั้น @ เป็นสัญลักษณ์ที่เอาไว้สร้างสิ่งที่เรียกว่า
เดคอเรเตอร์ (decorator) เดคอเรเตอร์เป็นหัวข้อหนึ่งในภาษาไพธอนที่ขึ้นชื่อว่าเข้าใจยาก ด้วยความที่การจะเข้าใจเดคอเรเตอร์ได้นั้นจะต้องมีความเข้าใจในพื้นฐาน เรื่องอื่นอีกหลายเรื่อง
ดังนั้นแล้วหากจะอ่านบทนี้ให้เข้าใจจำเป็นจะต้องเข้าใจเนื้อหาบทที่ผ่านๆมาพอสมควร โดยเฉพาะ
บทที่แล้วซึ่งพูดถึงการสร้างฟังก์ชันจากฟังก์ชัน
ไอดอลวง decoration จากอนิเมะเรื่อง IDOLM@STER (
ที่มา) (ภาพไม่เกี่ยวข้องกับเนื้อหา)
ความหมายของเดคอเรเตอร์ เดคอเรเตอร์คือฟังก์ชันหรือคลาสที่รับค่าฟังก์ชันเข้าไปเป็นอาร์กิวเมนต์แล้วคืนค่าฟังก์ชันตัวใหม่ออกมา
เดคอเรเตอร์ (decorator) หากแปลตรงๆจะมีความหมายว่า "ตัวตกแต่ง" ดังนั้นหน้าที่ของมันก็คือทำการตกแต่งนั่นเอง
แต่ว่าตกแต่งอะไร ถ้าดูจากแค่ความหมายของคำศัพท์ตรงๆแค่นี้ก็คงไม่รู้ จึงจำเป็นจะต้องขยายความ
ในภาษาไพธอน เดคอเรเตอร์คือตัวที่เอาไว้ตกแต่งฟังก์ชันหรือคลาส
แล้วตกแต่งในที่นี้หมายถึงอะไร?
เพื่อให้เห็นภาพชัดขอเริ่มจากการยกตัวอย่าง
def yaki(x):
return '~~' + x + '~~'
ตรงนี้เราได้สร้างฟังก์ชันง่ายๆขึ้นมาตัวหนึ่ง การทำงานง่ายๆคือรับเอาค่าสายอักขระอันหนึ่งเข้าไปแล้วก็ทำการใส่ ~~ ขนาบซ้ายขวา
จากนั้นมีตัวแปรสายอักขระอยู่ตัวหนึ่ง
mochi = 'โมจิ'
หากนำสายอักขระนี้ไปใส่เป็นอาร์กิวเมนต์ของฟังก์ชัน ก็จะได้เป็นสายอักขระที่ถูกแต่งเติม ลอง print ออกมาดูความแตกต่างระหว่างตัวเดิมกับตัวที่ได้จากฟังก์ชัน
print(mochi) # ได้ โมจิ
print(yaki(mochi)) # ได้ ~~โมจิ~~
จะเห็นว่าดูแล้วเหมือนกับสักอักขระ mochi นั้นถูกตกแต่งเพิ่มเติมเข้าไป มีความเปลี่ยนแปลงเกิดขึ้น
แต่ว่าการใช้ในฟังก์ชันในลักษณะนี้ตัวสายอักขระ mochi เองนั้นไม่ได้มีค่าเปลี่ยนแปลงไป แค่รับเอาค่าไปแต่งเติมเท่านั้น หากอยากให้มันเปลี่ยนแปลงก็จะต้องแทนค่าที่ได้ใหม่กลับเข้าไปยังตัวมัน
mochi = yaki(mochi)
print(mochi) # ได้ ~~โมจิ~~
ถ้าเติมอีกทีก็จะเปลี่ยนแปลงไปอีก
mochi = yaki(mochi)
print(mochi) # ได้ ~~~~โมจิ~~~~
ในลักษณะนี้จะเห็นว่า mochi ถูกแต่งเติมให้เปลี่ยนแปลงไปเรื่อยๆด้วยฟังก์ชัน yaki
และนี่ก็คือเป็นแนวคิดของเดคอเรเตอร์ คือการเอาออบเจ็กต์ที่มีอยู่แล้วมาตกแต่งเพิ่มเติม
อย่างไรก็ตาม ตัวอย่างข้างต้นนี้ไม่ใช่เดคอเรเตอร์ เพราะดังที่ได้กล่าวไปแล้วว่าเดคอเรเตอร์จริงๆนั้นใช้กับคลาสหรือฟังก์ชันเท่านั้น
เพียงแต่ว่าแนวคิดนั้นคล้ายกัน ดังนั้นจึงนำมาเป็นตัวช่วยในการทำความเข้าใจได้
ขอไล่ต่อไปทีละขั้น ต่อมาลองมาดูตัวอย่างที่ใกล้เคียงขึ้นไปกว่าเดิมอีกหน่อย นี่จะเป็นตัวอย่างสุดท้ายก่อนที่จะเริ่มเข้าสู่อาณาเขตของเดคอเรเตอร์
ใน
บทที่แล้วได้ลองสร้างฟังก์ชันที่มีไว้สำหรับสร้างฟังก์ชันมาแล้ว ฟังก์ชันนั้นรับค่าอะไรบางอย่างเข้าไปแล้วก็สร้างฟังก์ชันที่มีคุณสมบัติต่างไปตามค่าที่รับเข้ามา
def yaki(x):
def mochi():
return '~~' + x + '~~'
return mochi
yakimochi = yaki('โมจิย่าง')
print(yakimochi()) # ได้ ~~โมจิย่าง~~
yakimochi = yaki('焼き餅')
print(yakimochi()) # ได้ ~~焼き餅~~
จะเห็นว่าฟังก์ชัน yaki ทำหน้าที่สร้างฟังก์ชัน mochi ซึ่งเป็นฟังก์ชันที่จะคืนค่าตามที่ใส่ลงไปเป็นอาร์กิวเมนต์ในฟังก์ชัน yaki โดยจะมีการแต่งเพิ่มเติมโดยใส่ '~~' ขนาบซ้ายขวา
แต่ฟังก์ชัน yaki ในที่นี้ก็ยังไม่ถือว่าเป็นเดคอเรเตอร์ เพราะแค่รับค่าสายอักขระเข้าไปแล้วส่งฟังก์ชันออกมา แต่เดคอเรเตอร์จะต้องสามารถรับฟังก์ชันได้ แล้วส่งฟังก์ชันที่ถูกแต่งเติมไปจากเดิมกลับออกมา
แต่ที่ยกตัวอย่างนี้ขึ้นมาก็เพื่อจะทบทวนเนื้อหาของบทที่แล้วว่าเราสามารถให้ฟังก์ชันส่งค่าฟังก์ชันเป็นค่าคืนกลับออกมาได้
คราวนี้ได้เวลามาลองเข้าสู่ตัวอย่างของสิ่งที่เรียกว่าเดคอเรเตอร์ สิ่งที่จะต้องทำเพิ่มเติมจากตัวอย่างที่แล้วก็คือเปลี่ยนจากสายอักขระธรรมดา เป็นฟังก์ชัน
ดังนั้นเราต้องสร้างฟังก์ชันขึ้นมาก่อน จากนั้นจึงค่อยนำฟังก์ชันนั้นมาตกแต่ง
def deko(f):
def boko():
return '~*@*^*' + f() + '*^*@*~'
return boko
def kira():
return 'kirakira'
print(kira()) # ได้ kirakira
kira = deko(kira)
print(kira()) # ได้ ~*@*^*kirakira*^*@*~
มาลองค่อยๆไล่ดูทีละขั้น จะเห็นว่าฟังก์ชัน deko ในที่นี้คล้ายกับฟังก์ชัน yaki ในตัวอย่างที่แล้วมาก
แต่มีสิ่งหนึ่งที่ต่างไป นั่นคือมีวงเล็บ () อยู่หลัง f (ไม่รวมการที่เปลี่ยนชื่อตัวแปรจาก f เป็น x และส่วนแต่งเติมที่ดูฉูดฉาดขึ้นกว่าเดิม)
นั่นเพราะว่าคราวนี้สิ่งที่รับเข้าไปคือฟังก์ชัน ไม่ใช่แค่สายอักขระธรรมดา ตรงส่วน kira = deko(kira) นั้นจะเห็นว่าฟังก์ชัน deko รับฟังก์ชัน kira ไปเป็นอาร์กิวเมนต์ ดังนั้น f จะถูกแทนด้วย kira
จากนั้นฟังก์ชัน boko ก็เอา kira นี้มาใช้ โดยเติม () เข้าไปก็เท่ากับว่าทำให้ฟังก์ชันทำงาน ซึ่งจะคืนค่าสายอักขระ kirakira กลับออกมา จากนั้นจึงไปบวกกับส่วนแต่งเติมที่ขนาบข้างอยู่แล้วจึงคืนค่าออกมาในที่สุด
จากนั้นตรงในบรรทัดต่อมาคือ return boko นั้น ฟังก์ชัน boko นี้จะกลายเป็นค่าคืนกลับของฟังก์ชัน deko
และสุดท้ายฟังก์ชันนี้ก็จะถูกนำมาแทนลงในตัวแปร kira ซึ่งเดิมทีเป็นฟังก์ชันที่จะคืนค่าสายอักขระ kirakira แต่พอผ่านกระบวนการนี้มันก็จะกลายเป็นฟังก์ชันที่คืนค่าสายอักขระ ~*@*^*kirakira*^*@*~ ไปแล้ว
แน่นอนว่าตัวแปรที่มารับค่านั้นไม่จำเป็นจะต้องเป็นตัวแปรเดิม อาจเขียนเป็น
kirameki = deko(kira)
print(kirameki()) # ได้ ~*@*^*kirakira*^*@*~
แบบนี้ก็ได้เช่นกัน ถ้าเป็นกรณีแบบนี้ kirameki ก็จะกลายเป็นฟังก์ชัน kira ในเวอร์ชันที่ถูกแต่งเติมไปแล้ว
แต่โดยทั่วไปเราอาจไม่มีความจำเป็นจะต้องเก็บฟังก์ชันเดิมไว้ ดังนั้นจึงเขียนทับตัวแปรเก่าไปเลยก็ได้
และนี่ก็คือกระบวนการทำงานของเดคอเรเตอร์อย่างสั้นๆ แม้จะดูสั้นๆแค่นี้แต่กลไกการทำงานของมันไม่ได้เข้าใจสักเท่าไหร่เลย ยังไงก็อาจต้องลองอ่านทำความเข้าใจให้ดีก่อนที่จะเข้าสู่หัวข้อถัดไป
สรุปอีกครั้งสั้นๆคือ ในที่นี้ deko เป็นเดคอเรเตอร์ ส่วน kira เป็นฟังก์ชันที่ถูกตกแต่ง
เครื่องหมาย @ ตอนนี้ได้ทำความรู้จักไปแล้วว่าสิ่งที่เรียกว่าเดคอเรเตอร์นั้นคืออะไร แต่ว่ายังไม่ได้พูดถึงสัญลักษณ์ @ ซึ่งได้เกริ่นถึงไว้ในตอนแรกเลย
แท้จริงแล้วรูปแบบที่เขียนด้วย @ นั้นเป็นเพียงสิ่งที่เรียกว่า
น้ำตาลทางวากยสัมพันธ์ (syntax sugar หรือ
syntactic sugar) คือไวยากรณ์ที่ถูกออกแบบมาเป็นพิเศษเพื่อให้ดูแล้วเรียบง่ายขึ้น ดูแล้วเข้าใจง่ายขึ้น ไม่ต้องเขียนให้ยืดยาว
@ เป็นเครื่องหมายที่เอาไว้ใช้เขียนนำหน้าฟังก์ชันที่ต้องการใช้เป็นเดคอเรเตอร์ แล้วนำไปวางไว้ด้านบนฟังก์ชันที่ต้องการตกแต่งอีกที
ตัวอย่างข้างต้นสามารถนำมาเขียนใหม่โดยใช้ @ แทนได้ดังนี้
def deko(f):
def boko():
return '~*@*^*' + f() + '*^*@*~'
return boko
@deko
def kira():
return 'kirakira'
print(kira()) # ได้ ~*@*^*kirakira*^*@*~
จะเห็นว่าที่ต่างจากเดิมคือ บรรทัด kira = deko(kira) หายไปแล้ว นั่นเพราะถูกแทนด้วย @deko ที่วางเพิ่มเข้ามาก่อน def kira
และนี่ก็คือความหมายของ @ ที่ทำให้นักเขียนโปรแกรมมากมายที่เพิ่งหัดภาษาไพธอนต้องงงงวยกันพอสมควรในการทำความเข้าใจ
สิ่งที่ @ ทำเป็นเพียงแค่การย่อโค้ดให้ดูสั้นลงเล็กน้อยเท่านั้น
ในสายตาของคนที่ไม่รู้ไวยากรณ์นั้นการเขียนเต็มๆแบบเดิมอาจดูแล้วเข้าใจง่ายกว่า แต่หากทำความเข้าใจถึงความหมายของ @ แล้วก็จะรู้สึกว่าใช้แล้วดูเรียบง่ายขึ้น
หากให้เปรียบเทียบก็อาจเหมือนกับเวลาที่เรียนภาษาอังกฤษ คนที่เพิ่งหัดเรียนอาจไม่รู้ว่า I'm แปลว่าอะไร รู้จักแต่ I กับ am กว่าจะรู้ว่า I'm คือรูปย่อของ I am นั่นเองก็ต้องเรียนไปพอสมควร และพอรู้แล้วเราก็จะเริ่มใช้มันบ่อยขึ้น เพราะมันย่อแล้วดูสั้นลง ไม่ต้องเขียนยาว
ฟังก์ชันกับเดคอเรเตอร์ต้องมีความสอดคล้องกัน ฟังก์ชันที่สร้างขึ้นมาเป็นเดคอเรเตอร์นั้นอาจจะใช้กับฟังก์ชันได้หลายตัว ไม่ได้จำเพาะกับฟังก์ชันตัวใดตัวหนึ่ง แต่ถึงอย่างนั้นก็ไม่ใช่ว่าใช้กับฟังก์ชันไหนก็ได้
ขอยกตัวอย่างเพิ่มเติมโดยเอาตัวอย่างเดิมมาปรับปรุงให้ซับซ้อนขึ้นอีกหน่อย โดยเพิ่มพารามิเตอร์เข้ามา
เช่นเดียวกับตัวอย่างที่แล้ว เราสามารถเขียนได้สองแบบคือใช้ @ กับไม่ใช้
ก่อนอื่นขอเริ่มจากเขียนด้วยรูปแบบดั้งเดิมไม่ใช้ @ ซึ่งจะได้เป็น
def deko(f):
def boko(x):
return '~*@*^*' + f(x) + '*^*@*~'
return boko
def kira(x):
return x
kira = deko(kira)
print(kira('คิระคิระ')) # ได้ ~*@*^*คิระคิระ*^*@*~
และหากเขียนโดยใช้ @ ก็จะเป็น
def deko(f):
def boko(x):
return '~*@*^*' + f(x) + '*^*@*~'
return boko
@deko
def kira(x):
return x
print(kira('คิระคิระ')) # ได้ ~*@*^*คิระคิระ*^*@*~
สิ่งที่เพิ่มเข้ามาจากตัวอย่างที่แล้วคือฟังก์ชัน kira คราวนี้มีพารามิเตอร์ x เข้ามา ทำให้ต้องใส่อาร์กิวเมนต์เข้าไปเวลาเรียกใช้ฟังก์ชันด้วย
เมื่อฟังก์ชันที่จะให้แต่งเติมนั้นมีพารามิเตอร์เพิ่มเข้ามาฟังก์ชัน boko ก็ต้องเพิ่มพารามิเตอร์เข้ามาด้วย ดังที่เห็น
จะเห็นว่าเดคอเรเตอร์ deko นี้จะเอามาใช้กับฟังก์ชันอะไรก็ได้ที่รับค่าพารามิเตอร์เป็นสายอักขระตัวเดียว
@deko
def kirakirakira(x):
return x*3
print(kirakirakira('คิระ')) # ได้ ~*@*^*คิระคิระคิระ*^*@*~
แต่ถ้าเอาเดคอเรเตอร์ตัวเดิมนี้มาใช้กับฟังก์ชันที่ใช้พารามิเตอร์ ๒ ตัวก็จะขัดข้องทันที
@deko
def kirameki(x,y):
return x+y
print(kirameki('คิระ','เมคิ')) # ได้ TypeError: boko() takes 1 positional argument but 2 were given
เพื่อที่จะแก้ปัญหาต้องเปลี่ยนฟังก์ชัน deko ให้รองรับพารามิเตอร์
def deko(f):
def boko(x,y):
return '~*@*^*' + f(x,y) + '*^*@*~'
return boko
แบบนี้ก็จะทำให้กลายเป็นเดคอเรเตอร์ที่ใช้กับฟังก์ชันที่มีพารามิเตอร์ ๒ ตัว
อย่างไรก็ตาม หากต้องการให้เดคอเรเตอร์ใช้งานได้ยืดหยุ่นกว่านี้ คือรับพารามิเตอร์กี่ตัวก็ได้ อาจใช้ตัวแปรที่ติดดอกจัน * เป็นพารามิเตอร์
def deko(f):
def boko(*arg):
return '~*@*^*' + f(*arg) + '*^*@*~'
return boko
@deko
def kirameki(x,y): # พารามิเตอร์ ๒ ตัว
return x+y
@deko
def kirakira(x): # พารามิเตอร์ ๑ ตัว
return x*2
print(kirameki('คิระ','เมคิ')) # ได้ ~*@*^*คิระเมคิ*^*@*~
print(kirakira('คิระ')) # ได้ ~*@*^*คิระคิระ*^*@*~
จะเห็นว่าเดคอเรเตอร์ deko ตอนนี้สามารถใช้งานได้กับฟังก์ชันที่มีพารามิเตอร์กี่ตัวก็ได้
ถึงอย่างนั้นก็ยังมีข้อจำกัดอยู่ว่าต้องใช้กับฟังก์ชันที่คืนค่าเป็นสายอักขระเท่านั้น ไม่เช่นนั้นก็จะขัดข้องอยู่ดี
สิ่งที่อยากสรุปในหัวข้อนี้ก็คือ เดคอเรเตอร์มีความจำเพาะในการทำงานอยู่ เราควรจะต้องรู้ว่าเดคอเรเตอร์นี้ใช้ฟังก์ชันแบบไหนได้ ถ้าใช้ผิดก็จะเกิดข้อผิดพลาด
และเราสามารถเพิ่มความยืดหยุ่นให้เดคอเรเตอร์สามารถใช้งานได้กว้างขึ้นด้วยวิธีต่างๆ ตัวอย่างข้างต้นเป็นวิธีหนึ่ง คือการใช้ตัวแปรติดดอกจัน * เป็นพารามิเตอร์เพื่อให้รองรับฟังก์ชันที่มีจำนวนพารามิเตอร์กี่ตัวก็ได้
ส่วนในตัวอย่างต่อไปนี้เดคอเรเตอร์จะถูกสร้างมาให้รองรับฟังก์ชันได้ทุกชนิด
def décorateur(f):
def g(*arg,**kw):
print('อาร์กิวเมนต์: %s คีย์เวิร์ด: %s'%(arg,kw))
return f(*arg,**kw)
return g
@décorateur
def kiraku(a,b,c,d='ง'):
return a,b,c,d
@décorateur
def kirabiyaka(x,y,z=7):
return x,y,z
kiraku('ก','ข',c='ค') # ได้ อาร์กิวเมนต์: ('ก', 'ข') คีย์เวิร์ด: {'c': 'ค'}
kirabiyaka(1,y=2,z=5) # ได้ อาร์กิวเมนต์: (1,) คีย์เวิร์ด: {'z': 5, 'y': 2}
ในตัวอย่างนี้ฟังก์ชัน décorateur เป็นเดคอเรเตอร์ที่ทำหน้าที่แต่งให้ฟังก์ชันใดๆมีการ print ค่าอาร์กิวเมนต์และคีย์เวิร์ดทั้งหมดที่ถูกใส่เข้ามาเมื่อฟังก์ชันถูกเรียกใช้งาน
จะเห็นว่าฟังก์ชัน g มีพารามิเตอร์เป็น *arg และ **kw และ ซึ่งถูกนำมาแสดงผลใน print
*arg แทนอาร์กิวเมนต์ทั้งหมด และ **kw แทนคีย์เวิร์ดทั้งหมด ดังนั้นมันจึงครอบคลุมทั้งอาร์กิวเมนต์และคีย์เวิร์ดทั้งหมดที่ถูกใช้ใน ฟังก์ชันที่จะถูกแต่งเติม
อีกทั้งค่า arg และ kw ไม่ได้ถูกใช้เพื่อทำอะไรนอกจาก print ดังนั้นไม่ว่าอาร์กิวเมนต์และคีย์จะเป็นข้อมูลชนิดใดก็ตามก็ไม่ทำให้โปรแกรม ขัดข้อง
ฟังก์ชัน décorateur นี้ไม่ได้ทำอะไรเพิ่มเติมนอกจาก print อาร์กิวเมนต์และคีย์เวิร์ดของฟังก์ชันที่ถูกแต่ง ดังนั้นมันจึงเป็นเดคอเรเตอร์ที่ใช้กับฟังก์ชันอะไรก็ได้
เดคอเรเตอร์ที่ไม่ได้คืนค่าเป็นฟังก์ชันหรือคลาส มีสิ่งหนึ่งที่จำเป็นต้องพูดถึงไว้สักหน่อยเพราะไปเจอบางเว็บที่ยกตัวอย่างการใช้ @ ในแบบที่อ่านแล้วอาจทำให้งงกว่าเดิมจึงอยากจะเน้นย้ำ
ประเด็นก็คือ ฟังก์ชันที่ตามหลัง @ อาจไม่จำเป็นต้องคืนค่าเป็นฟังก์ชันหรือคลาสเสมอไป แต่อาจคืนออบเจ็กต์อะไรบางอย่างที่แตกต่างจากสิ่งเดิมออกไปสิ้นเชิงเลยก็เป็นได้
ก่อนอื่นดูตัวอย่างนี้
def kirari():
return '^คิราริ^'
print(kirari()) # ได้ ^คิราริ^
เมื่อนิยามฟังก์ชันขึ้นแบบธรรมดาไม่ได้ทำการตกแต่งใดๆ ก็จะได้ฟังก์ชันออกมาตามที่ควรจะเป็น ซึ่งจะทำงานต่อเมื่อใส่วงเล็บ () ไปข้างหลัง ตรงนี้ดูแล้วก็เป็นเรื่องปกติ ไม่มีปัญหาอะไร
แต่เหตุการณ์อาจเปลี่ยนไปโดยสิ้นเชิงก็เป็นได้เมื่อใช้ @
เช่นลองดูตัวอย่างนี้
def dekomori(f):
return '^เดโกโมริ^'
@dekomori
def kirari():
return '^คิราริ^'
print(kirari()) # ได้ TypeError: 'str' object is not callable
เกิดอะไรขึ้นกับฟังก์ชัน kirari ในกรณีนี้? ในเมื่อเรานิยาม kirari ด้วย def มันก็ควรเป็นฟังก์ชัน แต่ทำไมถึงเกิดข้อผิดพลาดโดยขึ้นเตือนว่า kirari เป็นสายอักขระ
เพื่อความเข้าใจที่มากขึ้น ลองแปลงกลับเป็นรูปแบบที่ไม่ใช้ @ ดู จะได้ว่า
def kirari():
return '^คิราริ^'
kirari = dekomori(kirari)
พอเขียนแบบนี้แล้วจะเห็นภาพได้ชัดขึ้น นั่นคือ kirari เริ่มถูกนิยามด้วย def ให้เป็นฟังก์ชัน แต่ต่อมันถูกเขียนทับใหม่ด้วยฟังก์ชัน dekomori
ซึ่งฟังก์ชันนี้ไม่ได้คืนค่ากลับเป็นฟังก์ชัน แค่กลับคืนแค่สายอักขระ ดังนั้น kirari ซึ่งเดิมทีเป็นฟังก์ชันจึงถูกเขียนทับกลายเป็นสายอักขระนั่นเอง สายอักขระไม่สามารถเติมวงเล็บ () ด้านหลังเพื่อให้ทำงานได้ ดังนั้นจึงขัดข้อง
หากลอง print ใหม่โดยตัด () ข้างหลังออกก็จะได้ข้อความจากสายอักขระออกมา
print(kirari) # ได้ ^เดโกโมริ^
หากยังไม่เข้าใจหลักการทำงานของ @ แล้วละก็เจอโปรแกรมแบบนี้ก็คงจะงงและยากที่จะเข้าใจได้
บางครั้งเดคอเรเตอร์ก็อาจแค่เอาฟังก์ชันมาทำอะไรบางอย่างแล้วส่งค่าที่ได้ออกมาโดยตรงโดยที่ไม่ได้คืนค่าฟังก์ชันนั้นออกมาเลยก็ได้
ลองยกตัวอย่างใหม่
def dekomori(f):
return f('คิราเดโกะ')
@dekomori
def kirari(x):
return 'คิราริ'+x
print(kirari) # ได้ คิราริคิราเดโกะ
ในตัวอย่างนี้ dekomori เป็นเดคอเรเตอร์ที่นำเอาฟังก์ชันมาแล้วป้อน 'คิราเดโกะ' เป็นอาร์กิวเมนต์ใส่เข้าไป จากนั้นก็คืนค่าที่ได้จากฟังก์ชันออกมาโดยตรงเลย
เดคอเรเตอร์แบบนี้ดูแล้วเขียนสั้นกว่า เหมือนจะเรียบง่ายกว่า แต่จริงๆแล้วอาจกลับทำให้งงยิ่งกว่าเดคอเรเตอร์ที่ยกตัวอย่างมาตอนแรกสุด เพราะมันทำให้ฟังก์ชันเปลี่ยนกลายไปเป็นอะไรอย่างอื่น
ในทางกลับกัน เดคอเรเตอร์ก็อาจทำให้เกิดอะไรที่ซับซ้อนยิ่งกว่าแค่ฟังก์ชันธรรมดาก็เป็นได้ เช่น ลองสร้างเดคอเรเตอร์ที่คืนค่าเป็นทูเพิลของฟังก์ชัน
def kiradeko(f):
def dekodeko():
return f('เดโกะเดโกะ')
def kirakira():
return f('คิระคิระ')
return (dekodeko,kirakira)
@kiradeko
def kirari(x):
return 'คิราริ' + x
print(kirari) # ได้ (.dekodeko at 0x1123529d8>, .kirakira at 0x112352d08>)
print(kirari[0]()) # ได้ คิราริเดโกะเดโกะ
print(kirari[1]()) # ได้ คิราริคิระคิระ
ยิ่งเขียนก็ยิ่งดูซับซ้อน แต่ต้องการแสดงให้เห็นว่าเดคอเรเตอร์นั้นอาจทำให้เกิดผลอะไรต่างๆได้หลาก หลายมากมาย จากที่เริ่มต้นแค่การใส่ฟังก์ชันลงไปอันหนึ่ง มันอาจกลายมาเป็นอะไรก็ได้สุดแท้แต่เราจะแต่งเติม
เรื่องของเดคอเรเตอร์ยังมีอะไรต้องเรียนรู้อีกมากมาย และเนื่องจากเนื้อหายาวขอตัดบทไปเขียนต่อในบทถัดไป
อ้างอิง