惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

S
Security @ Cisco Blogs
The Last Watchdog
The Last Watchdog
Application and Cybersecurity Blog
Application and Cybersecurity Blog
aimingoo的专栏
aimingoo的专栏
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
PCI Perspectives
PCI Perspectives
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
月光博客
月光博客
V
Visual Studio Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
T
Tailwind CSS Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
L
LangChain Blog
B
Blog RSS Feed
小众软件
小众软件
N
News | PayPal Newsroom
Attack and Defense Labs
Attack and Defense Labs
Microsoft Azure Blog
Microsoft Azure Blog
V
Vulnerabilities – Threatpost
The Hacker News
The Hacker News
T
Tor Project blog
A
Arctic Wolf
Jina AI
Jina AI
Hacker News: Ask HN
Hacker News: Ask HN
F
Fortinet All Blogs
Cloudbric
Cloudbric
S
Secure Thoughts
L
LINUX DO - 热门话题
博客园 - 司徒正美
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
S
Security Affairs
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
J
Java Code Geeks
P
Privacy International News Feed
AWS News Blog
AWS News Blog
S
Securelist
TaoSecurity Blog
TaoSecurity Blog
AI
AI
O
OpenAI News
C
Cyber Attacks, Cyber Crime and Cyber Security
K
Kaspersky official blog
T
The Blog of Author Tim Ferriss
大猫的无限游戏
大猫的无限游戏
Google DeepMind News
Google DeepMind News
Know Your Adversary
Know Your Adversary
P
Palo Alto Networks Blog
T
Tenable Blog
Last Week in AI
Last Week in AI
WordPress大学
WordPress大学
S
SegmentFault 最新的问题

博客园 - 箫笛

Tkinter - Button 组件 Tkinter - Label 组件 Tkinter - tk 变量 Tkinter - 事件与绑定 Tkinter - 几何管理器 Tkinter - 核心概念 Tkinter - 快速开始 Tkinter - 介绍 Python 编程 - 条件表达式 Python 编程 - 星号下划线参数释义 shell 编程 - shell 脚本的交互方式 Python insall - macOS 系统安装python的几种方式 Miniconda - Python 环境管理工具 Tkinter - Python GUI 开发 Python 编程 - 下划线命名的区别 Python 编程 - 元类编程 Python 编程 - 多重继承与MRO Python 编程 - 描述符协议 Python 编程 - 类型注解 Python 编程 - 生成器表达式 Python 编程 - 集合 Python 编程 - 闭包 Python 编程 - 列表推导式 Python 编程 - 函数式编程 Python 编程 - lambda 函数 Python 编程 - 文件操作 Python 编程 - 输入与输出 Python 编程 - 面向对象编程 Python 编程 - 元组(tuple) Python 编程 - 字符串(str) Python 编程 - 字典(dict) Python 编程 - 列表(list) Python 编程 - 数据类型和数据结构 Python 编程 - 语句 Python 编程 - 函数 windows - WSL 的安装与使用 shell编程 - dialog 程序使用指南 FE Team - 如何做好前端代码审查 git 提交的撤销和恢复 React15 - redux-saga 如何在saga中实现轮询接口调用? React15 - React CSS Modules BEM命名实践 React15 - React 15 中 componentWillReceiveProps 为什么会多次调用, 同时componentDidUpdate 也会多次调用? React15 - React15类组件多次执行render方法的原因? React15 - React15应用中代码逻辑复用方案 React15 - React状态同步问题解决 React15 - React 15 中 React.pureComponent 的使用场景 React15 - React 15应用在页面渲染时会多次执行类组件的render 函数的原因 React15 - React 15 中能用 componetDidUpdate 代替 componentWillReceiveProps 吗? React15 - React 15 生命周期函数详解 React15 - 如何在React 15中实现自定义的事件订阅与发送(例如组件间通信) React15 - React15应用中的事件订阅和发送机制 React15 - CSS中的BEM规范 React15 - React CSS Modules BEM命名实践 React15 - 写sass 样式文件,嵌套的结构好,还是扁平的结构好? React15 - sass 中 @mixin 和 @extend 的区别是什么? React15 - React 15 应用 如何使用Css moudules 方式进行模块化开发 React15 - React15应用Sass使用指南 React15 - React 15 应用如何进行性能优化?
Python 编程 - 装饰器
箫笛 · 2026-06-21 · via 博客园 - 箫笛

Python 3 装饰器是在不修改原函数代码的前提下,为函数动态添加额外功能的高级工具。其核心基于“闭包”和“函数式编程”思想。

下面我从基础原理高阶实战,为你构建一套完整的知识体系。

1. 核心原理:函数即“对象”

