Python Decorators ของดีมีประโยชน์

Decorator มีทำให้ Code เราอ่านง่ายและสั้นขึ้น

Tanabodin Kamol
3 min readJun 7, 2023

--

ตามคำนิยาม Decorators คือ function ที่รับ function อื่นมาขยายลักษณะการทำงาน อาจจะฟังดูน่าสับสน แต่มันก็ไม่ยากถ้าได้อ่านบทความนี้จบ และได้เห็นตัวอย่างวิธีการทำงานของ Decorators แล้ว

Functions

ก่อนที่จะทำความเข้าใจ Decorators ต้องเริ่มที่การเข้าใจการทำงานของ function ก่อน ดัง code ตัวอย่าที่จะสร้าง function summation มาเพื่อใช้บวกเลขแล้วส่งผลลัพธ์การบวกกลับไป โดยตัวแปรที่ส่งเข้ามาใน function จะถูกเรียกว่า argument กรณีนี้คือ num1, num2

def summation(num1, num2):
return num1 + num2

sum = summation(3, 4)
print(sum)
# 7

หมายเหตุ: ในการเขียน functional programing โดยปกติ เกือบทั้งหมดจะเป็น pure functions ซึ่งจะไม่มีผลข้างเคียงกับส่วนอื่น แต่ว่า Python ยังรองรับแนวคิดการเขียน functional programing อื่น ๆ ด้วย รวมถึง function ในฐานะ First-class objects

First-Class Objects

function เป็น First-class object ซึ่งหมายความว่า function สามารถส่งเป็น argument ได้ เช่นเดียวกับตัวแปรชนิดอื่น (string, int, float, list) ลองดูตัวอย่าง 3 function ด้านล่างนี้ :

# need string as argument
def say_hello(name):
return f"Hello, {name}"

def say_thank_you(name):
return f"Yo {name}, thank you for this blog"

# need function as argument
def greet_bright(func):
return func("Bright")

greet_bright(say_hello)
'Hello Bright'

greet_bright(say_thank_you)
'Yo Bright, thank you for this blog'

greet_bright(say_hello) อ้างอิงถึงสอง function แต่ในรูปแบบที่แตกต่างกันคือ greet_bright() และ say_hello สังเกตุว่า say_hello เขียนชื่อ function โดยไม่มี () ซึ่งหมายความว่าจะมีการส่งเฉพาะการอ้างอิงไปยัง function เท่านั้น function ไม่ได้ดำเนินการ ในทางกลับกัน function greet_bright() เขียนโดยมี () ดังนั้นจึงเรียกตามปกติ

Inner Functions

รู้หรือไม่ เราสามารถที่จะประกาศ function ภายใต้ function ได้ โดยจะเรียก function ที่อยู่ข้างในอีก function ว่า Inner function

def parent():
print("Printing from the parent() function")

def first_child():
print("Printing from the first_child() function")

def second_child():
print("Printing from the second_child() function")

second_child()
first_child()

parent()
# Printing from the parent() function
# Printing from the second_child() function
# Printing from the first_child() function

เราจะเห็นว่าลำดับของ function ไม่สำคัญว่าจะอยู่ก่อนหลัง จะดูแค่การเรียกใช้งาน เหมือนที่เราเห็นการพิมพ์ที่มาจาก second_child() ก่อน first_child()

ตัว Inner function ไม่สามารถถูกเรียกจากภายนอก Parent function ได้ เพราะ scope ถูกกำหนดให้ใช้งานได้ภายใต้ Parent function เท่านั้น

Returning Functions From Functions

Python ยังสามารถให้คุณ return function ได้ด้วย เมื่อต้องการคืนค่า Inner function ภายใน Parent function ออกมาภายนอก

def parent(num):
def first_child():
return "Hi, I am Tanabodin Kamol"

def second_child():
return "Call me Bright"

if num == 1:
return first_child
else:
return second_child

# returning a reference to the function
first = parent(1)
second = parent(2)

first
<function parent.<locals>.first_child at 0x7f599f1e2e18>

second
<function parent.<locals>.second_child at 0x7f599dad5268>

# now you can call inner function as normal function
first()
'Hi, I am Tanabodin Kamol'

second()
'Call me Bright'

โอเค ทั้งหมดที่อ่านมาก็เพียงพอแล้วที่จะทำให้เราเข้าใจ Decorators เพราะงั้นเดี๋ยวเรามาเริ่มกันเลย

