Python : Decorator ตอน 1

First-class citizen

ในทางคณิตศาสตร์ higher-order function คือ function ที่สามารถทำได้อย่างน้อย 1 อย่างต่อไปนี้ [1]
  • 1) รับ function อื่นเป็น input ได้
  • 2) ส่ง output ในรูปแบบของ function ได้
และ decorator ในภาษา Python คือสิ่งที่ทำให้ function ใดๆ เรียกใช้ higher-order function ได้

โดยทั่วไป หน้าที่หลักของ function คือการกระทำบางอย่างกับกับ input และส่งค่าผลลัพธ์ออกไปเป็น output (แต่บาง funtion ไม่ได้ส่ง output ที่เชื่อมโยงกับ input เช่น print() รับค่าเป็น String แต่ส่งค่า None เป็น output แต่มีการนำ input ไปแสดงบนหน้าจอ เรียกว่า side effect)

Function ในภาษา Python จัดเป็น "first-class object"(first-class citizen)[2] หมายความว่า ตัวของ function เป็นได้หลายรูปแบบ :-
  • - assigned to variable
  • - defined in another function
  • - returned as output
  • - passed as argument

การ assign function ให้กับ variable
    
#-- this is function    
def square(x):
	return x**2

#-- assign function to variable
pow_2 = square

pow_2(4)

# result is 16
    

การ define function ภายใน function
    
#-- function in function   
def square(x):
    def plus(x,y):
    	return x + y
	_sum = 0
	for _ in range(x):
    	_sum = plus(_sum,x)
    return _sum

square(4)

# result is 16
    

function as argument
    
def mul(x,y):
	return x/2
	
def plus(x,y):
	return x + y

def operate(f1,f2,var1,var2):
	result = f2(f1(var1,var2),f1(var2,var1)) 
	return result

v1 = 4
v2 = 3
print(operate(mul,plus,v1,v2))

# result is 3.5
    

return result as a function
    
def mul(x,y):
	return x/2
	
def plus(x,y):
	return x + y

def operate(f1,f2,var1,var2):
	return f2(f1(var1,var2),f1(var2,var1)) 

v1 = 4
v2 = 3
print(operate(mul,plus,v1,v2))

3.5
    

Simple decoration

หลังจากเข้าใจกับสิ่งที่เรียกว่า "first-class object" หรือ "first-class citizen" แล้ว ก็มาดูเรื่องการสร้าง decorator กัน เริ่มต้นด้วย
  
def decorator_func(input_func):
	def wrapper():
		print("Before input function is executed.")
		input_func()
		print("After input function is executed.")
		
	return wrapper
	
def stand_alone_func():
	print("I am standalone function, do not modify me.")


#-- decoration happends here
decoration = decorator_func(stand_alone_func)	

#-- execute 
decoration()

#-- out put is 
# Before input function is executed.
# I am standalone function, do not modify me.
# After input function is executed.
      

พิจารณาดู decorator_func() มีข้อสังเกตุคือ
  • 1) การใช้ function เป็น argument
  • 2) มี inner function
  • 3) return เป็น function
สรุปว่า decorator_func() คือ higher-order function

ดูบรรทัดที่มีชุดคำสั่ง decoration = decorator_func(stand_alone_func) การ decorate เกิดขึ้นที่นี่ ขั้นตอนคือการ ส่ง stand_alone_func() ให้เป็น argument ของ decorator_func() ทำให้ชุดคำสั่งภายใน stand_alone_func() ถูกโยงเข้าไปยัง inner function ชื่อ wrapper() ผลที่จะเกิดขึ้นคือการทำงานร่วมกันระหว่างชุดคำสั่งทั้งที่มีอยู่ก่อนแล้วและส่วนที่ถูกส่งเข้าไป แล้วสุดท้ายก็ assign ไปยังตัวแปรชื่อ declaration ขั้นตอนทั้งหมดนี้เหมือนกับการตกแต่ง (decoration) stand_alone_func() ด้วย wrapper function ภายใน decorator_fun()
รูปที่ 1 decoration เหมือนการตกแต่ง stand alone function ด้วย wrapper function


Syntactic Sugar

การทำ decoration ตามแบบตัวอย่างข้างต้น ใช้งานได้แต่ออกจะเทอะทะไปสักหน่อย ในภาษา Python กำหนดให้ใช้เครื่องหมาย "@" วางไว้หน้าชื่อ decorator function (เรียกว่า pie syntax) ซึ่งจะต้องมีก่อนหน้าการสร้าง stand alone function จากตัวอย่างข้างต้น สามารถเขียนใหม่ด้วย pie syntax เป็น

def decorator_func(input_func):
	def wrapper():
		print("Before input function is executed.")
		input_func()
		print("After input function is executed.")
		
	return wrapper

@decorator_func	
def stand_alone_func():
	print("I am standalone function, do not modify me.")


stand_alone_func()

#-- out put is 
# Before input function is executed.
# I am standalone function, do not modify me.
# After input function is executed.  
  

หลังจากทำ decoration แล้ว เมื่อเรียกใช้ stand alond function ผลที่ได้จะต่างออกไปจากที่กำหนดไว้ ตัวอย่างนี้จะทำให้เห็นหน้าที่ของ decorator function ชัดเจนคือ "การเปลี่ยนแปลงพฤติกรรมของ stand alone function เหมือนการตกแต่ง" พิจารณาตัวอย่างต่อไปนี้

def sqaure(input_func):
	def wrapper():
		result = input_func()**2
		return result
		
	return wrapper

@sqaure
def decorated():
	return 5

def undecorated():
	return 5


print("Result from decorated change from {} to {}.".format(undecorated(),decorated()))
print("Result from undecorated is still {}.".format(undecorated()))

#-- out put is 
# Result from decorated change from 5 to 25.
# Result from undecorated is still 5.  

จะเห็นว่าผลของ decoration ทำให้ function ที่ชุดคำสั่งจะระบุให้ return ค่าเดียวกัน ได้ผลออกมาต่างกันได้


อ้างอิง

[1] https://en.wikipedia.org/wiki/Higher-order_function
[2] https://en.wikipedia.org/wiki/First-class_citizen

ความคิดเห็น