在 Python 中,函数是一等公民,这意味着它可以像变量一样被传递、赋值和返回。

def say_hello():
    print("Hello")

# 将函数赋值给变量
greet = say_hello
greet()  # 输出 Hello

# 将函数作为参数传入另一个函数
def run(func):
    func()
run(say_hello)  # 输出 Hello

装饰器正是利用了这一特性:它是一个接收函数作为参数、返回新函数的可调用对象。


2. 手写第一个装饰器(无参数)

最标准的装饰器结构是三层闭包(外层接收函数,内层包装逻辑,返回包装函数)。

import functools
import time

# 定义一个计时装饰器
def timer(func):
    @functools.wraps(func)  # 关键!保留原函数的元数据(如 __name__,文档字符串)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = func(*args, **kwargs)  # 执行原函数
        end = time.perf_counter()
        print(f"{func.__name__} 执行耗时: {end - start:.4f} 秒")
        return result
    return wrapper

# 使用语法糖 @
@timer
def long_task(n):
    time.sleep(n)
    return n * 2

long_task(1)  # 输出: long_task 执行耗时: 1.0002 秒

3. 带参数的装饰器(三层嵌套)

如果需要让装饰器本身接收参数(如重复次数、日志级别),需要再包裹一层“装饰器工厂函数”。

def repeat(times):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for _ in range(times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@repeat(times=3)
def say_hi():
    print("Hi")

say_hi()  # 会连续打印 3 次 "Hi"

(调用顺序:repeat(3) 返回 decorator@decorator 再接收 say_hi


4. 类装饰器(使用 __call__

如果装饰逻辑复杂、需要维护状态,可以用类来实现(利用 __init__ 接收函数,__call__ 包装逻辑)。

class CountCalls:
    def __init__(self, func):
        functools.update_wrapper(self, func)  # 手动拷贝元数据
        self.func = func
        self.count = 0

    def __call__(self, *args, **kwargs):
        self.count += 1
        print(f"调用第 {self.count} 次")
        return self.func(*args, **kwargs)

@CountCalls
def test():
    pass

test()  # 调用第 1 次
test()  # 调用第 2 次

5. 关键陷阱与最佳实践(务必注意)

陷阱 解决方案
元数据丢失:装饰后的函数 __name__docstring 会变成 wrapper 必须使用 @functools.wraps(func) 修复。
作用域问题:内层 wrapper 中修改外层变量(如 count)会报错。 使用 nonlocal 声明(Python 3 专属)。
类装饰器与 self:装饰类方法时,wrapper 必须接收 self 参数,否则会报错。 wrapper(*args, **kwargs) 中透传,self 会自动传递。

6. 经典实战场景(拿来即用)

场景一:重试机制(处理网络抖动)

def retry(max_attempts=3):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(1, max_attempts + 1):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    if attempt == max_attempts:
                        raise
                    print(f"重试第 {attempt} 次...")
        return wrapper
    return decorator

场景二:参数校验(类型检查)

def type_check(**types):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            # 绑定参数名和值
            bound = func.__annotations__  # 或使用 inspect.signature
            for name, value in {**kwargs}.items():
                if name in types and not isinstance(value, types[name]):
                    raise TypeError(f"{name} 应为 {types[name]}")
            return func(*args, **kwargs)
        return wrapper
    return decorator

@type_check(name=str, age=int)
def greet(name, age):
    print(f"{name} is {age}")

场景三:缓存(LRU,Python 3.2+ 内置)

from functools import lru_cache

@lru_cache(maxsize=128)
def expensive_fib(n):
    if n < 2: return n
    return expensive_fib(n-1) + expensive_fib(n-2)

7. 进阶:同时兼容带参和不带参的装饰器

为了让装饰器既可以 @deco 用,也可以 @deco(arg) 用,需要做一个“智能适配”,不过日常开发中分开写更清晰,不建议过度设计。


8. Python 3 特有增强

  • nonlocal:让嵌套函数修改外层变量成为可能(装饰器计数器必备)。
  • functools.wraps:修复元数据的标准方案(Python 3.2+ 完善)。
  • 异步装饰器:若装饰 async defwrapper 也必须定义为 async def,并用 await func() 调用。
def async_timer(func):
    @functools.wraps(func)
    async def wrapper(*args, **kwargs):
        start = time.perf_counter()
        result = await func(*args, **kwargs)
        print(f"耗时 {time.perf_counter() - start}")
        return result
    return wrapper

总结一句话

写装饰器 = 写“返回函数的函数”;用装饰器 = 用 @ 语法糖增强函数。
记住 @functools.wraps 是保命符,理解“闭包”是核心,掌握“带参装饰器”是分水岭。