Python的装饰器把我绕晕了,原来带参和不带参区别这么大

简介: 本文深入剖析Python装饰器加括号与不加括号的本质区别:`@decorator`是直接用函数本身作装饰器;`@decorator()`则是先调用函数,用其返回值作装饰器。通过真实踩坑案例,厘清无参/带参装饰器的执行逻辑、类实现方式及使用规范,助你彻底掌握装饰器核心原理。(239字)

讲个真事儿。

上个月我写一个内部工具,要给十几个函数统一加日志。心想,用装饰器呗,优雅又省事。于是写了个带日志级别的装饰器,让每个函数自己指定日志等级:

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 get_user():
   return {"name": "张三"}

@log("DEBUG")
def calculate():
   return 42

运行没问题,完美。我心里还美滋滋的。

后来有个函数不需要指定级别,我就想:那简单,把括号去掉用默认值不就完了?于是改成了这样:

@log
def test_func():
   return "hello"

结果,程序直接报错,说log函数缺少参数。我盯着屏幕看了十分钟,就多了对括号的事,怎么差别这么大?

那一刻我才真正搞明白,装饰器加不加括号,背后是两种完全不同的逻辑。

代理 IP 使用小技巧 让你的数据抓取效率翻倍 (77).png

先搞明白装饰器本质是什么

在聊括号之前,咱们先把装饰器的本质搞清楚。

一句话总结:装饰器就是一个函数,它接收一个函数作为参数,返回一个新的函数

语法糖@decorator等价于:

@decorator
def func():
   pass

# 等价于
def func():
   pass
func = decorator(func)

看到了吗?@decorator就是**把func传进decorator()这个函数里,然后把返回值重新赋值给func**。

关键点:装饰器在定义函数的时候就已经执行了,不是调用的时候才执行

搞明白这个,咱们再来看加括号和不加括号到底差在哪。

加括号 vs 不加括号,到底差在哪儿

用一句话说清楚:

  • @log —— 把log本身当作装饰器,直接传入被装饰的函数
  • @log() —— 先调用log(),把它的返回值当作装饰器

你看,加括号是先执行函数调用,把结果当作装饰器。不加括号是直接把函数本身当作装饰器。

这两个返回的东西完全不一样:

  • log是一个函数,它接收一个函数作为参数
  • log()是调用了log,它返回的是另一个函数

形状一样,但一个是原函数,一个是返回值。

场景一:无参装饰器——千万别加括号

最常见的装饰器写法:

def timer(func):
   def wrapper(*args, **kwargs):
       start = time.time()
       result = func(*args, **kwargs)
       print(f"耗时: {time.time() - start}")
       return result
   return wrapper

@timer  # ✅ 正确,不加括号
def slow_function():
   time.sleep(1)

如果你手贱写成@timer()

@timer()  # ❌ 报错
def slow_function():
   time.sleep(1)

为什么报错?因为timer()先被执行,返回的是wrapper这个函数。然后Python把slow_function传给了wrapper。但wrapper接收的参数格式是(*args, **kwargs),它不接收一个函数作为参数,所以报错。

这就好比你本来应该把门牌号告诉快递员,结果你把门牌号告诉了物业,物业又说“我不收快递”,快递就送不到了。

无参装饰器的正确写法(用类实现)

除了用函数,还可以用类来实现无参装饰器:

class my_decorate(object):
   def __init__(self, f):
       """装饰器不带参数时,函数作为参数传给构造器"""
       print("进入到 __init__")
       self.f = f
   
   def __call__(self, *args):
       print("进入 __call__")
       self.f(*args)
       print("结束 __call__")

@my_decorate
def myFunction(arg1, arg2):
   print('myFunction arguments:', arg1, arg2)

myFunction("say", "hello")

输出:

进入到 __init__
进入 __call__
myFunction arguments: say hello
结束 __call__

注意,__init__只被调用了一次。因为用类作为装饰器,会先创建类实例,然后再去执行这个实例。执行过程等价于:

myDecorate = my_decorate(myFunction)
myDecorate('say', 'hello')

被装饰器修饰的函数,最终类型实际上是装饰器本身。

场景二:带参装饰器——必须加括号

这就是我踩坑的场景。当装饰器需要参数时,你必须多包一层:

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 get_user():
   return {"name": "张三"}

执行顺序是这样的:

  1. 先执行log("INFO"),返回decorator函数
  2. Python把get_user传进decorator,返回wrapper
  3. 最终get_user被替换成wrapper

