@ เป็นสัญลักษณ์ที่เอาไว้สร้างสิ่งที่เรียกว่าเดคอเรเตอร์ (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 ถูกแต่งเติมให้เปลี่ยนแปลงไปเรื่อยๆด้วยฟังก์ชัน yakidef 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 จะถูกแทนด้วย kiraboko ก็เอา kira นี้มาใช้ โดยเติม () เข้าไปก็เท่ากับว่าทำให้ฟังก์ชันทำงาน ซึ่งจะคืนค่าสายอักขระ kirakira กลับออกมา จากนั้นจึงไปบวกกับส่วนแต่งเติมที่ขนาบข้างอยู่แล้วจึงคืนค่าออกมาในที่สุดreturn boko นั้น ฟังก์ชัน boko นี้จะกลายเป็นค่าคืนกลับของฟังก์ชัน dekokira ซึ่งเดิมทีเป็นฟังก์ชันที่จะคืนค่าสายอักขระ 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@ ที่ทำให้นักเขียนโปรแกรมมากมายที่เพิ่งหัดภาษาไพธอนต้องงงงวยกันพอสมควรในการทำความเข้าใจ@ ทำเป็นเพียงแค่การย่อโค้ดให้ดูสั้นลงเล็กน้อยเท่านั้น@ แล้วก็จะรู้สึกว่าใช้แล้วดูเรียบง่ายขึ้น@ กับไม่ใช้@ ซึ่งจะได้เป็น
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 ให้เป็นฟังก์ชัน แต่ต่อมันถูกเขียนทับใหม่ด้วยฟังก์ชัน dekomorikirari ซึ่งเดิมทีเป็นฟังก์ชันจึงถูกเขียนทับกลายเป็นสายอักขระนั่นเอง สายอักขระไม่สามารถเติมวงเล็บ () ด้านหลังเพื่อให้ทำงานได้ ดังนั้นจึงขัดข้อง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]()) # ได้ คิราริคิระคิระ
ติดตามอัปเดตของบล็อกได้ที่แฟนเพจ