มาเริ่มในส่วนของ Decorator กันเถอะ

Simple Decorators

หลังจากอ่านสิ่งต่าง ๆ เกี่ยวกับ function มาแล้ว เราลองทำความเข้าใจ code ข้างล่างนี้ดูก่อนที่จะอ่านคำอธิบายข้างล่าง

def my_decorator(func):
def wrapper():
print("Something is happening before the function is called.")
func()
print("Something is happening after the function is called.")
return wrapper

def say_whee():
print("Whee!")

say_whee = my_decorator(say_whee)

say_whee()
# Something is happening before the function is called.
# Whee!
# Something is happening after the function is called.

การทำงานของส่วนนี้ เราจะเป็นการส่ง function say_whee เข้าไปยัง my_decorator() ตัวของ my_decorator ก็จะทำการ wrap function ที่ถูกส่งเข้าไปใหม่ตามการทำงานที่เราต้องการ แล้ว return function นั้นออกมา ในกรณีนี้คือ print() ก่อนทำ function และหลังทำ function เสร็จ

เอาแบบง่าย ๆ ก็คือ decorators จะ wrap function แล้วเปลี่ยนแปลงการทำงาน

พอเห็นตัวอย่างข้างบนไปเราอาจจะรู้สึกว่าเขียนยาวขนาดนี้เขียน function ปกติยังดีกว่าเลยมั้งเนี่ย คือจริง ๆ แล้วมันมี Syntactic Sugar(syntax ที่ช่วยให้อ่านหรือเขียนได้ง่าย)

Syntactic Sugar

Python สามารถให้เราเรียก Decorators ด้วยวิธีง่าย ๆ ได้ด้วยการใช้สัญลักษ์ @ นำหน้าชื่อ Decorator ที่จะเรียกใช้

def my_decorator(func):
def wrapper():
print("Something is happening before the function is called.")
func()
print("Something is happening after the function is called.")
return wrapper

@my_decorator
def say_whee():
print("Whee!")

say_whee()

ก็คือการใช้ @my_decorator เป็นวิธีง่าย ๆ ในการเขียน say_whee = my_decorator(say_whee) เพื่อให้ Decorator ถูกใช้กับ function นั้น ๆ

Usability มาดูการใช้งานของมันกัน

ในการเขียน code เราอาจจะมี function บางอย่างที่ต้องเรียกใช้หรือเขียนในทุก ๆ function เราสามารถที่จะใช้ Decorator เพื่อที่จะลดความซ้ำซ้อนของงานเราได้ ไม่ว่าจะเป็น ตอนที่เราต้องการทำ validate ข้อมูลทุกครั้งก่อนที่จะทำการเรียก API หรือการจัดการ except error ที่เกิดขึ้น จะเอาไปเช็คว่า user login อยู่หรือเปล่าก็ยังได้

อันนี้ก็จะเป็นตัวอย่าง การใช้ decorator ในการดัก exception ที่เกิดขึ้นเมื่อมีการเรียก API ของเรา จะเป็นการเขียน API ผ่าน framwork ใดก็ได้ ถ้าเป็น python ใช้ได้เหมือนกัน

def exception_handler(func):
def wrapper_function(*args, **kwargs):
try:
return func(*args, **kwargs)
except Exception as ex:
# handler exception here
return False
return wrapper_function

@exception_handler
def add_document():
# Your API here
return "Function Work"

@exception_handler
def add_document_except():
# Your API here
raise Exception

print(add_document())
>>> Function Work
print(add_document_except())
>>> False

ลองคิดถึงกรณีของโปรเจคของเราที่มี API จำนวนมากและต้องมีการใส่ Exception หรือว่าต้องมีการจัดรูปแบบ response ก็สามารถทำผ่าน decorator ได้ ขึ้นอยู่กับการออกแบบของเราเลยว่าจะใช้ทำอะไร

เดี๋ยวจะมาเขียนเพิ่มสำหรับการส่งค่าจาก Decorator มาที่ function ที่โดน wrap

ตัวอย่างการใช้ Decorator Handler API Exception

REFERENCE

  1. https://realpython.com/primer-on-python-decorators/

--

--

Tanabodin Kamol

I always self-study about electronic devices and computer programming, So, I will share what I have learned for all of you! Sometime It’s code for Python