def dekoboko(f):
def g():
return f() + ' dekoboko'
return g
def dekopin(f):
def g():
return f() + ' dekopin'
return g
@dekoboko
@dekopin
def odeko():
return 'odeko'
print(odeko()) # ได้ odeko dekopin dekoboko
dekoboko
กับ dekopin
จากนั้นทั้ง ๒ อันถูกนำมาใช้เพื่อตกแต่งฟังก์ชัน odeko
โดยวางซ้อนกันไปdef odeko():
return 'odeko'
odeko = dekoboko(dekopin(odeko))
odeko = dekopin(odeko)
odeko = dekoboko(odeko)
odeko
ถูกตกแต่งด้วย dekopin
ก่อน จากนั้นจึงถูกตกแต่งด้วย dekoboko
ตามมาodeko
ซึ่งเดิมทีจะคืนค่าแค่คำว่า odeko
ก็จะถูกเติมคำว่า dekopin
จากนั้นก็ถูกเติมคำว่า dekoboko
ตามลำดับdef dekopin():
def pin(f):
def g():
return f() + ' dekopin'
return g
return pin
@dekopin()
def odeko():
return 'odeko'
print(odeko()) # odeko dekopin
@dekopin()
มีวงเล็บอยู่ข้างหลังแทนที่จะใส่แค่ชื่อฟังก์ชันทำให้ดูแล้วแปลกตา และแค่เห็นฟังก์ชัน dekopin
มี def
ซ้อนกัน ๓ ชั้นแบบนี้ก็คงจะรู้สึกลำบากใจแล้ว แต่มาลองค่อยๆไล่ดูไปทีละนิดdekopin
หากเขียนโดยไม่ใช่ @
แล้วก็จะกลายเป็น
def odeko():
return 'odeko'
odeko = dekopin()(odeko)
dekopin
มีวงเล็บตามอยู่ข้างหลังอันหนึ่งก่อนที่จะเป็นวงเล็บที่มี odeko
หมายความว่ามันจะทำงานในฐานะฟังก์ชันทันทีก่อน และผลที่ได้ก็คือจะคืนค่าออกมา ซึ่งในที่นี้จะได้ว่าคืนค่าฟังก์ชัน pin
ออกมาodeko = pin(odeko)
@pin
def odeko():
return 'odeko'
pin
ที่อยู่ข้างใน dekopin
อีกทีdekopin
ต้องการพารามิเตอร์ในวงเล็บก็ต้องใส่ตาม เช่น
def dekopin(x):
def pin(f):
def g(y):
return f(y)+x
return g
return pin
@dekopin('pin')
def odeko(s):
return s
print(odeko('dekodeko')) # ได้ dekodekopin
def deko(f):
def neko():
'<<นี่เป็นฟังก์ชันที่ถูกสร้างขึ้นภายใน deko>>'
return f() + 'neko'
return neko
@deko
def koko():
'<<นี่คือฟังก์ชันที่ชื่อว่า koko>>'
return 'konoko'
print(koko()) # ได้ konokoneko
print(koko) # ได้ .neko at 0x11251c0d0>
print(koko.__name__) # ได้ neko
print(koko.__doc__) # ได้ <<นี่เป็นฟังก์ชันที่ถูกสร้างขึ้นภายใน deko>>
deko
เพื่อทำการตกแต่งฟังก์ชัน koko
koko
ที่ถูกแต่งแล้วก็คือได้ข้อความ konokoneko
นั้นเป็นไปตามที่ต้องการไม่มีปัญหาneko
neko
koko = deko(koko)
deko
นั้น return
กลับมาก็คือฟังก์ชัน neko
เลยกลายเป็นว่าฟังก์ชันชื่อ neko
ไปอยู่ในตัวแปรชื่อ koko
neko
นี้จะสืบทอดคุณสมบัติต่างๆของ koko
แต่ว่าชื่อฟังก์ชันและด็อกสตริงไม่ได้สืบทอดมาด้วยneko.__doc__ = f.__doc__
ไว้ก่อน return neko
แบบนี้เราก็จะได้ด็อกสตริงของฟังก์ชันเดิม แต่นั่นก็ยังไม่ใช่การแก้ปัญหาที่เด็ดขาดถูกทางwraps
หรือฟังก์ชัน update_wrapper
ซึ่งอยู่ในมอดูล functools
functools
เป็นมอดูลที่รวบรวมฟังก์ชันที่เอาไว้จัดการเกี่ยวกับฟังก์ชัน มีฟังก์ชันหลายตัวที่มีประโยชน์ แต่ในที่นี้จะพูดถึง ๒ ตัวซึ่งมักใช้คู่กับเดคอเรเตอร์update_wrapper
ก่อน เราสามารถใช้ฟังก์ชันนี้เพื่อแก้ฟังก์ชัน deko
ให้เป็นแบบนี้
import functools
def deko(f):
def neko():
'<<นี่เป็นฟังก์ชันที่ถูกสร้างขึ้นภายใน deko>>'
return f() + 'neko'
return functools.update_wrapper(neko,f)
deko
เดิมก็มีเพียงแค่ return neko
เปลี่ยนเป็น return functools.update_wrapper(neko,f)
เท่านั้น__doc__
และ __name__
ของฟังก์ชันที่ใส่เป็นอาร์กิวเมนต์ตัวแรกแทนที่ด้วยของอาร์กิวเมนต์ตัวหลังneko
) และอาร์กิวเมนต์ตัวหลังคือฟังก์ชันที่ต้องการตกแต่ง (ในที่นี้คือ f
)neko
ที่ถูกแก้แล้ว จากนั้นเราก็นำค่านี้มาใส่ใน return
แทนที่จะใส่ neko
เฉยๆแบบตอนแรกdeko
แล้วจากนั้นลองดูใหม่คราวนี้จะได้ผลตามที่ต้องการ
print(koko) # ได้
print(koko.__doc__) # ได้ <<นี่คือฟังก์ชันที่ชื่อว่า koko>>
wraps
ก็จะเขียนคล้ายๆกัน
import functools
def deko(f):
def neko():
'<<นี่เป็นฟังก์ชันที่ถูกสร้างขึ้นภายใน deko>>'
return f() + 'neko'
return functools.wraps(f)(neko)
functools.update_wrapper(neko,f)
เป็น functools.wraps(f)(neko)
เท่านั้น ส่วนการทำงานก็เช่นเดิมfunctools.wraps(f)(neko)
แทน neko
นั้นก็เท่ากับว่า functools.wraps(f)
ทำตัวเป็นเดคอเรเตอร์ที่ตกแต่ง neko
import functools
def deko(f):
@functools.wraps(f)
def neko():
'<<นี่เป็นฟังก์ชันที่ถูกสร้างขึ้นภายใน deko>>'
return f() + 'neko'
return neko
@functools.wraps(f)
ลงไปเท่านั้นwraps
หรือ update_wrapper
จะทำให้เดคอเรเตอร์ดูสมบูรณ์แบบยิ่งขึ้น ดังนั้นอาจถูกใช้เป็นประจำเวลาสร้างฟังก์ชันสำหรับใช้เป็นเดคอเรเตอร์ติดตามอัปเดตของบล็อกได้ที่แฟนเพจ