整个过程等价于:

get_user = log("INFO")(get_user)

先执行log("INFO"),返回的是decorator函数,再调用返回的函数,参数是get_user函数,返回值最终是wrapper函数。

带参装饰器的正确写法(用类实现)

用类实现带参装饰器,结构会发生较大变化:

class my_decorate(object):
   def __init__(self, arg1, arg2, arg3):
       """带参数时,__init__接收的是装饰器的参数,不是函数"""
       print("进入 __init__()")
       self.arg1 = arg1
       self.arg2 = arg2
       self.arg3 = arg3
   
   def __call__(self, f):
       """函数作为参数传给__call__方法"""
       print("进入 __call__()")
       def wrapped_f(*args):
           print("进入 wrapped_f()")
           print("装饰器参数:", self.arg1, self.arg2, self.arg3)
           f(*args)
           print('执行完函数myfunction')
       return wrapped_f

@my_decorate("hello", "world", 1)
def myFunction(*args):
   if len(args) > 0:
       print('this is myFunction args: %s' % str(args))
   else:
       print('this is myFunctions')

myFunction('hello', 'myfunction')

输出:

进入 __init__()
进入 __call__()
进入 wrapped_f()
装饰器参数: hello world 1
this is myFunction args: ('hello', 'myfunction')
执行完函数myfunction

关键区别:装饰器带参数时,不能在__init__中接收函数作为参数,因为__init__的参数是装饰器自己的参数。函数是在__call__方法中接收的。

场景三:带参装饰器,解决实际问题

回到开头那个场景。不带参数的装饰器只能打印固定的字符串:

def decorator(fun):
   def wrapper(*args, **kwargs):
       print("Method is running")
       return fun()
   return wrapper

但如果我们想打印不同的信息,就需要带参数的装饰器:

def decorator_par(name):
   def decorator(fun):
       def wrapper(*args, **kwargs):
           print("Method {} is running".format(name))
           return fun()
       return wrapper
   return decorator

@decorator_par("A")
def A():
   print("I am A")

@decorator_par("B")
def B():
   print("I am B")

A()  # 输出: Method A is running \n I am A
B()  # 输出: Method B is running \n I am B

可以看到,在不改变方法调用代码的情况下,实现了在不同方法之前打印特定的信息。

带参数的装饰器,本质上就是在不带参数的装饰器外面再封装一层。这多出来的一层,就是为了接收装饰器自己的参数。

场景四:装饰器既要带参又要无参——能兼容吗?

有些场景下,你可能希望同一个装饰器既能不带参数使用,也能带参数使用。比如:

@logged
def f1():
   pass

@logged(level=logging.CRITICAL)
def f2():
   pass

这时候就需要在装饰器内部做判断。但说实话,这种写法比较复杂,而且容易引入歧义。一般情况下,建议明确区分:需要参数就用带参版本,不需要就用无参版本,不要试图让一个装饰器同时兼容两种用法。

总结

对比项 无参装饰器 @log 带参装饰器 @log("INFO")
嵌套层数 2层(decorator + wrapper) 3层(outer + decorator + wrapper)
执行时机 定义时直接装饰 先执行外层函数获取装饰器,再装饰
适用场景 功能固定,不需要额外配置 需要根据不同情况调整行为

记住最核心的一句话:

  • @log —— 把log本身当作装饰器
  • @log() —— 先调用log(),把返回值当作装饰器

加不加括号,决定了Python把当作装饰器。这个区别,搞明白了就不会再踩坑了。

