ใช้ประโยชน์จากการที่ฟังก์ชันเป็นอ็อบเจกต์ชั้นหนึ่ง เพื่อเขียนเดคอเรเตอร์ที่แข็งแรง ปรับพารามิเตอร์ได้ และโปร่งใส
เปิดบทเรียนนี้ใน Kodokonใน Python ฟังก์ชันคืออ็อบเจกต์ตัวหนึ่งเหมือนอ็อบเจกต์อื่น ๆ คุณกำหนดมันให้ตัวแปรได้ ส่งมันเป็นอาร์กิวเมนต์ได้ และคืนมันกลับมาได้ ฟังก์ชันอันดับสูง (higher-order function) คือฟังก์ชันที่รับหรือคืนฟังก์ชัน - sorted(data, key=...) ก็เป็นหนึ่งในนั้น เพิ่ม closure เข้าไป ซึ่งฟังก์ชันข้างในจับตัวแปรของฟังก์ชันที่ครอบมันไว้และรักษามันให้มีชีวิตอยู่ต่อหลังฟังก์ชันนั้นคืนค่าไปแล้ว เท่านี้คุณก็มีส่วนผสมครบทุกอย่างของเดคอเรเตอร์
def make_multiplier(factor):
def multiply(value):
return value * factor
return multiply
double = make_multiplier(2)
triple = make_multiplier(3)
print(double(10), triple(10)) # 20 30เดคอเรเตอร์ คือฟังก์ชันที่รับฟังก์ชันเข้าไปและคืนอีกฟังก์ชันหนึ่งออกมา - โดยปกติคือ wrapper ที่เพิ่มพฤติกรรมรอบ ๆ การเรียกใช้ ไวยากรณ์ @timed เหนือ def slow_sum เป็นเพียงน้ำตาลเคลือบของ slow_sum = timed(slow_sum) เท่านั้นเป๊ะ ๆ ตัว wrapper รับ *args, kwargs เพื่อไม่ผูกติดกับลายเซ็นของฟังก์ชัน และ คืน** ผลลัพธ์ของฟังก์ชันที่ถูกตกแต่ง - การลืมข้อนี้คือความผิดพลาดคลาสสิกที่เปลี่ยนค่าคืนกลับทุกค่าให้กลายเป็น None
import functools
import time
def timed(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
delay = time.perf_counter() - start
print(f"{func.__name__}: {delay:.4f}s")
return result
return wrapper
@timed
def slow_sum(n):
return sum(range(n))
print(slow_sum(1_000_000))หากต้องการให้เดคอเรเตอร์รับพารามิเตอร์ได้ (@repeat(times=3)) คุณต้องมีอีกหนึ่งชั้น: โรงงาน (factory) ที่รับพารามิเตอร์และคืนเดคอเรเตอร์ออกมา ซึ่งเดคอเรเตอร์นั้นจะคืน wrapper ต่ออีกที ฟังก์ชันซ้อนกันสามชั้น - นั่นคือราคาของความยืดหยุ่น หากสถานะซับซ้อนขึ้น คลาสที่มีเมทอด __call__ มักอ่านง่ายกว่า จงสงวนเดคอเรเตอร์ไว้สำหรับเรื่องที่ตัดขวางทุกส่วนของระบบ: การบันทึกล็อก การแคช การลองใหม่ การควบคุมสิทธิ์การเข้าถึง เดคอเรเตอร์ที่ไปเปลี่ยนตรรกะเชิงธุรกิจจะซ่อนสาระสำคัญไว้จากผู้อ่าน
import functools
def repeat(times):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
result = None
for _ in range(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(times=3)
def greet(name):
print(f"Hello {name}")
greet("Ada")