Skip to content

บทที่ 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:
# สวัสดี อลิซ
# สวัสดี อลิซ
# สวัสดี อลิซ