ในตอนที่ 1 ได้กล่าวถึงเรื่องการเป็น first-class citizen ของ function ในภาษา Python และการสร้าง function decoration แบบง่ายมาแล้ว ตอนนี้จะกล่าวถึงเรื่อง การส่ง arguments และ chaining decorator พร้อมยกตัวอย่างเพิ่มเติมเพื่อให้เห็นภาพชัดขึ้น
Recap *args และ **kwargs
ในภาษา Python มี keyword 2 ตัวคือ *args และ **kwargs ที่เกี่ยวข้องกับการส่งผ่าน arguments ทั้งสองมีความหมายต่างกัน *args ใช้ส่ง arguments เป็นชุดโดยไม่ต้องมี keyword ในขณะที่ **kwargs ใช้ส่ง arguments เป็นชุดและมีการใช้ keyword ยกตัวอย่างข้างล่างนี้ เป็นการส่ง arguments เป็นชุดโดยไม่มี keywords
def X(*args):
for a in args:
print(a)
X(10,20,30)
#-- output is
#10
#20
#30
ตัวอย่างการส่ง arguments เป็นชุดมี keywords
def X(**kwargs):
for k,v in kwargs.items():
print("{} = {}".format(k,v))
X(a=10,b=20,c=30)
#-- output is
#a=10
#b=20
#c=30
ตัวอย่างการส่ง arguments โดยใช้แบบไม่มี keywords และมี keywords
def X(*args.**kwargs):
for a in args:
print(a)
for k,v in kwargs.items():
print("{} = {}".format(k,v))
X(10,20,30,a=10,b=20,c=30)
#-- output is
#10
#20
#30
#a=10
#b=20
#c=30
ในทางปฏิบัติมีข้อควรจำอยู่ 2 อย่าง คือ ไม่จำเป็นต้องใช้กลุ่มอักษร args,kwargs ใช้อักษรอื่นแทนได้ แต่ต้องมี *,** เพื่อระบุว่าเป็น positional arguments หรือ keyword arguments และหากต้องการใช้ทั้งสองแบบใน function เดียวกัน จะต้องให้ positional arguments มาก่อน keyword arguments เสมอ
def X(*args.**kwargs):
for a in args:
print(a)
for k,v in kwargs.items():
print("{} = {}".format(k,v))
X(a=10,10,b=20,20,30,c=30)
#-- output is
# SyntaxError: positional argument follows keyword argument
การส่ง arguments ไปยัง decorator function
ตัวอย่างแรกคือการเลียนแบบการทำงานของ time_it() ของ Python
ตัวอย่างนี้ argument ของ stand alone function ถูกส่งเข้าไปยัง wrapper function ภายใน decorator (mimic_time_it)
import time
import math
def mimic_time_it(func):
def wrapper(*args):
start = time.time()
result = func(*args)
print("This function took {} seconds to work.".format(time.time() - start))
return result
return wrapper
@mimic_time_it
def power(x,y):
return "result is {}".format(math.pow(x,y))
print(power(3,5))
#-- result is
#This function took 3.4332275390625e-05 seconds to work.
#243.0
ตัวอย่างนี้ argument ของ stand alone function ถูกส่งเข้าไปยัง wrapper function ภายใน decorator (mimic_time_it)
อีกตัวอย่าง เก็บ state ไว้ใน decorator
ตัวอย่างนี้ใช้ keyword "nonlocal" หมายถึงตัวแปรที่ไม่ใช่ global (ประกาศไว้นอก scope ของ function) และ local (ประกาศภายใน scope ของ function) จะนำมาใช้ในกรณีที่มีการประกาศ inner function หรือ nested function โดย inner function แต่ละ function สามารถเข้าถึงได้ ในตัวอย่างนี้ใช้เก็บจำนวนครั้งของการเรียกใช้ function
def count_it(func):
count = 0
def wrapper(*args):
nonlocal count
count+=1
print("This is your {} times.".format(count))
return func(*args)
return wrapper
@count_it
def power(x,y):
return "result is {}\n".format(math.pow(x,y))
print(power(1,2))
print(power(3,4))
print(power(5,6))
print(power(7,8))
'''
-- result is
This is your 1 times.
result is 1.0
This is your 2 times.
result is 81.0
This is your 3 times.
result is 15625.0
This is your 4 times.
result is 5764801.0
'''
ตัวอย่างนี้ใช้ keyword "nonlocal" หมายถึงตัวแปรที่ไม่ใช่ global (ประกาศไว้นอก scope ของ function) และ local (ประกาศภายใน scope ของ function) จะนำมาใช้ในกรณีที่มีการประกาศ inner function หรือ nested function โดย inner function แต่ละ function สามารถเข้าถึงได้ ในตัวอย่างนี้ใช้เก็บจำนวนครั้งของการเรียกใช้ function
ตัวอย่างข้างต้น เป็นตัวอย่างที่ส่งตัว stand alone function เป็น argument ให้กับ decorator function ตัวอย่างต่อไปนี้ใช้การตัวแปรเป็น arguments อาจพบได้บ่อยใน web application framework เช่น Flask, Bottle
ในตัวอย่างนี้ file_name="data.txt" ถูกส่งไปยัง log_it function และ text="Hello World 1" ถูกส่งไปยัง wrapper function โดยมี midle_function เป็นตัวรับ test_log function เป็น argument
import time
import datetime;
def log_it(*args,**kwargs):
def midle_function(func):
log_fname = kwargs['file_name']
def wrapper(*args,**kwargs):
nonlocal log_fname
ts = datetime.datetime.now().timestamp()
line = "{} : {}\n".format(ts,kwargs['text'])
with open(log_fname,'a') as f :
f.write(line)
func()
return wrapper
return midle_function
@log_it(file_name="data.txt")
def test_log(**kwargs):
print("It is logged!")
test_log(text="Hello World 1")
test_log(text="Hello World 2")
test_log(text="Hello World 3")
test_log(text="Hello World 1")
test_log(text="Hello World 2")
test_log(text="Hello World 3")
'''
Would see these on screen :
It is logged!
It is logged!
It is logged!
It is logged!
It is logged!
It is logged!
and these in "data.txt"
1623382604.706355 : Hello World 1
1623382604.706575 : Hello World 2
1623382604.706677 : Hello World 3
1623382626.668089 : Hello World 1
1623382626.668272 : Hello World 2
1623382626.668382 : Hello World 3
'''
ในตัวอย่างนี้ file_name="data.txt" ถูกส่งไปยัง log_it function และ text="Hello World 1" ถูกส่งไปยัง wrapper function โดยมี midle_function เป็นตัวรับ test_log function เป็น argument
Chain decorator
stand alone function 1 function สามารถถูก decorate ได้มากกว่า 1 decorator
import time
import math
def mimc_time_it(func):
def wrapper(*args):
start = time.time()
result = func(*args)
print("This function took {} seconds to work.".format(time.time() - start))
return result
return wrapper
def count_it(func):
count = 0
def wrapper(*args):
nonlocal count
count+=1
print("This is your {} times.".format(count))
return func(*args)
return wrapper
@count_it
@mimc_time_it
def power(x,y):
return "result is {}\n".format(math.pow(x,y))
print(power(1,2))
print(power(3,4))
print(power(5,6))
print(power(7,8))
'''
result are :
This is your 1 times.
This function took 2.0503997802734375e-05 seconds to work.
result is 1.0
This is your 2 times.
This function took 1.3828277587890625e-05 seconds to work.
result is 81.0
This is your 3 times.
This function took 2.1457672119140625e-06 seconds to work.
result is 15625.0
This is your 4 times.
This function took 3.337860107421875e-06 seconds to work.
result is 5764801.0
'''
ความคิดเห็น
แสดงความคิดเห็น