บทที่ 5.1: Decorators และ Closures (การขยายการทำงานของฟังก์ชัน)
เอกสารนี้อธิบายถึงแนวคิด First-Class Functions, การทำงานของ Closures และการใช้ Decorators เพื่อขยายหรือปรับแต่งพฤติกรรมของฟังก์ชันโดยไม่ต้องแก้ไขโค้ดเดิมภายในฟังก์ชันนั้น
1. แนวคิดพื้นฐาน: First-Class Functions และ Closures
ในภาษา Python ฟังก์ชันถือเป็น First-Class Citizen หมายความว่าสามารถส่งฟังก์ชันเป็นอาร์กิวเมนต์ คืนค่าฟังก์ชันออกจากฟังก์ชันอื่น หรือกำหนดฟังก์ชันให้ตัวแปรได้
1.1 First-Class Functions
def greet(name):
return f"สวัสดี {name}"
# กำหนดฟังก์ชันให้กับตัวแปร
my_func = greet
print(my_func("เซบาสเตียน")) # Output: สวัสดี เซบาสเตียน
1.2 Closures
Closure คือ ฟังก์ชันภายใน (Inner Function) ที่สามารถจดจำและเข้าถึงตัวแปรในขอบเขตของฟังก์ชันภายนอก (Outer Function) ได้ แม้ว่าฟังก์ชันภายนอกนั้นจะทำงานเสร็จสิ้นและคืนค่ากลับไปแล้วก็ตาม
def make_multiplier(factor):
# Outer function รับ factor
def multiply(number):
# Inner function เข้าถึง factor จากขอบเขตภายนอก
return number * factor
return multiply
double = make_multiplier(2)
triple = make_multiplier(3)
print(double(5)) # Output: 10
print(triple(5)) # Output: 15
2. Decorators พื้นฐาน
Decorator คือฟังก์ชันที่รับฟังก์ชันอื่นเป็นพารามิเตอร์ และคืนค่าเป็นฟังก์ชันใหม่ที่ถูกเพิ่มความสามารถแล้ว โดยใช้สัญลักษณ์ @ ในการครอบฟังก์ชันเป้าหมาย
2.1 โครงสร้าง Decorator
def my_decorator(func):
def wrapper():
print("--- เริ่มต้นการทำงาน ---")
func()
print("--- สิ้นสุดการทำงาน ---")
return wrapper
# การใช้งานผ่านสัญลักษณ์ @
@my_decorator
def say_hello():
print("Hello World!")
say_hello()
# Output:
# --- เริ่มต้นการทำงาน ---
# Hello World!
# --- สิ้นสุดการทำงาน ---
3. Decorators ที่รับ Parameters และ คืนค่า
เพื่อให้ Decorator รองรับฟังก์ชันที่มีการรับค่าอาร์กิวเมนต์และมีการ return ค่า จะต้องใช้ *args และ **kwargs ภายในฟังก์ชัน wrapper
import time
def timer_decorator(func):
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs) # รับผลลัพธ์จากฟังก์ชันเดิม
end_time = time.time()
print(f"ฟังก์ชัน '{func.__name__}' ใช้เวลาทำงาน: {end_time - start_time:.4f} วินาที")
return result # คืนค่าผลลัพธ์กลับไป
return wrapper
@timer_decorator
def compute_square_sum(n):
return sum(i ** 2 for i in range(n))
total = compute_square_sum(1000000)
print(f"ผลลัพธ์: {total}")
4. การรักษา Metadata ด้วย functools.wraps
เมื่อใช้ Decorator ชื่อและ docstring ของฟังก์ชันเดิมจะถูกแทนที่ด้วยฟังก์ชัน wrapper การใช้ @functools.wraps จะช่วยรักษา Metadata ของฟังก์ชันเดิมไว้
from functools import wraps
def my_logger(func):
@wraps(func) # สงวน Metadata ของฟังก์ชันเดิม
def wrapper(*args, **kwargs):
"""Wrapper function"""
print(f"กำลังเรียกใช้งาน: {func.__name__}")
return func(*args, **kwargs)
return wrapper
@my_logger
def add(a, b):
"""ฟังก์ชันสำหรับการบวกเลขสองจำนวน"""
return a + b
print(add.__name__) # Output: add (หากไม่ใช้ @wraps จะได้ 'wrapper')
print(add.__doc__) # Output: ฟังก์ชันสำหรับการบวกเลขสองจำนวน
5. Decorators ที่รับ Arguments (Decorator Factories)
หากต้องการให้ตัว Decorator เองรับพารามิเตอร์ได้ จะต้องสร้างฟังก์ชันซ้อนกัน 3 ชั้น
def repeat(num_times):
def decorator_repeat(func):
@wraps(func)
def wrapper(*args, **kwargs):
for _ in range(num_times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator_repeat
@repeat(num_times=3)
def greet_person(name):
print(f"สวัสดี {name}")
greet_person("อลิซ")
# Output:
# สวัสดี อลิซ
# สวัสดี อลิซ
# สวัสดี อลิซ