φυβλαςのβλογ
บล็อกของ phyblas



ภาษา python เบื้องต้น บทที่ ๓๐: รู้จักกับเดคอเรเตอร์
เขียนเมื่อ 2016/04/27 17:54
แก้ไขล่าสุด 2021/09/28 16:42
หากตามอ่านเนื้อหามาเรื่อยๆตั้งแต่บทแรกจนถึงตรงนี้คิดว่าหากไปดูโค้ดภาษาไพธอนที่คนอื่นเขียนไว้ก็คงพอจะอ่านเข้าใจได้คร่าวๆแล้ว อย่างน้อยก็รู้ไวยากรณ์ที่สำคัญไปเกือบหมดแล้ว น่าจะเข้าใจความหมายของสัญลักษณ์ส่วนใหญ่ที่ใช้

แต่หลายคนอาจเคยอ่านโค้ดแล้วไปเจอสัญลักษณ์ @ แล้วก็คงจะงงว่ามันมีไว้ทำอะไร

สัญลักษณ์ @ นั้นโดยทั่วไปเวลาคนเห็นคงจะนึกถึงอีเมล์ แต่ว่าในภาษาไพธอนนั้น @ เป็นสัญลักษณ์ที่เอาไว้สร้างสิ่งที่เรียกว่าเดคอเรเตอร์ (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]()) # ได้ คิราริคิระคิระ

ยิ่งเขียนก็ยิ่งดูซับซ้อน แต่ต้องการแสดงให้เห็นว่าเดคอเรเตอร์นั้นอาจทำให้เกิดผลอะไรต่างๆได้หลาก หลายมากมาย จากที่เริ่มต้นแค่การใส่ฟังก์ชันลงไปอันหนึ่ง มันอาจกลายมาเป็นอะไรก็ได้สุดแท้แต่เราจะแต่งเติม



เรื่องของเดคอเรเตอร์ยังมีอะไรต้องเรียนรู้อีกมากมาย และเนื่องจากเนื้อหายาวขอตัดบทไปเขียนต่อในบทถัดไป



อ้างอิง


<< บทที่แล้ว      บทถัดไป >>
หน้าสารบัญ


-----------------------------------------

囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧

ดูสถิติของหน้านี้

หมวดหมู่

-- คอมพิวเตอร์ >> เขียนโปรแกรม >> python

ไม่อนุญาตให้นำเนื้อหาของบทความไปลงที่อื่นโดยไม่ได้ขออนุญาตโดยเด็ดขาด หากต้องการนำบางส่วนไปลงสามารถทำได้โดยต้องไม่ใช่การก๊อปแปะแต่ให้เปลี่ยนคำพูดเป็นของตัวเอง หรือไม่ก็เขียนในลักษณะการยกข้อความอ้างอิง และไม่ว่ากรณีไหนก็ตาม ต้องให้เครดิตพร้อมใส่ลิงก์ของทุกบทความที่มีการใช้เนื้อหาเสมอ

สารบัญ

รวมคำแปลวลีเด็ดจากญี่ปุ่น
มอดูลต่างๆ
-- numpy
-- matplotlib

-- pandas
-- manim
-- opencv
-- pyqt
-- pytorch
การเรียนรู้ของเครื่อง
-- โครงข่าย
     ประสาทเทียม
ภาษา javascript
ภาษา mongol
ภาษาศาสตร์
maya
ความน่าจะเป็น
บันทึกในญี่ปุ่น
บันทึกในจีน
-- บันทึกในปักกิ่ง
-- บันทึกในฮ่องกง
-- บันทึกในมาเก๊า
บันทึกในไต้หวัน
บันทึกในยุโรปเหนือ
บันทึกในประเทศอื่นๆ
qiita
บทความอื่นๆ

บทความแบ่งตามหมวด



ติดตามอัปเดตของบล็อกได้ที่แฟนเพจ

  ค้นหาบทความ

  บทความแนะนำ

ตัวอักษรกรีกและเปรียบเทียบการใช้งานในภาษากรีกโบราณและกรีกสมัยใหม่
ที่มาของอักษรไทยและความเกี่ยวพันกับอักษรอื่นๆในตระกูลอักษรพราหมี
การสร้างแบบจำลองสามมิติเป็นไฟล์ .obj วิธีการอย่างง่ายที่ไม่ว่าใครก็ลองทำได้ทันที
รวมรายชื่อนักร้องเพลงกวางตุ้ง
ภาษาจีนแบ่งเป็นสำเนียงอะไรบ้าง มีความแตกต่างกันมากแค่ไหน
ทำความเข้าใจระบอบประชาธิปไตยจากประวัติศาสตร์ความเป็นมา
เรียนรู้วิธีการใช้ regular expression (regex)
การใช้ unix shell เบื้องต้น ใน linux และ mac
g ในภาษาญี่ปุ่นออกเสียง "ก" หรือ "ง" กันแน่
ทำความรู้จักกับปัญญาประดิษฐ์และการเรียนรู้ของเครื่อง
ค้นพบระบบดาวเคราะห์ ๘ ดวง เบื้องหลังความสำเร็จคือปัญญาประดิษฐ์ (AI)
หอดูดาวโบราณปักกิ่ง ตอนที่ ๑: แท่นสังเกตการณ์และสวนดอกไม้
พิพิธภัณฑ์สถาปัตยกรรมโบราณปักกิ่ง
เที่ยวเมืองตานตง ล่องเรือในน่านน้ำเกาหลีเหนือ
ตระเวนเที่ยวตามรอยฉากของอนิเมะในญี่ปุ่น
เที่ยวชมหอดูดาวที่ฐานสังเกตการณ์ซิงหลง
ทำไมจึงไม่ควรเขียนวรรณยุกต์เวลาทับศัพท์ภาษาต่างประเทศ

ไทย

日本語

中文