目录
相关文章
|
20天前
|
人工智能 缓存 前端开发
DeepSeek Harness 首发实测 + 入门教程,夯爆了!梁神我错了
DeepSeek Harness + DeepSeek V4 Pro 项目实战保姆级教程!手把手带你从零安装开源 AI 编程工具,开发架构图、知识讲解网站、3D 网页游戏、全栈 AI 应用 4 个项目,覆盖运行模式选择、插件安装与开发,看看能不能对标 Claude。
13231 90
DeepSeek Harness 首发实测 + 入门教程,夯爆了!梁神我错了
|
8天前
|
人工智能 自然语言处理 安全
阿里云千问办公、Qoder Teams、Qoder CN区别与选择指南:模型能力、适用场景与最新活动参考
本文聚焦阿里云2026年推出的三款自研AI办公产品,清晰拆解千问办公、Qoder Teams、Qoder CN的差异化定位与能力边界:千问办公主打职场全场景提效,支持自然语言指令一键完成PPT生成、数据分析等高频办公任务;Qoder Teams面向程序员团队,深度整合AI代码生成、团队协同与企业知识库能力;Qoder CN则专为金融、政务等强合规场景打造,实现数据不出境与VPC私有化部署。文章同步给出分场景选型指南与最新活动定价,帮助不同类型的企业按需组合产品,实现业务岗、研发岗与强合规场景的AI能力全覆盖。
阿里云千问办公、Qoder Teams、Qoder CN区别与选择指南:模型能力、适用场景与最新活动参考
|
3天前
|
缓存 人工智能 API
阿里云Qwen3.8‑Flash完整能力解析:模型特性、API调用实操与计费规则深度拆解
在AI应用快速落地的当下,开发者与企业选型大模型API,不再只单纯关注评测榜单分数,推理速度、上下文长度、多模态能力、工具调用稳定性以及实际调用成本,共同决定项目能否平稳上线。Qwen3.8‑Flash作为新一代多模态混合专家模型,主打高性能推理与低成本开销,面向编程开发、智能Agent工作流、超长文档解析、图文混合理解等高频场景,提供托管API服务,权重同时开放可供本地部署,兼容主流接口协议,能够无缝接入各类开发工具链。很多开发者在接入过程中,容易混淆普通按量Token计费、缓存计费、各类订阅计划之间的差异,造成实际账单超出预估。本文从模型底层架构、核心功能能力、适用场景、API调用实操、完
801 0
|
13天前
|
Web App开发 人工智能 API
16 个超火的 DeepSeek Harness 插件,大肥鱼已经落后 N 个版本了。。。
DeepSeek Harness 精选插件推荐合集,从图片识别、浏览器操控、多 Agent 协作到手机远程控制,一口气带你看完 DSH 社区热门的十几个插件,覆盖技能扩展、UI 界面增强、整活玩法三大类,让你的鲸鱼变得更强。
1792 4
|
14天前
|
人工智能 Java BI
【AI】DeepSeek Harness 安装、运行、管理插件
本文介绍了如何运行DeepSeek开源的Agent框架DeepSeek Harness(dsh)。主要内容包括:使用nvm安装适配的Node版本;通过代理加速克隆GitHub源码;使用pnpm安装依赖并启动项目;配置DeepSeek API Token;安装扩展功能的插件。该框架自带Web界面,支持模型适配、文件编辑等插件化功能
1969 1
|
人工智能 JavaScript 开发工具
DeepSeek Harness 本地安装与使用指南
DeepSeek Harness(DSH)是DeepSeek AI开源的Agent运行框架,支持本地文件操作、命令执行与工具调用。基于Cordis插件架构,具备高扩展性与强可控性,适合开发者搭建可控Agent环境或开展模型基准测试。当前为开发者预览版,需Node.js环境,推荐先用`npx @deepseek-ai/dsh web`快速体验。
5230 0
|
9天前
|
人工智能 Linux iOS开发
Ollama使用教程:Ollama官网下载、Ollama本地部署大模型(2026最新)
Ollama 是一款免费开源的本地大模型运行工具,支持在 Windows/macOS/Linux 上离线运行 Qwen、DeepSeek、Llama 等主流开源模型,数据不出本机、隐私安全。提供 OpenAI 兼容 API,命令行一键拉取/运行/管理模型,无需联网,无调用限制,是开发者与 AI 爱好者部署本地 AI 助手的理想选择。(239 字)
|
16天前
|
人工智能 JavaScript 测试技术
保姆级教程:DeepSeek Harness从安装到跑通测试,30分钟上手
DeepSeek Harness是DeepSeek开源的AI Agent运行时,主打“一行命令安装、5分钟跑通”。它让模型真正动手干活——读代码、跑测试、分析失败、生成修复方案。本文手把手教你30分钟从零上手,覆盖安装、配置、实测及避坑指南,助你快速掌握下一代AI编程范式。
|
6天前
|
人工智能 监控 测试技术
Qwen3.8-Flash 来了,100万上下文、Agent、Coding 都加强了
8月26日,通义千问发布Qwen3.8-Flash-Next:125B参数、每Token仅激活6B,原生支持26万Token、可扩展至100万上下文;Coding、Agent与工具调用能力显著增强,面向真实软件工程任务,推动大模型从“回答问题”迈向“完成工作”。