Syntax:

  • Decorator
    • a callable that takes a function func and returns a new function
    • 一个可调用对象,接收 func,返回一个“增强后的 func”
  • syntactic sugar (语法糖)
    • 更好写、更好读”的语法形式,但它不增加新能力(= 更基础的写法)
    • Example: @deco is just a nicer way to write hello = deco(hello)
  • *args**kwargs:
    • *args: var-positional arguments (可变位置参数)
    • **kwargs: var-keyword arguments (可变关键字参数)

Python, everything is an object (Numbers, Strings, Functions)

  • hello:是“函数本身”(function object)
  • hello():是“调用这个函数”(execute / run it)

Example 1: Simple decorator (no arguments)

def deco(func):
    def wrapper():
        print("before")  # 包装:原函数前做点事
        func()           # 调原函数
        print("after")   # 包装:原函数后做点事
    return wrapper
 
@deco                    # hello = deco(hello)
def hello():
    print("hello")
 
hello()

Return:

before
hello
after

Example 2: Decorator (with arguments)

  • @deco is syntactic sugar for add = deco(add)
def deco(func):
    def wrapper(a, b):          # wrapper accepts the same args
        print("before", a, b)
        result = func(a, b)     # call original function with args
        print("after", result)
        return result
    return wrapper
 
@deco                          # add = deco(add)
def add(a, b):
    return a + b
 
print(add(1, 2))

Example 3: 通用 Decorator(支持任意参数)

  • *args and **kwargs allow a function to accept an arbitrary number of arguments.
  • *args**kwargs 是 Python 的“固定套路”(约定俗成 + 语法规则)
    • *args 位置参数的“打包/收集”
      • args 是个 tuple
      • 用来接收你传进来的 所有位置参数
    • **kwargs 关键字参数的“打包/收集”
      • kwargs 是个 dict
      • 用来接收你传进来的 所有关键字参数
def deco(func):
    def wrapper(*args, **kwargs):
        print("before", args, kwargs)
        result = func(*args, **kwargs)
        print("after", result)
        return result
    return wrapper

Example 4: Decorator to measure runtime

  • time.perf_counter()
import time
 
def timing(func):
    def wrapper(*args, **kwargs):
        start_time = time.perf_counter()
        result = func(*args, **kwargs)
        end_time = time.perf_counter()
        elapsed_ms = (end_time - start_time) * 1000
        print(f"[{func.__name__}] runtime: {elapsed_ms:.3f} ms")
        return result
    return wrapper
 
@timing
def hello(a, b):
    time.sleep(1)
    print(a, b)
 
hello("Alan", "Wang")
 
print(hello.__name__)  # wrapper, 不方便(undesired)

Note:

  • Uses time.perf_counter() (better for timing than time.time()).
  • Uses @wraps(func) to keep the original function’s name/docstring.
  • add @wraps(func)
    • 让被装饰后的函数“看起来还是原函数”,不然它会变成 wrapper,很多信息会丢、调试会变难。
import time
from functools import wraps
 
def timing(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        try:
            return func(*args, **kwargs)
        finally:
            end = time.perf_counter()
            elapsed_ms = (end - start) * 1000
            print(f"[{func.__name__}] runtime: {elapsed_ms:.3f} ms")
 
    return wrapper
 
@timing
def hello(a, b):
    time.sleep(1)
    print(a, b)
 
hello("Alan", "Wang")
 
print(hello.__name__)  # hello (not wrapper)

Example 5: Decorator with parameters (@repeat(times))

  • repeat(times) 先接收参数(比如 5),并把 times 记住(闭包)
  • 然后返回一个真正的 decorator:decorate(func),它才接收函数并返回 wrapper
from functools import wraps
 
def repeat(times):
    def decorate(func):
        @wraps(func)   # ✅ 这里要传 func
        def wrapper(*args, **kwargs):
            result = None
            for _ in range(times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorate
 
@repeat(5)
def hello(name):
    print(name)
 
hello("Alan")