1. 为什么我花了整整三周才真正“看懂”装饰器——一个老手的笨办法刚入行那会儿我带过几个实习生。每次讲到装饰器他们眼睛里那种混合着困惑、敬畏和一丝“这玩意儿怕不是在考我智商”的表情我都特别熟悉——因为三年前我自己就是那样坐在工位上盯着符号发呆手边是半杯凉透的咖啡屏幕里是functools.wraps的文档心里反复默念“它到底在哪儿偷偷改了我的函数”装饰器不是魔法但它确实像魔术师的手法你只看到结果变了却抓不住那个关键的“一抖手腕”的瞬间。Python 官方文档里那句“装饰器是一种语法糖用于修改或增强函数行为”听起来无比正确也无比空洞。就像告诉你“自行车靠两个轮子保持平衡”可当你第一次跨上去风从耳边呼啸而过身体本能地左右摇晃时你才真正开始理解“平衡”这个词的全部重量。我今天要写的不是一篇教科书式的定义罗列而是一份我亲手拆解、反复试错、最终把装饰器从“黑箱”变成“透明玻璃盒”的完整操作日志。它会从最原始的痛点出发——比如你写了一个get_user_profile()函数老板突然说“所有接口都要加耗时日志明天上线。” 你第一反应是不是想把start time.time()和end time.time()像补丁一样一块一块贴到每个函数开头结尾这种重复劳动就是装饰器诞生的唯一且全部理由。关键词Towards AI - Medium在这里不是指某个平台而是代表一种典型的、面向实践者的技术传播风格不堆砌术语用真实场景倒推原理把抽象概念钉死在具体代码的十字路口上。这篇文章适合三类人一是被符号卡住超过两天的初学者二是能写但总担心“用得不对”的中级开发者三是想给团队新人讲清楚装饰器本质的 Tech Lead。它不会教你如何用lru_cache做性能优化也不会深入__call__方法的元编程细节它只做一件事让你合上电脑后能对着白板用一支笔把装饰器的执行流画出来并且能解释清楚每一行代码在哪个时刻、以什么身份、对哪个对象做了什么操作。如果你现在正为一个TypeError: NoneType object is not callable报错抓耳挠腮别急我们马上就要走到那个坑边了。2. 装饰器的本质一场关于“函数身份”的精密手术2.1 从“重复计时”这个具体问题切入让我们回到那个最朴素的场景你需要测量两个函数的执行时间。原文中给出了add_directly和use_expression但为了更贴近真实开发我把它换成一个更常见的例子——一个模拟数据库查询的函数import time def fetch_user_data(user_id): 模拟从数据库获取用户数据耗时约0.1秒 time.sleep(0.1) # 模拟I/O延迟 return {id: user_id, name: 张三, email: zhangsanexample.com} def fetch_order_history(user_id): 模拟获取订单历史耗时约0.15秒 time.sleep(0.15) return [{order_id: ORD-001, amount: 99.99}]现在老板要求“所有数据接口必须记录耗时误差小于1毫秒。” 你第一个念头肯定是复制粘贴def fetch_user_data_with_timer(user_id): start time.time() result fetch_user_data(user_id) end time.time() print(f[fetch_user_data] 执行耗时: {end - start:.4f}秒) return result def fetch_order_history_with_timer(user_id): start time.time() result fetch_order_history(user_id) end time.time() print(f[fetch_order_history] 执行耗时: {end - start:.4f}秒) return result这看起来没问题但问题立刻浮现如果项目有50个接口你就要写50份几乎一模一样的计时逻辑如果老板明天说“改成记录到日志文件”你得改50个地方如果后天说“只对生产环境开启”你得在50个函数里加if DEBUG:判断。这就是DRYDont Repeat Yourself原则被活活扼杀的现场。2.2 第一次抽象把“计时”抽成一个独立函数聪明的程序员会立刻想到“把计时逻辑单独拎出来” 于是有了time_it函数def time_it(func, *args, **kwargs): 一个通用的计时函数 start time.time() result func(*args, **kwargs) end time.time() print(f[{func.__name__}] 执行耗时: {end - start:.4f}秒) return result # 使用方式 user_data time_it(fetch_user_data, 123) order_history time_it(fetch_order_history, 123)这已经比复制粘贴强多了但问题没根除。你看调用方式time_it(fetch_user_data, 123)它完全破坏了原有的调用习惯。原来你是fetch_user_data(123)现在变成了一个“函数参数”的二元组。更重要的是如果你的代码里已经有100处fetch_user_data(123)的调用你得把它们全部替换成time_it(fetch_user_data, 123)。这等于把“重复劳动”从“写代码”转移到了“改调用”本质上还是在重复。2.3 关键转折让“计时函数”返回一个“新函数”真正的突破点在于意识到我们不需要一个“执行计时”的函数我们需要一个“生成计时版函数”的工厂。这就是装饰器的核心思想——它不是一个动作而是一个“制造动作的模具”。让我们手动实现这个“模具”def make_timed_function(func): 这是一个‘函数工厂’它接收一个函数返回一个‘计时版’的新函数 def timed_wrapper(*args, **kwargs): start time.time() result func(*args, **kwargs) end time.time() print(f[{func.__name__}] 执行耗时: {end - start:.4f}秒) return result return timed_wrapper # 现在我们用这个工厂来‘改造’原函数 fetch_user_data_timed make_timed_function(fetch_user_data) fetch_order_history_timed make_timed_function(fetch_order_history) # 调用方式恢复了 user_data fetch_user_data_timed(123) # 输出: [fetch_user_data] 执行耗时: 0.1002秒 order_history fetch_order_history_timed(123) # 输出: [fetch_order_history] 执行耗时: 0.1503秒提示注意make_timed_function(fetch_user_data)的返回值是什么它返回的不是fetch_user_data的执行结果而是一个全新的、名叫timed_wrapper的函数对象。你可以把它想象成一个“套着计时外壳的快递盒”盒子本身不是货物但它能确保货物原函数在被取出执行时自动附带一张计时单。这个模式就是装饰器的“无糖版”原型。它揭示了装饰器最底层的真相装饰器就是一个接受函数作为输入并返回另一个函数作为输出的高阶函数。符号只是 Python 给这个模式提供的语法糖目的是让代码看起来更干净而不是改变其内在逻辑。2.4 语法糖降临符号的真正含义现在我们把上面的手动工厂调用换成 Python 的标准写法def make_timed_function(func): def timed_wrapper(*args, **kwargs): start time.time() result func(*args, **kwargs) end time.time() print(f[{func.__name__}] 执行耗时: {end - start:.4f}秒) return result return timed_wrapper # 使用 语法糖 make_timed_function def fetch_user_data(user_id): time.sleep(0.1) return {id: user_id, name: 张三, email: zhangsanexample.com} make_timed_function def fetch_order_history(user_id): time.sleep(0.15) return [{order_id: ORD-001, amount: 99.99}]这段代码和下面这段是完全等价的def fetch_user_data(user_id): time.sleep(0.1) return {id: user_id, name: 张三, email: zhangsanexample.com} def fetch_order_history(user_id): time.sleep(0.15) return [{order_id: ORD-001, amount: 99.99}] # 手动应用装饰器等价于 make_timed_function fetch_user_data make_timed_function(fetch_user_data) fetch_order_history make_timed_function(fetch_order_history)注意make_timed_function这行代码是在fetch_user_data函数被定义完成后立即执行的。它不是在你调用fetch_user_data(123)的时候才运行而是在模块加载、函数定义完毕的那一刻就完成了“替换”。所以当你后续调用fetch_user_data(123)时你实际上调用的是timed_wrapper而timed_wrapper内部再调用原始的fetch_user_data。这就是为什么装饰器能“无感”地增强函数——因为它发生在函数被使用之前是一次性的、静态的“身份覆盖”。理解了这一点你就不会再问“为什么写在函数上面效果却在下面体现”这种问题了。它不是“影响”而是“替换”。3. 核心细节解析从wrapper到functools.wraps的必经之路3.1wrapper函数的三个核心要素一个合格的wrapper函数必须同时满足三个条件缺一不可。我见过太多人只实现了第一个结果在调试时栽了大跟头。第一必须能接收任意参数*args, **kwargs这是wrapper的“万能插头”。原函数可能是def f(a, b)也可能是def f(*args, **kwargs)甚至是def f(a1, bNone, **options)。wrapper必须能兼容所有情况否则就会报TypeError: wrapper() takes 0 positional arguments but 1 was given。# ❌ 错误示范硬编码参数 def bad_wrapper(a, b): # 只能接两个参数 start time.time() result func(a, b) # 如果原函数是 f(x, y, z)这里就崩了 end time.time() return result # ✅ 正确示范万能插头 def good_wrapper(*args, **kwargs): # 接收所有可能的参数组合 start time.time() result func(*args, **kwargs) # 再原封不动地传给原函数 end time.time() return result第二必须能返回原函数的返回值这是wrapper的“透明管道”。很多新手写完计时装饰器发现函数执行后返回None百思不得其解。原因就是忘了return result这一行。def broken_wrapper(*args, **kwargs): start time.time() result func(*args, **kwargs) end time.time() print(f耗时: {end-start}) # ❌ 忘了 return result # 函数默认返回 None # 调用后user_data 将是 None而不是期望的字典 user_data fetch_user_data(123) # user_data None第三必须保留原函数的“身份信息”metadata这是最容易被忽视却最致命的一点。Python 函数自带一些“身份证信息”比如__name__函数名、__doc__文档字符串、__module__所属模块。这些信息在调试、IDE 自动补全、单元测试、API 文档生成时至关重要。def my_decorator(func): def wrapper(*args, **kwargs): print(Before) result func(*args, **kwargs) print(After) return result return wrapper my_decorator def greet(name): 向某人打招呼 return fHello, {name}! print(greet.__name__) # 输出: wrapper (不是 greet) print(greet.__doc__) # 输出: None (文档字符串丢失了)提示这个现象叫“装饰器污染”。greet这个名字现在指向的是wrapper函数而不是原来的greet。所以greet.__name__是wrappergreet.__doc__是wrapper的文档如果有的话而不是greet的向某人打招呼。这会导致你的 IDE 在你输入greet(时无法显示正确的参数提示你的单元测试框架在报告失败时会显示wrapper而不是greet让你一头雾水。3.2functools.wraps修复身份污染的终极方案functools.wraps就是为了解决上述第三点而生的。它不是一个装饰器而是一个“装饰器的装饰器”——一个用来修饰其他装饰器内部wrapper函数的工具。它的原理非常简单粗暴把原函数的所有重要属性一股脑地复制到wrapper函数身上。这包括__name__,__doc__,__module__,__annotations__,__dict__等。from functools import wraps def my_decorator(func): wraps(func) # 关键把 func 的所有属性复制给下面的 wrapper def wrapper(*args, **kwargs): print(Before) result func(*args, **kwargs) print(After) return result return wrapper my_decorator def greet(name): 向某人打招呼 return fHello, {name}! print(greet.__name__) # 输出: greet (✅ 修复了) print(greet.__doc__) # 输出: 向某人打招呼 (✅ 修复了)wraps(func)这行代码等价于在wrapper函数定义后手动执行wrapper.__name__ func.__name__ wrapper.__doc__ func.__doc__ wrapper.__module__ func.__module__ # ... 其他属性但wraps做得更全面、更安全。它是 Python 标准库经过千锤百炼的解决方案没有理由不用。实操心得我给自己定了一条铁律——只要我的装饰器里有wrapper函数wraps(func)就是wrapper上面的第一行代码雷打不动。这已经成为肌肉记忆就像写if __name__ __main__:一样自然。省掉这一行后面花十倍时间调试的代价远超敲这几个字符的时间。3.3 装饰器的执行顺序当多个叠在一起时现实项目中一个函数往往需要多个装饰器。比如一个 Web API 接口可能需要login_required权限、rate_limit限流、time_it计时三个装饰器。login_required rate_limit time_it def api_get_user(request): return {status: success}那么这三个装饰器的执行顺序是怎样的答案是从下往上层层包裹。这是理解复杂装饰器链的关键。我们可以把它想象成一个洋葱最内层api_get_user函数本身。第二层time_it包裹它生成time_it(api_get_user)即一个计时版函数。第三层rate_limit包裹上一步的结果生成rate_limit(time_it(api_get_user))即一个“先计时、再限流”的函数。最外层login_required包裹最外层生成login_required(rate_limit(time_it(api_get_user)))即一个“先计时、再限流、最后鉴权”的函数。所以当你调用api_get_user(request)时实际执行流是login_required→rate_limit→time_it→api_get_user→time_it→rate_limit→login_required注意每个装饰器的wrapper函数都只负责自己那一层的逻辑。time_it的wrapper只管计时它不关心也不应该关心rate_limit是否放行了请求。这种职责分离正是装饰器强大和灵活的根源。4. 实操过程与核心环节实现从计时器到认证器的完整演进4.1 构建一个生产级的计时装饰器基于前面的原理我们来构建一个真正能在生产环境中使用的time_it。它不仅要计时还要支持配置、日志分级、以及异常处理。import time import logging from functools import wraps # 配置一个专用的日志器 logger logging.getLogger(decorator.timer) logger.setLevel(logging.INFO) if not logger.handlers: handler logging.StreamHandler() formatter logging.Formatter(%(asctime)s - %(name)s - %(levelname)s - %(message)s) handler.setFormatter(formatter) logger.addHandler(handler) def time_it(levellogging.INFO, threshold_ms100.0, log_to_fileFalse): 一个可配置的计时装饰器 Args: level (int): 日志级别默认 INFO threshold_ms (float): 耗时阈值毫秒超过此值才记录 WARNING log_to_file (bool): 是否将日志写入文件此处仅为示意实际需配置 FileHandler def decorator(func): wraps(func) def wrapper(*args, **kwargs): start time.perf_counter() # 使用 perf_counter()精度更高 try: result func(*args, **kwargs) end time.perf_counter() elapsed_ms (end - start) * 1000 # 根据耗时决定日志级别 if elapsed_ms threshold_ms: logger.log(level, f[{func.__name__}] 耗时过长: {elapsed_ms:.2f}ms (阈值: {threshold_ms}ms)) else: logger.debug(f[{func.__name__}] 执行完成: {elapsed_ms:.2f}ms) return result except Exception as e: end time.perf_counter() elapsed_ms (end - start) * 1000 logger.error(f[{func.__name__}] 执行失败耗时 {elapsed_ms:.2f}ms: {e}) raise # 重新抛出异常不吞掉 return wrapper return decorator # 使用示例 time_it(threshold_ms50.0) def slow_database_query(user_id): time.sleep(0.08) # 模拟80ms的慢查询 return {user_id: user_id} time_it(levellogging.DEBUG) def fast_calculation(x, y): return x y这个版本的time_it展示了装饰器的高级用法带参数的装饰器。它实际上是“装饰器工厂的工厂”。time_it(...)返回一个decorator函数decorator函数再接收func并返回wrapper。这种三层嵌套结构是 Python 装饰器最强大的地方也是初学者最容易绕晕的地方。4.2 构建一个简易的登录认证装饰器计时器是“读”操作现在我们来做一个“写”操作的典型权限控制。假设我们有一个简单的用户系统需要确保只有管理员才能调用某些函数。from functools import wraps # 模拟一个简单的用户上下文 class User: def __init__(self, username, is_adminFalse): self.username username self.is_admin is_admin # 全局模拟当前登录用户实际项目中会从 request 或 context 中获取 current_user User(guest, is_adminFalse) def login_required(redirect_url/login): 登录认证装饰器 Args: redirect_url (str): 未登录时重定向的URL def decorator(func): wraps(func) def wrapper(*args, **kwargs): # 检查当前用户是否已登录这里简化为检查用户名 if not current_user.username or current_user.username guest: print(f访问被拒绝: 用户未登录将重定向至 {redirect_url}) # 在Web框架中这里会返回一个HTTP重定向响应 return {error: Unauthorized, redirect: redirect_url} # 检查用户是否有权限这里简化为检查 is_admin if not current_user.is_admin: print(f访问被拒绝: 用户 {current_user.username} 权限不足) return {error: Forbidden, user: current_user.username} # 权限通过执行原函数 print(f用户 {current_user.username} 已通过权限验证正在执行 {func.__name__}) return func(*args, **kwargs) return wrapper return decorator # 使用示例 login_required(redirect_url/admin/login) def delete_user(user_id): 只有管理员才能删除用户 return f用户 {user_id} 已被删除 # 测试 current_user User(admin, is_adminTrue) print(delete_user(123)) # 输出: 用户 admin 已通过权限验证... / 用户 123 已被删除 current_user User(normal_user, is_adminFalse) print(delete_user(123)) # 输出: 访问被拒绝: 用户 normal_user 权限不足这个login_required装饰器完美体现了装饰器的“横切关注点”Cross-Cutting Concern特性。权限检查的逻辑与delete_user的业务逻辑删除用户完全解耦。你可以把它加到任何需要权限的函数上而无需修改函数内部的任何一行代码。4.3 构建一个缓存装饰器理解*args, **kwargs的威力缓存是装饰器的另一个经典应用场景。它的核心挑战在于如何用函数的参数生成一个唯一的、可哈希的缓存键这直接考验你对*args, **kwargs的理解深度。from functools import wraps import hashlib import pickle def cache(maxsize128): 一个基于内存的简易缓存装饰器 Args: maxsize (int): 缓存最大条目数0 表示无限制 def decorator(func): # 使用字典作为缓存存储 cache_dict {} cache_order [] # 用于实现 LRU最近最少使用淘汰策略 wraps(func) def wrapper(*args, **kwargs): # 1. 生成缓存键将 args 和 kwargs 序列化为 bytes再取 hash # 这是关键必须保证相同的输入总是生成相同的 key key_data (args, tuple(sorted(kwargs.items()))) key_bytes pickle.dumps(key_data) key hashlib.md5(key_bytes).hexdigest() # 2. 检查缓存 if key in cache_dict: print(f[Cache HIT] {func.__name__} with key {key[:8]}...) # 更新访问顺序LRU cache_order.remove(key) cache_order.append(key) return cache_dict[key] # 3. 缓存未命中执行原函数 print(f[Cache MISS] {func.__name__} with key {key[:8]}...) result func(*args, **kwargs) # 4. 存入缓存 cache_dict[key] result cache_order.append(key) # 5. 如果超出大小淘汰最久未使用的 if maxsize 0 and len(cache_order) maxsize: oldest_key cache_order.pop(0) del cache_dict[oldest_key] return result # 为 wrapper 添加一个清除缓存的方法方便测试和管理 wrapper.clear_cache lambda: cache_dict.clear() return wrapper return decorator # 使用示例 cache(maxsize10) def fibonacci(n): 计算斐波那契数列第n项递归版很慢 if n 2: return n return fibonacci(n-1) fibonacci(n-2) # 第一次调用会计算并缓存 print(fibonacci(35)) # 耗时较长 # 第二次调用直接从缓存读取瞬间返回 print(fibonacci(35)) # 耗时极短这个cache装饰器展示了*args, **kwargs的终极用途它让你可以编写一个完全不知道被装饰函数长什么样的通用工具。fibonacci函数的签名是def fibonacci(n)而wrapper却能用(args, tuple(sorted(kwargs.items())))这种通用方式为它生成一个完美的缓存键。这就是抽象的力量。5. 常见问题与排查技巧实录那些让我熬夜到凌晨三点的坑5.1 常见问题速查表问题现象可能原因排查与解决方法TypeError: NoneType object is not callable装饰器函数本身没有return一个函数即decorator没有return wrapper检查你的装饰器定义确保最外层函数decorator有return wrapper语句。这是最高频的错误。NameError: name wrapper is not definedwraps(func)写在了def wrapper(...)之前或者wrapper函数定义有语法错误wraps(func)必须紧贴在def wrapper(...)的上方。确保wrapper函数定义完整、无缩进错误。装饰器生效了但函数的__name__和__doc__依然是wrapper忘记了wraps(func)或者wraps写错了位置在wrapper函数定义前添加wraps(func)。这是强制性步骤没有例外。多个装饰器叠加时行为不符合预期如计时器没生效装饰器的执行顺序理解错误或者某个装饰器内部没有return result画一个洋葱图从下往上确认包裹顺序。在每个wrapper函数里确保最后一行是return result。带参数的装饰器如time_it(threshold100)报错TypeError: decorator() takes 1 positional argument but 2 were given装饰器工厂的嵌套层级错误decorator函数没有正确接收func确保结构是def outer_decorator(...):→def decorator(func):→def wrapper(...):→return wrapper→return decorator。5.2 我踩过的最深的三个坑坑一*args, **kwargs的“假万能”陷阱我以为*args, **kwargs能接住一切直到我遇到了一个带*参数的函数def problematic_func(a, *b, c): return a sum(b) c # 这个函数的签名是a, 任意数量的位置参数 b, 以及一个必须是关键字参数的 c # 当你用 my_decorator 装饰它时wrapper(*args, **kwargs) 会尝试把所有东西都塞进去 # 但 problematic_func 对参数位置有严格要求这会导致 TypeError解决之道在生产级装饰器中不要盲目信任*args, **kwargs。对于需要极致兼容性的场景可以使用inspect.signature来动态分析原函数的签名并进行精确匹配。但这属于进阶内容对于 95% 的场景*args, **kwargs已经足够。坑二装饰器里的状态变量state variable共享我曾经写过一个计数装饰器想统计某个函数被调用了多少次def count_calls(): count 0 # ❌ 错误这个变量在 decorator 函数里会被所有 wrapper 共享 def decorator(func): wraps(func) def wrapper(*args, **kwargs): nonlocal count # 这会让所有被装饰的函数共享同一个 count count 1 print(f{func.__name__} 已被调用 {count} 次) return func(*args, **kwargs) return wrapper return decorator结果发现count_calls() def f(): ...和count_calls() def g(): ...的调用次数是混在一起的解决之道把状态变量放在wrapper函数内部或者更推荐的做法是利用 Python 的闭包特性将状态绑定在wrapper函数对象上def count_calls(): def decorator(func): wraps(func) def wrapper(*args, **kwargs): # 每个 wrapper 都有自己的 count 属性 if not hasattr(wrapper, count): wrapper.count 0 wrapper.count 1 print(f{func.__name__} 已被调用 {wrapper.count} 次) return func(*args, **kwargs) return wrapper return decorator坑三异步函数async def不能被同步装饰器装饰这是 Python 3.7 的一个常见误区。如果你有一个async def fetch_data()然后你试图用上面写的time_it去装饰它程序会启动但fetch_data()会变成一个coroutine对象而不是立即执行。time_it async def fetch_data(): await asyncio.sleep(1) return data # 错误调用方式会返回 coroutine 对象不会执行 result fetch_data() # result 是 coroutine object fetch_data at 0x... # 正确调用方式需要 await result await fetch_data()解决之道为异步函数编写专门的异步装饰器使用async def wrapper和await func(...)。这超出了本文范围但记住一点同步装饰器和异步装饰器是两套平行世界不能混用。如果你的项目大量使用async/await请务必为它们单独设计装饰器。5.3 一个终极调试技巧打印装饰器的执行流当你被复杂的多层装饰器搞晕时最有效的方法不是猜而是“看”。在每个wrapper函数的开头和结尾加上清晰的日志def debug_decorator(name): def decorator(func): wraps(func) def wrapper(*args, **kwargs): print(f[DEBUG] ENTER {name} - {func.__name__}) result func(*args, **kwargs) print(f[DEBUG] EXIT {name} - {func.__name__}) return result return wrapper return decorator # 然后这样用 debug_decorator(AUTH) debug_decorator(RATE_LIMIT) debug_decorator(TIMER) def my_api(): return OK运行它你会在控制台看到一条清晰的、从外到内、再从内到外的执行路径就像 X 光片一样让你一眼看清整个调用栈。这是我解决所有装饰器疑难杂症的“核武器”。6. 从“学会”到“精通”装饰器思维的迁移与扩展6.1 装饰器不是终点而是一种思维方式写完这篇文章我回看自己过去三年的代码库发现一个惊人的事实我写的最多、复用率最高、维护成本最低的代码几乎都以装饰器的形式存在。它们不再是time_it或login_required这样教科书式的例子而是深深嵌入业务逻辑的“隐形骨架”。比如在一个金融风控系统中我有一个validate_transaction装饰器它会在每一笔交易提交前自动校验账户余额、交易限额、黑名单状态、反洗钱规则。这些校验逻辑分散在几十个不同的交易类型函数中但它们的入口都被这个装饰器统一接管。当风控规则更新时我只需要改一处装饰器而不是去翻遍所有交易函数。再比如在一个物联网平台中我有一个retry_on_failure(max_retries3, backoff1.5)装饰器它会自动为所有与硬件设备通信的函数添加重试逻辑。网络抖动、设备休眠、信号干扰……这些物理世界的不确定性被这个小小的符号优雅地封装了起来。这就是装饰器思维的精髓识别出那些“横跨”多个业务函数的、非核心的、但又必不可少的逻辑并将它们提取、封装、标准化最后以一种声明式的方式xxx应用到需要的地方。它让你的业务代码变得无比干净所有的“噪音”都被推到了边缘。6.2 如何设计你自己的第一个业务装饰器不要一上来就想造火箭。从一个最小、最痛的点开始。问问自己我最近一周有没有哪段代码被我复制粘贴了三次以上我有没有哪段逻辑每次写新函数时都得“条件反射”地加上我有没有哪类错误