Python 深入浅出装饰器

本篇文章将详细介绍装饰器的来龙去脉!

参考文章:Python 装饰器 | 简单一点学习 easyeasy.me

1. 装饰器是啥?

装饰器(Decorator)本质上是一个函数(或类),它接受一个函数作为输入,然后返回一个新的函数。新的函数通常会在原函数的基础上增加一些功能,比如记录日志、计时、权限验证等。

用生活化的比喻,装饰器就像是给你的手机壳加个防摔功能。手机(原函数)还是那个手机,但壳(装饰器)让它多了点保护功能。你不用改手机本身,就能让它更耐用。

在 Python 中,装饰器通常用 @ 符号来写,比如:

@my_decorator
def my_function():
    print("我是一个函数!")

这里的 @my_decorator 就是在 my_function 上加了个装饰器。接下来我们一步步揭开它的神秘面纱。


2. 函数的基础知识

在讲装饰器之前,得先搞清楚 Python 函数的一些基本概念,因为装饰器本质上是对函数的操作。

2.1 函数是“第一等公民”

在 Python 中,函数可以:

  • 赋值给变量
  • 作为参数传递给其他函数
  • 作为返回值从函数返回

来看个例子:

def say_hello():
    print("Hello!")

# 赋值给变量
greet = say_hello
托管
greet()  # 输出:Hello!

# 作为参数传递
def call_func(func):
    func()

call_func(say_hello)  # 输出:Hello!

这说明函数可以像普通变量一样被操作,为装饰器提供了基础。

2.2 闭包和内部函数

装饰器经常用到闭包(closure),也就是函数里定义的函数能记住外部函数的变量。简单例子:

def outer():
    x = 10
    def inner():
        print(x)  # 能访问外层的 x
    return inner

func = outer()
func()  # 输出:10

这个特性让装饰器可以“包装”函数并保留额外的信息。


3. 从简单例子入手

让我们直接写一个简单的装饰器,感受一下它的威力。

3.1 例子:记录函数执行时间

假设我们想知道一个函数跑了多久,可以用装饰器来实现:

import time

def timer(func):
    def wrapper():
        start = time.time()
        func()
        end = time.time()
        print(f"{func.__name__} 运行时间:{end - start} 秒")
    return wrapper

@timer
def slow_function():
    time.sleep(2)
    print("我睡了 2 秒")

slow_function()
# 输出:
# 我睡了 2 秒
# slow_function 运行时间:2.002345 秒

3.2 发生了什么?

  • @timer 等价于 slow_function = timer(slow_function)
  • timer 函数接收 slow_function 作为参数,返回一个新的函数 wrapper
  • wrapper 函数在调用 func()(即 slow_function)前后加了计时代码。

这个例子展示了装饰器的基本逻辑:用一个函数包装另一个函数,增加新功能。


4. 装饰器的工作原理

为了更深入理解,我们来拆解一下装饰器的结构。

4.1 装饰器的本质

装饰器是一个高阶函数,接收一个函数,返回一个新的函数。新函数通常会调用原函数,并在前后加点“料”。

上面的 timer 装饰器可以改写成这样(去掉 @ 语法糖):

def slow_function():
    time.sleep(2)
    print("我睡了 2 秒")

slow_function = timer(slow_function)
slow_function()

4.2 wrapper 函数的参数问题

上面的例子有个问题:如果 slow_function 有参数怎么办?我们需要让 wrapper 函数能接受任意参数。

改进版:

def timer(func):
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"{func.__name__} 运行时间:{end - start} 秒")
        return result
    return wrapper

@timer
def greet(name):
    time.sleep(1)
    print(f"你好,{name}!")
    return f"问候 {name}"

result = greet("小明")
# 输出:
# 你好,小明!
# greet 运行时间:1.00123 秒
# result = "问候 小明"

这里用了 *args**kwargs 来处理任意参数和关键字参数,还返回了原函数的返回值。


5. 带参数的装饰器

有时候,我们希望装饰器本身也能接受参数。比如,控制日志的级别:

def log(level):
    def decorator(func):
        def wrapper(*args, **kwargs):
            print(f"[{level}] {func.__name__} 被调用")
            return func(*args, **kwargs)
        return wrapper
    return decorator

@log("INFO")
def say_hello(name):
    print(f"你好,{name}!")

say_hello("小明")
# 输出:
# [INFO] say_hello 被调用
# 你好,小明!

5.1 原理拆解

带参数的装饰器多了一层函数嵌套:

  • log(level) 返回一个装饰器函数 decorator
  • decorator 再接收目标函数 func,返回 wrapper
  • @log("INFO") 等价于 say_hello = log("INFO")(say_hello)

这让装饰器变得更灵活,可以根据参数定制行为。


6. 装饰器的实际应用场景

装饰器在实际开发中超级实用,以下是一些常见的场景。

6.1 日志记录

像上面的 log 装饰器,可以记录函数的调用信息:

def log(func):
    def wrapper(*args, **kwargs):
        print(f"调用 {func.__name__},参数:{args}, {kwargs}")
        return func(*args, **kwargs)
    return wrapper

6.2 权限验证

比如,只有管理员才能调用某些函数:

def require_admin(func):
    def wrapper(user, *args, **kwargs):
        if user.get("role") == "admin":
            return func(user, *args, **kwargs)
        else:
            raise PermissionError("需要管理员权限")
    return wrapper

@require_admin
def delete_user(user, user_id):
    print(f"用户 {user_id} 已被删除")

user = {"role": "admin"}
delete_user(user, 123)  # 正常执行
user = {"role": "guest"}
# delete_user(user, 123)  # 抛出 PermissionError

6.3 缓存

可以用装饰器实现函数结果缓存,提高性能:

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

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

print(fib(10))  # 计算很快,因为中间结果被缓存

7. 高级用法:类装饰器和多重装饰器

7.1 类装饰器

装饰器不一定是函数,也可以用类来实现:

class Timer:
    def __init__(self, func):
        self.func = func

    def __call__(self, *args, **kwargs):
        start = time.time()
        result = self.func(*args, **kwargs)
        end = time.time()
        print(f"{self.func.__name__} 运行时间:{end - start} 秒")
        return result

@Timer
def slow_function():
    time.sleep(2)
    print("我睡了 2 秒")

slow_function()

类装饰器通过 __call__ 方法让对象可以像函数一样被调用,适合需要保存状态的场景。

7.2 多重装饰器

可以给一个函数加多个装饰器:

@log("INFO")
@timer
def greet(name):
    time.sleep(1)
    print(f"你好,{name}!")

greet("小明")
# 输出:
# [INFO] greet 被调用
# 你好,小明!
# greet 运行时间:1.00123 秒

多重装饰器的执行顺序是从内到外(由下到上),但定义顺序相反。比如,log 先执行,然后是 timer


8. 常见坑和怎么避坑

8.1 函数元信息丢失

装饰器可能会覆盖原函数的元信息(比如 __name____doc__)。可以用 functools.wraps 解决:

from functools import wraps

def timer(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(f"{func.__name__} 运行时间:{end - start} 秒")
        return result
    return wrapper

@timer
def greet(name):
    """问候用户"""
    print(f"你好,{name}!")

print(greet.__name__)  # 输出:greet
print(greet.__doc__)   # 输出:问候用户

@wraps(func) 会保留原函数的元信息。

8.2 性能问题

装饰器会增加函数调用的开销,尤其是在嵌套多层装饰器时。尽量保持 wrapper 函数简单,避免复杂逻辑。

8.3 调试困难

装饰器可能会让代码逻辑变得复杂,调试时可以用 print 或日志记录 wrapper 的执行过程。


9. 总结与实践建议

装饰器是 Python 中一个强大且灵活的工具,能让你的代码更简洁、更模块化。

  • 装饰器的本质是函数(或类)包装,增加功能。
  • 简单装饰器用 @ 语法糖,带参数的装饰器多一层嵌套。
  • 实际应用场景包括日志、权限、缓存等。
  • 注意元信息丢失问题,用 functools.wraps 解决。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值