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



ภาษา python เบื้องต้น บทที่ ๓๐: รู้จักกับเดคอเรเตอร์
เขียนเมื่อ 2016/04/27 17:54
แก้ไขล่าสุด 2024/02/22 11:08
 

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

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

สัญลักษณ์ @ นั้นโดยทั่วไปเวลาคนเห็นคงจะนึกถึงอีเมล แต่ว่าในภาษาไพธอนนั้น @ เป็นสัญลักษณ์ที่เอาไว้สร้างสิ่งที่เรียกว่าเดคอเรเตอร์ (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)
หอดูดาวโบราณปักกิ่ง ตอนที่ ๑: แท่นสังเกตการณ์และสวนดอกไม้
พิพิธภัณฑ์สถาปัตยกรรมโบราณปักกิ่ง
เที่ยวเมืองตานตง ล่องเรือในน่านน้ำเกาหลีเหนือ
ตระเวนเที่ยวตามรอยฉากของอนิเมะในญี่ปุ่น
เที่ยวชมหอดูดาวที่ฐานสังเกตการณ์ซิงหลง
ทำไมจึงไม่ควรเขียนวรรณยุกต์เวลาทับศัพท์ภาษาต่างประเทศ

บทความแต่ละเดือน

2024年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2023年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2022年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2021年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2020年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

ค้นบทความเก่ากว่านั้น

ไทย

日本語

中文