0Pricing
Python Academy · 课时

编写自定义装饰器

使用包装函数和 functools.wraps 创建装饰器。

编写自定义装饰器 是 CoddyKit 上的免费 Python Academy 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Python Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Python Academy 课程共包含 4 节课。

简介

装饰器是用于包装其他函数的函数,可以在不修改原函数的情况下,在其执行前后添加行为。

什么是装饰器

函数上方的 @decorator 是一种语法糖,等价于:func = decorator(func)。装饰器接收函数并返回一个包装器。
def shout(func):
    def wrapper(*a, **kw):
        result = func(*a, **kw)
        return str(result).upper()
    return wrapper
@shout
def greet(name): return f'hello {name}'
print(greet('Alice'))

包装器模式

包装函数与原函数具有相同的签名。它会在内部调用原函数,并在调用前后添加行为。
def timer(func):
    import time
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        print(f'{func.__name__} took {time.time()-start:.4f}s')
        return result
    return wrapper
@timer
def slow(): import time; time.sleep(0.01)
slow()

functools.wraps

在包装函数上使用 @wraps(func) 可以保留原函数的 __name__、__doc__ 和 __annotations__。
from functools import wraps
def log(func):
    @wraps(func)
    def wrapper(*a, **kw):
        print(f'calling {func.__name__}')
        return func(*a, **kw)
    return wrapper
@log
def add(a, b): return a + b
print(add.__name__)
add(1, 2)

带参数的装饰器

带参数的装饰器需要多一层包装:使用一个接收参数并返回装饰器的工厂函数。
def repeat(n):
    def decorator(func):
        from functools import wraps
        @wraps(func)
        def wrapper(*a, **kw):
            for _ in range(n): func(*a, **kw)
        return wrapper
    return decorator
@repeat(3)
def hi(): print('hi')
hi()

叠加装饰器

多个装饰器会从下到上叠加:@d2 @d1 表示 func = d2(d1(func))。最外层的装饰器(顶部装饰器)最后运行。
def bold(f):
    from functools import wraps
    @wraps(f)
    def w(*a,**k): return '<b>' + f(*a,**k) + '</b>'
    return w
def italic(f):
    from functools import wraps
    @wraps(f)
    def w(*a,**k): return '<i>' + f(*a,**k) + '</i>'
    return w
@bold
@italic
def text(): return 'hello'
print(text())

基于类的装饰器

实现了 __call__ 的类可以作为装饰器使用。它可以在多次调用之间保存状态。
class Counter:
    def __init__(self, func): self.func,self.count=func,0
    def __call__(self, *a, **kw):
        self.count+=1
        return self.func(*a, **kw)
@Counter
def greet(name): return f'hi {name}'
greet('Alice'); greet('Bob')
print(greet.count)

记忆化装饰器

缓存之前的结果,避免重复计算:if args in cache: return cache[args]。functools.lru_cache 内置实现了这一功能。
from functools import lru_cache
@lru_cache(maxsize=None)
def fib(n):
    if n < 2: return n
    return fib(n-1) + fib(n-2)
print(fib(30))

授权装饰器

装饰器常用于访问控制:运行函数前检查用户是否已经登录。
def requires_auth(func):
    from functools import wraps
    @wraps(func)
    def wrapper(user, *a, **kw):
        if not user.get('authenticated'):
            raise PermissionError('Not authenticated')
        return func(user, *a, **kw)
    return wrapper
@requires_auth
def dashboard(user): return 'Welcome!'
try: dashboard({'authenticated': False})
except PermissionError as e: print(e)

重试装饰器

包装一个函数,使其在失败时重试:for _ in range(retries): try: return func() except Exception: pass
import random
from functools import wraps
def retry(times=3):
    def dec(func):
        @wraps(func)
        def wrapper(*a, **kw):
            for i in range(times):
                try: return func(*a, **kw)
                except Exception as e:
                    if i==times-1: raise
        return wrapper
    return dec
@retry(3)
def flaky():
    if random.random()<0.7: raise ValueError
    return 'ok'
try: print(flaky())
except ValueError: print('failed')

装饰器最佳实践

始终使用 @functools.wraps。让每个装饰器只负责一个目的。记录它添加的功能。将装饰器与被装饰的函数分开测试。
from functools import wraps
def validate_positive(func):
    @wraps(func)
    def wrapper(x, *a, **kw):
        if x <= 0: raise ValueError(f'{x} must be positive')
        return func(x, *a, **kw)
    return wrapper
@validate_positive
def sqrt(x): return x**0.5
print(sqrt(9))

快速检查

将 @functools.wraps(func) 应用于包装器时,它会做什么?

回顾

装饰器:func = decorator(func)。使用 wrapper(*args, **kwargs)。@wraps 会保留元数据。参数化装饰器会增加一层包装。使用多个 @decorators 叠加装饰器。

继续学习

进步非常出色!请继续学习,掌握下一个概念。

常见问题解答

「编写自定义装饰器」课时是免费的吗?

是的 — 「编写自定义装饰器」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Python Academy 课程的其余内容,请升级到 CoddyKit PRO。 Python Academy 课程共包含 4 节课。

「编写自定义装饰器」这节课中我会学到什么?

使用包装函数和 functools.wraps 创建装饰器。 你通过在浏览器中直接运行的动手代码来练习 Python Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Python Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Python Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。

「编写自定义装饰器」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Python Academy 课中编写并运行代码吗?

能。每节 Python Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 作为一等对象的函数
  2. 编写自定义装饰器
  3. @property 装饰器
  4. @classmethod 与 @staticmethod
← 返回 Python Academy