文章目录

Python 装饰器:@decorator 语法与函数包装

发布于 2026-04-03 12:04:24 · 浏览 8 次 · 评论 0 条

Python 装饰器:@decorator 语法与函数包装

Python 的装饰器(decorator)是一种让你在不修改原函数代码的前提下,动态地给函数“加功能”的机制。最常见的表现形式就是函数定义前那一行 @xxx。它本质上是一个接收函数作为参数、并返回一个新函数的高阶函数


理解装饰器的基本原理

  1. 定义一个普通函数

    def greet():
        print("Hello!")
  2. 手动“包装”这个函数
    创建一个新函数 enhanced_greet,让它在调用 greet 前后加上额外操作:

    def add_logging(func):
        def wrapper():
            print("Calling function:", func.__name__)
            func()
            print("Function finished.")
        return wrapper
    
    enhanced_greet = add_logging(greet)
    enhanced_greet()

    输出:

    Calling function: greet
    Hello!
    Function finished.
  3. @ 语法糖简化写法
    上面的 enhanced_greet = add_logging(greet) 可以直接写在函数定义上方:

    @add_logging
    def greet():
        print("Hello!")

    这等价于先定义 greet,再执行 greet = add_logging(greet)@ 只是语法糖,核心仍是函数替换


编写能处理任意参数的装饰器

上面的 wrapper 函数没有参数,无法用于带参数的函数。要让装饰器通用,必须使用 *args**kwargs

def add_logging(func):
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__} with args={args}, kwargs={kwargs}")
        result = func(*args, **kwargs)
        print(f"{func.__name__} returned: {result}")
        return result
    return wrapper

@add_logging
def add(a, b):
    return a + b

add(3, 5)

输出:

Calling add with args=(3, 5), kwargs={}
add returned: 8

关键点wrapper 必须接受任意参数,并原样传给被装饰的函数,同时返回其结果。


保留原函数元信息

使用上述装饰器后,greet.__name__ 会变成 "wrapper",这会干扰调试和文档工具。解决方法是使用 functools.wraps

from functools import wraps

def add_logging(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print(f"Calling {func.__name__}")
        return func(*args, **kwargs)
    return wrapper

@add_logging
def greet():
    """Say hello."""
    print("Hello!")

print(greet.__name__)  # 输出: greet
print(greet.__doc__)   # 输出: Say hello.

务必在 wrapper 上加 @wraps(func),否则原函数的名称、文档字符串等元数据会丢失。


带参数的装饰器

有时需要给装饰器本身传参,比如指定日志级别。这时需要三层嵌套:

from functools import wraps

def log_with_level(level):
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            print(f"[{level}] Calling {func.__name__}")
            return func(*args, **kwargs)
        return wrapper
    return decorator

@log_with_level("DEBUG")
def fetch_data():
    return "data"

fetch_data()

输出:

[DEBUG] Calling fetch_data

执行顺序

  • 先调用 log_with_level("DEBUG"),返回 decorator 函数。
  • 再用 @decorator 装饰 fetch_data,即 fetch_data = decorator(fetch_data)

常见用途与内置装饰器

1. @property:将方法转为属性

class Circle:
    def __init__(self, radius):
        self._radius = radius

    @property
    def area(self):
        return 3.14 * self._radius ** 2

c = Circle(2)
print(c.area)  # 直接访问,无需 c.area()

2. @staticmethod@classmethod

class MathUtils:
    @staticmethod
    def add(x, y):
        return x + y

    @classmethod
    def from_string(cls, s):
        x, y = map(int, s.split(','))
        return cls.add(x, y)

print(MathUtils.add(1, 2))          # 3
print(MathUtils.from_string("3,4")) # 7

3. 自定义缓存装饰器

from functools import wraps

def memoize(func):
    cache = {}
    @wraps(func)
    def wrapper(*args):
        if args in cache:
            return cache[args]
        result = func(*args)
        cache[args] = result
        return result
    return wrapper

@memoize
def fib(n):
    if n < 2:
        return n
    return fib(n-1) + fib(n-2)

print(fib(10))  # 高效计算,避免重复递归

装饰器叠加与执行顺序

多个装饰器可以叠加使用,执行顺序从下到上:

def bold(func):
    @wraps(func)
    def wrapper():
        return f"<b>{func()}</b>"
    return wrapper

def italic(func):
    @wraps(func)
    def wrapper():
        return f"<i>{func()}</i>"
    return wrapper

@bold
@italic
def text():
    return "Hello"

print(text())  # 输出: <b><i>Hello</i></b>

等价于:text = bold(italic(text))最靠近函数定义的装饰器最先执行


注意事项与陷阱

  1. 装饰器在模块导入时立即执行
    被装饰的函数在定义时就会被替换,不是在调用时才生效。

  2. 避免在装饰器中修改原函数行为逻辑
    装饰器应只添加辅助功能(如日志、计时),不要改变核心逻辑,否则难以调试。

  3. 带参数装饰器必须返回 decorator 函数
    如果忘记返回内层函数,会导致 @xxx() 后面接的不是函数,引发 TypeError

  4. 类也可以作为装饰器
    只需实现 __call__ 方法:

    class CallCounter:
        def __init__(self, func):
            self.func = func
            self.count = 0
    
        def __call__(self, *args, **kwargs):
            self.count += 1
            return self.func(*args, **kwargs)
    
    @CallCounter
    def say_hi():
        print("Hi")
    
    say_hi()
    print(say_hi.count)  # 1

装饰器是 Python 中实现横切关注点(如日志、权限、缓存)的优雅方式。掌握其原理后,你可以写出更简洁、可复用的代码。

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文