ข้ามไปยังเนื้อหา

Decorators

TypeScript ก็มี decorator เหมือนกัน แต่ยังเป็นฟีเจอร์ทดลอง ผูกอยู่กับ class และพฤติกรรมยังเปลี่ยนไปมาตาม TypeScript กับ ECMAScript proposal

ส่วน decorator ของ Python นิ่งกว่า เรียบง่ายกว่า และใช้ได้กับ function ทุกแบบ ไม่จำกัดแค่ class method framework ฝั่ง Python ใช้กันหนักมาก ทั้ง Flask route, FastAPI endpoint, Celery task และ dataclasses.dataclass จนแทบทุก codebase ต้องมีให้เห็น

Decorator คือ function ที่รับ function มาและคืน function ใหม่กลับไป syntax @name เป็นเพียง shorthand ของ func = name(func)

รูปแบบหลักคือการ wrap function ที่มีอยู่เพื่อเพิ่มพฤติกรรมก่อนและหลังการเรียก ฝั่ง TypeScript หากต้องการผลเดียวกันกับ plain function จะต้องใช้ higher-order function ที่เรียกด้วยตนเอง หรือ class decorator ที่ใช้ได้กับ method เท่านั้น

TypeScript
// TypeScript: class method decorator (experimental, stage 3)
// Works on class methods, not plain functions
function log(target: any, key: string, descriptor: PropertyDescriptor) {
const original = descriptor.value;
descriptor.value = function (...args: any[]) {
console.log(`Calling ${key}...`);
const result = original.apply(this, args);
console.log(`${key} finished.`);
return result;
};
return descriptor;
}
class Greeter {
@log
greet(name: string) {
console.log(`Hello, ${name}!`);
}
}
// For a plain function you must wrap it manually:
function withLog<T extends (...args: any[]) => any>(fn: T): T {
return ((...args: any[]) => {
console.log(`Calling ${fn.name}...`);
const result = fn(...args);
console.log(`${fn.name} finished.`);
return result;
}) as T;
}
const greet = withLog((name: string) => console.log(`Hello, ${name}!`));
greet("Alice");
Python
# Python: decorators work on any function, syntax is @name
import functools
def timer_decorator(func):
@functools.wraps(func) # preserves __name__, __doc__, etc.
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}...")
result = func(*args, **kwargs)
print(f"{func.__name__} finished.")
return result
return wrapper
@timer_decorator # equivalent to: greet = timer_decorator(greet)
def greet(name):
print(f"Hello, {name}!")
greet("Alice")
# Output:
# Calling greet...
# Hello, Alice!
# greet finished.

เมื่อคุณต้องการส่ง configuration ให้ decorator คุณเพิ่ม outer function ที่รับ argument และคืน decorator จริงกลับไป รูปแบบนี้เรียกว่า decorator factory

def repeat(n):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for _ in range(n):
func(*args, **kwargs)
return wrapper
return decorator
@repeat(3) # repeat(3) returns decorator; decorator(say_hi) returns wrapper
def say_hi():
print("Hi!")
say_hi() # prints "Hi!" three times

การซ้อนสามชั้น (factory -> decorator -> wrapper) คือรูปแบบมาตรฐานเมื่อ decorator ต้องการ parameter

import functools
def timer_decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
print(f"Calling {func.__name__}...")
result = func(*args, **kwargs)
print(f"{func.__name__} finished.")
return result
return wrapper
@timer_decorator
def greet(name):
print(f"Hello, {name}!")
greet("Alice")
def repeat(n):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for _ in range(n):
func(*args, **kwargs)
return wrapper
return decorator
@repeat(3)
def say_hi():
print("Hi!")
say_hi()
Python decorator ทำงานเมื่อใด?
`@functools.wraps(func)` ภายใน decorator ทำอะไร?
Python ธรรมดาที่เทียบเท่ากับ `@my_decorator` ที่ใช้กับ `def foo(): ...` คืออะไร?