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

推荐订阅源

C
CXSECURITY Database RSS Feed - CXSecurity.com
Help Net Security
Help Net Security
P
Privacy International News Feed
S
Securelist
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Tor Project blog
AWS News Blog
AWS News Blog
K
Kaspersky official blog
A
Arctic Wolf
Latest news
Latest news
T
Threat Research - Cisco Blogs
L
LINUX DO - 最新话题
P
Privacy & Cybersecurity Law Blog
Security Archives - TechRepublic
Security Archives - TechRepublic
Google DeepMind News
Google DeepMind News
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
月光博客
月光博客
N
News and Events Feed by Topic
Jina AI
Jina AI
博客园 - 司徒正美
WordPress大学
WordPress大学
罗磊的独立博客
雷峰网
雷峰网
AI
AI
Hugging Face - Blog
Hugging Face - Blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
S
Security @ Cisco Blogs
博客园 - 三生石上(FineUI控件)
H
Heimdal Security Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
酷 壳 – CoolShell
酷 壳 – CoolShell
C
Cisco Blogs
博客园 - 【当耐特】
The Hacker News
The Hacker News
有赞技术团队
有赞技术团队
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Schneier on Security
Schneier on Security
博客园 - Franky
S
SegmentFault 最新的问题
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Cloudbric
Cloudbric
爱范儿
爱范儿
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
S
Secure Thoughts
Last Week in AI
Last Week in AI
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Know Your Adversary
Know Your Adversary
Google DeepMind News
Google DeepMind News

少数派

派早报:Google 发布 Fitbit Air 等 - 少数派 「新人报到」確認需求,再開始 - 少数派 从 SOLO 独立开发者社区,我看到了越来越多开发者开始做自己的产品 - 少数派 我怎么管理那些"不常做,但总会忘"的生活事项 - 少数派 人形机器人量产元年,数据才是具身智能的“生死线” - 少数派 BuhoLaunchpad 高度还原 Mac 启动台:开发历程与思考 - 少数派 五年陪伴依然不舍,DIY 换壳后让罗技 MX Master 3 继续服役 - 少数派 新玩意 240|少数派的编辑们最近买了啥? - 少数派 一日一技|为什么你应该关闭 iOS 的键盘声音 - 少数派 我做了个插件和 Skills,一键提取任何网站的设计规范 Design.md - 少数派 住在三四线城市的你,该开始录播客了 - 少数派 甘南秘境,大白高国 - 少数派 AI的审美:谁让把我变成川内倫子 - 少数派 返工怎能不烦恼,打工人片单总有一部是你的「嘴替」 - 少数派 为了让「上厕所」更健康,我做了一个小工具 - 少数派 AI + Skill,能够让生成的文章去除 AI 味吗? - 少数派 新玩意|韶音OpenDots ONE 耳夹式耳机 - 少数派 《美满》| 在每一个春天的晚上相爱(362) - 少数派 新玩意|优篮子 PS01 MagSnap 磁吸支架 - 少数派 自我整合手记 | 我开始早睡了:用稳定规则,为自由托底 - 少数派 用龙虾(OpenClaw)两个多月,我最深的12个体会 - 少数派 听歌时间到,12 张你可能错过的 2025 华语乐坛好专辑 - 少数派 承诺能追吗 - 少数派 macOS 26启动台没了? 我做了个不一样的App启动器 - Keboard - 少数派 《四海为家的人》| INTJ对话INTJ(361) - 少数派 你发过的那些黑历史,是时候一次清干净了 - 少数派 新玩意:安安静静玩,越玩越专注:计客密码机 - 少数派 iPad 用户首次体验 Android 平板:vivo Pad6 Pro - 少数派 数据逻辑强 - 少数派 极北行+ | 一路向北,探访日本至北之地 | 001 - 少数派 万字剖析:千问App深度体验报告(2026) - 少数派 在2026年,如何真正防止别人抄袭你的作品 - 少数派 怎么用 50 块搭个 AI 语音助手?我踩了 3 天坑 - 少数派 YeeroAI:让 AI 对话真正成为知识管理的一部分 - 少数派 爬泰山 - 少数派 「旅图显影」 App 更新:这次,我们补上了一点「手感」 - 少数派 假期出门太折磨?我的 23 条经验帮你规划惬意旅行 - 少数派 工作流会变吗 - 少数派 Claude Opus 4.6 怎么用最省钱?我测了 5 种方案 - 少数派 GPT Image 2 让图文并茂不再稀罕 - 少数派 用户侧出发——什么是AI,我要不要学习? - 少数派 找片、转存、整理、播放一条龙!让你的付费网盘值回票价 - 少数派 欢迎试用!日课一问2.0插件 - 少数派 自己做的MDeditor,原本想购买 Typora 试了两次支付不成功,干脆自己做一个 - 少数派 vibe coding了一个 3MB 的小工具,让 ~/Downloads 彻底告别混乱 - 少数派 因为受不了 Mac 的风扇策略,我做了一个风扇控制工具 - 少数派 别只怪模型 - 少数派 Warp 终端的 AI 功能怎么用?我测了一周的体验 - 少数派 AI 写代码老是出 bug?这 5 个配置我后悔没早知道 - 少数派 「新玩意」苹果出相机可能就这样:Sigma BF + 45mm F2.8 DG Contemporary - 少数派 一个面向2030年的AI操作系统是什么样子的:浅谈cola这款有灵魂的Agent - 少数派 别只看写代码 - 少数派 每天解决10个问题,还是一口气攻坚解决400个? - 少数派 AI 交易机器人怎么搭?我用 Claude 跑了一周实盘 - 少数派 Maptoposter Online:把你爱的城市画成艺术海报 - 少数派 Function Calling 怎么用?我测了 3 个模型发现差距真大 - 少数派 Legend Talk:我做了个 AI 圆桌,让 160 位思想家围着你的问题转 - 少数派 如何找到自己的蓝方?在小县城寻找压力测试 - 少数派 语音输入与软件接口|2026年聊AI时,我们都聊些什么(上) - 少数派 混动已经卖爆,纯电又来补刀——钛7闪充版简直“不讲武德” - 少数派 本月玩什么|朋友收藏、识质存在、沙罗周期 - 少数派 为什么要每天坚持输出? - 少数派 Claude API 挂了好几个小时,你的项目有备用方案吗? - 少数派 Function Calling 没你想的复杂——我用它做了个有点用的工具 - 少数派 登录系统立即播放视频或者图片音乐的软件 - 少数派 我为什么创建 FlipHTML5 下载工具 - 少数派 残局没电?多品牌外设电量统一管理软件EasyBluetooth已支持RTSS游戏内显示以及AIDA64 - 少数派 前往通义路的路 - 少数派 太好看了,媲美Sun的个人导航页,NAS部署星云门户 - 少数派 乌黑嘴唇“一键检测”上线了 - 少数派 派早报:Claude AI 接入多个创意软件生态、FILCO 生产方接手品牌等 - 少数派 【更新】BearCLI、Claude 连接器与 MCP 服务器 - 少数派 记了上千条流水,还是看不懂财务?我做了一个让 AI 读懂账本的工作台 - 少数派 MINI R56 升级原厂 Sport 模式 - 少数派 新玩意 | 一棵柠檬树(仿真版) - 少数派 Momenta的“物理AI”野望,需迈过“含摩量”这道关 - 少数派 网页直接投屏控制手机!NAS一键部署PandaScrcpy,流畅丝滑可远程。 - 少数派 众测|邀你一同探索随身 AI 硬件入口 YoooClaw C·ONE - 少数派 2050大会:分享时间是真诚 参会记 - 少数派 iPad 赋能电影创作:国内首部宣纸手绘长片《燃比娃》的幕后故事 - 少数派 AI的审美:我用 8 个大模型给 100 张旅行照片打分 - 少数派 普通人如何破圈?去参加一个本地协会 - 少数派 把极空间的图标全换了,主题DIY全攻略打造你的专属NAS桌面 - 少数派 电子便签墙,帮你实现便签自由 - 少数派 我如何用三个 CLI 工具取代文档创建需求 - 少数派 原来真的有人可以玩一辈子 - 少数派 社区速递 139 | 派友热议三月买了啥、复古单反尼康 Df 体验 - 少数派 06 作品的赏析与评价 - 少数派 TDS REVIEW|索尼 WF-1000XM6 降噪真无线耳机体验 - 少数派 35.98万起售的第二代腾势D9,我看重的不是堆料,而是不凑合 - 少数派 鼠须管 Squirrel 皮肤配置指北 - 少数派 从watch ultra2换到redmi watch6 - 少数派 派早报:阿里巴巴发布视频生成模型 HappyHorse 1.0 等 - 少数派 别迷信1M - 少数派 家人们天塌了!网盘“大封杀”,多个渠道多条路,NAS部署PanHub - 少数派 AI与人勾心斗角!NAS一键部署AI狼人杀,假日休闲必备。 - 少数派 电商必备!Comfyui工作流批量生图插件,一次生成12张!支持Nano banana pro模型 - 少数派 Comfyui工作流配置Gpt-image-2模型教程,0.03/张 - 少数派 OpenClaw第三方APi怎么配置?可使用Gpt-image-2模型 - 少数派 会员社区话题精选 Ep. 103 - 少数派
24. 装饰器语法与应用 - 少数派
2023-08-20 · via 少数派
cover

Hi, 大家好。我是茶桁。

在最近几期的课程中,相信小伙伴们都频繁的看到一个词:「装饰器」, 那到底什么是装饰器,又有什么作用呢?我们这节课,就来好好的来了解一下。

装饰器定义

装饰器就是在不改变原有函数代码,且保持原函数调用方法不变的情况下,给原函数增加新的功能(或者给类增加属性和方法)。

**核心思想:**用一个函数(或者类)去装饰一个旧函数(或者类),造出一个新函数(或者新类)。

**应用场景:**引入日子,函数执行时间的统计,执行函数钱的准备工作,执行函数后的处理工作,权限校验,缓存等。

**语法规则:**在原有的函数上加上@符,装饰器会把下面的函数当作参数传递到装饰器中,@符又被称为「语法糖」。

装饰器原型

装饰器其实就是利用闭包,把函数当作参数传递,并在在函数内去调用传递进来的函数,并返回一个函数。

来,我们还是用代码来学习,先让我们定义一个普通函数:

# 定义一个普通函数
def old():
    print('我是一个普通的函数')

old() # 作为普通函数直接调用

---
我是一个普通的函数

现在让我们定义一个嵌套函数,分为外函数和内函数两部分:

# 定义外函数,接受一个函数作为参数
def outer(f):
    # 定义内函数, 并且在内函数中调用了外函数的参数
    def inner():
        print('我是外函数中的内函数1')
        f()
        print('我是外函数中的内函数2')
    return inner

这里面我们在内函数中打印了两句话,在两句话中间执行了一次外函数的参数(传递进来一个函数)。最后讲内函数作为参数返回。

然后我们讲刚才的普通函数old作为参数传进去,然后再用外函数返回的inner内函数重新赋值普通函数old,最后让我们再执行一遍old函数, 这个时候,因为old被重新赋值,其实等同于调用了inner函数。来,我们看看结果:

old = outer(old) # outer返回了inner函数,赋值给了old
old()

---
我是外函数中的内函数1
我是一个普通的函数
我是外函数中的内函数2

是不是稍显繁杂?那让我们换个思路,现在我们已经先定义好了outerhe inter,两者关系不变。还是之前那些代码,那么我们如何利用装饰器来进行调用呢?

# 装饰器用法
@outer # 此处将outer作为了装饰器
def old():
    print('我是一个普通的函数')

我们在定义old函数的时候,直接加上一个@语法糖,就将outer作为了装饰器。这个装饰器的作用就等同于old = outer(old)。那让我们打印看看结果:

old()

---
我是外函数中的内函数1
我是一个普通的函数
我是外函数中的内函数2

那我们现在完成了装饰器的用法,按照定义,我们在不改变old函数的代码,且保持了old函数调用方法不变的情况下,增加了新的方法outer

old函数经过outer装饰器进行了装饰,代码和调用方法不变,但是函数的功能发生了改变。

你是不是这个时候又有疑问了,那装饰器要用在什么地方呢?让我们来实现一个应用:

装饰器应用:统计函数的执行时间

在正式写代码之前,我还是习惯带着大家先思考一遍。我们需要统计函数的执行时间,那我们需要什么关键点?

  1. 开始时间,
  2. 结束时间
  3. 开始时间和结束时间之间,就是程序在运行的过程。

好的,让我们来开始写代码,先来一段简单的需要运行的程序,为了能顺利统计时间,我们给它设定两个东西,一个循环,一个停止运行时长。这样,我们不会因为程序运行过快而看不到结果:

import time

# 定义一个普通函数
def func():
  for i in range(5):
    print(i, end=" ")
    time.sleep(1)

函数写完之后,让我们来执行一下看看:

func()

---
0 1 2 3 4
5sec_func

没问题,确实是一秒打印一次。

现在,再来让我们完成要称为装饰器的统计函数:

# 定义一个统计函数执行时间的装饰器
def runtime(f):
    def inner():
        start = time.perf_counter()
        f()
        end = time.perf_counter() - start
        print(f'\n函数的调用执行时间为:{end}')
    return inner

在函数inner中,我们最终是打印了最终的时间end, 然后将整个inner函数返回。

那么,让我们来尝试执行一下看看吧:

func = runtime(func)
func()

---
0 1 2 3 4 
函数的调用执行时间为:5.017943917075172

这样,我们就得到了func这个函数最后执行的时间,那现在的问题是,统计时间的函数是一个通用函数,我们很多函数中都需要用到它进行统计。但是我们总不能所有的函数都要用这种方法重新赋值之后再调用吧?

那我们就用装饰器来解决就好了:

# 定义一个普通函数
@runtime
def func():
  for i in range(5):
    print(i, end=" ")
    time.sleep(1)
    
func()

---
0 1 2 3 4 
函数的调用执行时间为:5.017888417001814

当然,最终这种函数的调用执行时间并不会像现在这样打印到前台,而是会写进log变为日志存储起来,便于之后分析使用。

装饰器嵌套语法

在这一段代码中,我们来约个妹子,完成一场约会。从哪开始呢?就从找妹子要微信开始吧:

def begin(f):
    def begin_inner():
        print('找妹子要微信,成功...')
        f()
        print('送妹子回家...')
    return begin_inner
    
@begin
def love():
    print('跟妹子畅谈人生和理想...')

love()

---
找妹子要微信,成功...
跟妹子畅谈人生和理想...
送妹子回家...

那这样,我们实现了一段最普通装饰器的定义。现在让我们在下面再定义一个装饰器函数,为什么呢?因为我渐渐不满足于只谈理想和人生了,要有点实际的行动了,顺便,我们写了一个列表,把和妹子要做的事情都列了个顺序,再来看看:

def begin(f):
    def begin_inner():
        print('找妹子要微信,成功... 1')
        f()
        print('送妹子回家... 5')
    return begin_inner

def evolve(f):
    def evolve_inner():
        print('和妹子一起吃了个大餐.. 2')
        f()
        print('和妹子看了一场夜场电影... 4')
    return evolve_inner

@evolve
@begin
def love():
    print('跟妹子畅谈人生和理想... 3')

love()

---
和妹子一起吃了个大餐.. 2
找妹子要微信,成功... 1
跟妹子畅谈人生和理想... 3
送妹子回家... 5
和妹子看了一场夜场电影... 4

这个...顺序似乎不太对啊。让我们改变一下装饰器的顺序试试:

@begin
@evolve
def love():
    print('跟妹子畅谈人生和理想... 3')

love()

---
找妹子要微信,成功... 1
和妹子一起吃了个大餐.. 2
跟妹子畅谈人生和理想... 3
和妹子看了一场夜场电影... 4
送妹子回家... 5

这回没错了,我们在最开始要妹子微信和最后送妹子回家中间,又进行了点什么。也算是有些进展了。那么,我们怎么去理解这个程序运行顺序呢?

  1. 先使用离得最近的begin装饰器,装饰love函数,返回了一个begin_inner函数
  2. 在使用上面的evolve, 装饰了上一次返回的begin_inner函数,又返回了一个evolve_inner函数。

在调用完成之后,就是需要顺序执行了,其执行的嵌套关系和顺序如下:

love_func

那这,就是我们嵌套装饰器的用法。当然,这种嵌套装饰器的用法并不常见,可是一旦我们遇到了,要理解他的运行机制和顺序,避免不必要的麻烦。

装饰带有参数的函数

上一个部分,我们做了一个约会妹子的函数,并且使用装饰器进行了装饰。使的我们成功的按照进度依次执行了自己的计划。

但是问题来了,我们到目前为止约会了那么多妹子,都不知道谁是谁(海王体质),这可怎么办。这次,我们吸取教训,先要名字,既然之前的流程很成功,我们直接拿来使用就行了。但是繁杂的步骤我们都去掉,直奔主题:

# 带有参数的函数
def love(name):
    print(f'跟{name}妹子在___畅谈人生...')

一个简单的函数,并且是一个填空题,你愿意待谁去哪里畅谈人生,随便。比如我,找「露思」去,去了哪里,恕不奉告了:

love('露思')

---
跟露思妹子在___畅谈人生...

可是即便如此,该有的流程还是不能丢,总不能凭空变出个妹子吧,还是得把必要的流程加上,原本定义的装饰器函数似乎不能使用了:

# 定义装饰器
def outer(f):
    def inner():
        print(f'找到妹子,成功的拿到了微信...')
        f()
        print(f'约妹子去看一场午夜电影...')
    return inner

@outer
def love(name):
    print(f'跟{name}妹子在___畅谈人生...')

流程上现在是没问题了,可是我们执行一下发现,报错了。

love('露思')

---
TypeError: outer..inner() takes 0 positional arguments but 1 was given

进行不下去了吧?那没办法,谁叫你之前和之后都把人家名字忘了呢,海王也得有点职业道德才行。既然我们在执行的时候有参数,那你整个过程中都得带上才行。不能玩着玩着忘记人家名字,对吧。让我们改进一下,既然我们已经知道,在使用装饰器装饰过后的love()执行实际上是执行装饰器inner, 那我们尝试给inner加上参数进行传递,还有很重要的,我们之前和之后,得把妹子名字记清楚才行,所以执行的时候也记得加上:

# 定义装饰器
def outer(f):
    def inner(var):
        print(f'找到{var}妹子,成功的拿到了微信...')
        f(var)
        print(f'约{var}妹子去看一场午夜电影...')
    return inner

# 带有参数的函数
@outer
def love(name):
    print(f'跟{name}妹子在___畅谈人生...')

love('露思')

---
找到露思妹子,成功的拿到了微信...
跟露思妹子在___畅谈人生...
约露思妹子去看一场午夜电影...

嗯,这样一场和「露思」妹子之间完美的从认识到约会流程就完成了。我们总结一下:

如果装饰器带有参数的函数,需要在内函数中定义形参,并传递给调用的函数。因为调用原函数等于调用内函数。

装饰多参数的函数

上一个流程跑完之后呢,我觉得还是不太妥当。主要是其中有两个选择题,一个是谁,一个是去哪里。对吧。再说了,我也得跟妹子自我介绍一下,加强一点印象。

好,我们这次再多设置几个参数,让整个约会过程更完善一些,那我们从最初就要规划一下,需要的参数包括:我,妹子,地点,行为等等。还蛮多的。

# 装饰带有多参数的函数
def outer(f):
    def inner(man,name,*args,**kwargs):
        print(f'{man}要到了{name}妹子的微信...')
        f(man, name, *args, **kwargs)
        print('天色渐晚...')
    return inner

# 定义多参数的函数
@outer
def love(man, name, *args, **kwargs):
    print(f'{man}跟{name}畅谈人生...')
    print(f'带{name}妹子去吃了很多美食:', args)
    print(f'和{name}妹子看了夜场电影:', kwargs)

这样我们就定义好了,至于*args以及**kwargs是什么,可以翻看之前的教程。

让我们现在来执行一下:

love('茶桁','露思', '火锅', '海鲜', '饭后甜点', mov='封神第一部')

---
茶桁要到了露思妹子的微信...
茶桁跟露思畅谈人生...
带着露思妹子去吃了很多美食: ('火锅', '海鲜', '饭后甜点')
和露思妹子看了夜场电影: {'mov': '封神第一部'}
天色渐晚...

这样,多道选择题就被我们一一的化解了。相信「露思」妹子对我们的整体安排也是相当的满意了。

带有参数的装饰器

在我们平时使用Python各种第三方库的时候,不可避免的会遇到带有参数的装饰器。比如说,Django框架中的@login_required(redirect_field_name="my_redirect_field")

image-20230819183310594

这种带有参数的装饰器是干嘛的呢?还是拿我们之前的海王约会流程来举例。之前的流程是都没有什么问题,可是有没有发现,所有事情都是我们自己做主了,似乎妹子一直都没有反对过,也没有说自己想要什么。这是不是不太符合现实?

没错,我们也要给妹子装上一个会思考的大脑,也要学会做判断,好,让我们来实现一下:

既然我们这节是学习带有参数的装饰器,那么必然装饰器上是带参数的。呃,不要认为这句话是废话,我们看代码:

@put(var)
def love():
    print('畅谈人生...')

就像这样,我们给装饰器加上了参数。

那么现在问题就来了,我们来看看我们之前写的装饰器函数:

def outer(f):
  def inner():
    pass
  return inner

发现有什么问题了么?虽然我们的外层函数outer是有形参的,但是我们之前的过程中了解到,这个形参f是为了接收当前执行函数的。那还有什么其他地方接收非函数的普通参数嘛?

既然outer中很重要的作用,除了接收函数在内函数内执行,还有一个就是返回inner内函数,那么我们在不改变outer的基础之上,再加一个接收普通参数的函数不就行了。

def put(var):
    def outer(f):
        def inner1():
            print('妹子给了你微信')
        def inner2():
            print('妹子给了你她闺蜜的微信')
        def inner3():
            print('妹子送了你一句感人肺腑的话:滚...')

        # 装饰器壳的参数,可以用于在函数内去做流程控制。
        if var == 1:
            return inner1
        elif var == 2:
            return inner2
        else:
            return inner3
    return outer

@put(2)
def love():
    print('畅谈人生...')

定义完成之后,我们来执行一下看看:

love()

---
妹子给了你她闺蜜的微信

家人们谁懂啊,妹子真把我当海王了嘛?最后,我还是老老实实的接受了妹子的好意。

从整段代码中我们可以看出来,如果装饰器中有参数,需要有一个外壳函数来接收参数,传参之后就会进入到下一层函数中,并且传递当前对象。再然后才会再进入下一层中去。当然,我们在这里,利用传递的参数写了一段if判断,用于确定妹子的决定是什么。然后我们就返回哪个决定的函数。最后别忘记,在外壳函数中,我们还需要讲outer函数返回出去。此时虽然love函数是outer函数,但是在之前,put装饰器已经将参数传递给了外壳函数put(var)。在装饰器函数的争端代码中,我们都没有再执行过传进来的参数,也就是函数love(), 所以此段代码中love函数中的打印方法并未执行。

其执行步骤为:

put(var) => outer() => outer(love) => inner2()

用类装饰器装饰函数

之前我们所有的代码中,装饰器一直使用的都是函数装饰器。那我们能否用类来当装饰器装饰函数呢?

试试不就知道了。

# 类装饰器装饰函数
class Outer():
    def __call__(self, func):
        self.func = func
        return self.inner
    
    def inner(self, who):
        print('拿到妹子的微信...')
        self.func(who)
        print('看一场午夜电影...')
@Outer()
def love(who):
    print(f'{who}和妹子谈理想与人生...')

写完了,这下我们省略了那么多杂七杂八的流程,因为我发现,那么多流程下来,最终感动的人只有自己。妹子愿意,怎么都愿意,不愿意的,无论做多少都不愿意。

来,让我们跑一下程序试试:

love('茶桁')

---
拿到妹子的微信...
茶桁和妹子谈理想与人生...
看一场午夜电影...

没问题,正确的执行。那这个时候的love函数到底是什么呢?我们来打印出来看看:

print(love)

---
<bound method Outer.inner of <__main__.Outer object at 0x106646c80>>

可以看到,此时的love函数就是Outer类中的inner函数。那我们怎么理解整个代码呢?

我们在love函数上使用了装饰器Outer, 那么这个时候Outer就会实例化出来一个对象obj,然后这个@obj就等同于obj(love)

然后我们实例化对象进入Outer()内部,进入之后遇到了魔术方法__call__, 它会把该类的对象当作函数调用时自动触发。也就是obj()触发。

还记得类的实例化么?会传入一个参数,也就是实例化对象本身:obj。并且,第二个参数func用来接收了传递进来的函数love, 设置了self.func = func, 把传进来的函数作为对象的成员方法。最后返回了一个函数inner, 这个返回的函数是类中定义好的,于是作为实例化对象也将这个成员方法继承了下来,所以self.inner可以直接被返回出去。

这个定义好的inner接收了两个形参,一个是实例化对象本身,一个就是传递进来的函数love的参数who。然后,中间执行了一下魔术方法__call__内定义好的self.func(who),实际上也就是obj.love(who)

看着迷糊?这样,我写一个注释过的完整版本。

# 类装饰器装饰函数
class Outer():

    # 魔术方法:当把该类的对象当作函数调用时,自动触发obj()
    def __call__(self, func):
        # 把传进来的函数作为对象的成员方法
        self.func = func 
        # 返回一个函数
        return self.inner
    
    # 在定义的需要返回的新方法中,去进行装饰和处理
    def inner(self, who):
        print('拿到妹子的微信...')
        self.func(who)
        print('看一场午夜电影...')
'''
Outer() 实例化对象 => obj
@obj 就等于 obj(love)
进入类后 => __call__(love)
接收返回参数`inner()`
'''
@Outer()
def love(who):
    print(f'{who}和妹子谈理想与人生...')

# inner('茶桁')
love('茶桁')
# 此时的love就是属于`Outer`类这个对象中的inner方法
print(love)

不知道这段注释代码加上刚才的解说,大家能否看懂?有没有发现,用类做装饰器比起函数装饰器反而更清晰一点?不需要写那么多外层函数和内层函数。

让我们继续...

类方法装饰函数

刚才我们将整个类都用作了一个装饰器,那我们思考一下,是不是我们还可以用类中的方法来做装饰器呢?

说干就干,直接上代码测试:

# 用类方法装饰函数
class Outer():
    def newinner(f):
        # 把传递进来的函数定义为类方法
        Outer.func = f
        # 同时返回一个新的类方法
        return Outer.inner
    
    def inner():
        print('拿到妹子微信...')
        Outer.func()
        print('看一场午夜电影...')

'''
Outer.newinner(love) 
接收返回参数:Outer.inner
'''

@Outer.newinner
def love():
    print('和妹子谈谈人生喝喝茶...')
# love() 等于 Outer.inner()
love()

---
拿到妹子微信...
和妹子谈谈人生喝喝茶...
看一场午夜电影...

在经历了几场约会之后,我们的耐心也渐渐没了。连是谁都不管,也没耐心去谈理想了。喝点茶聊聊天就直奔主题了都是。

到目前为止以上所有形式的装饰器,包括「函数装饰器」、「类装饰器」、「类方法装饰器」,都有一个共同特点:都是在给函数去进行装饰,增加功能

那我们这个时候就不满足了,既然能装饰函数,那是否也能装饰类呢?

用装饰器装饰类

还真有一种装饰器是专门装饰类的,也就是在类的定义的前面使用@装饰器这种语法。和装饰函数并无什么区别,只是放在了类前面而已:

@装饰器
class Demo():
  	pass

装饰器给函数进行装饰,目的是不改变函数调用和代码的情况下给原函数增加新的功能。

装饰器给类进行装饰,目的是不改变类的定义和调用的情况下给类增加新的成员(属性或者方法)。

来,让我们具体的看看:

函数装饰器装饰类

# 定义函数,接收一个类。返回修改后的类
def expand(cls):
    def func2():
        print('我是在装饰器中追加的新方法,func2')
    cls.func2 = func2 # 把刚才定义的方法赋值给 类
    cls.name = '我是在装饰器中追加的新属性 name'

    # 返回时,把追加类新成员的类返回去
    return cls


@expand   # expand(Demo) ==> cls ==> Demo
class Demo():
    def func():
        print('我是Demo类中定义的func方法')

Demo.func() # 此时在调用的Demo类是通过装饰器,更新过的Demo类
Demo.func2()
print(Demo.name)

---
我是Demo类中定义的func方法
我是在装饰器中追加的新方法,func2
我是在装饰器中追加的新属性 name

这样,我们在原来的Demo这个类中,使用装饰器增加了一个成员方法func2,并且增加了一个成员属性name, 并最终返回到原始类中。从而扩展了这个原始类Demo中的方法和属性。

类装饰器装饰类

# 使用类装饰器装饰类
class expand():
    def __call__(self, cls):
        # 把接收的类,赋值给当前对象,作为一个属性
        self.cls = cls
        # 返回一个函数
        return self.newfunc

    def newfunc(self):
        self.cls.name = '我是在类装饰器中追加的新属性 name'
        self.cls.func2 = self.func2
        # 返回传递进来的类的实例化结果,obj
        return self.cls()

    def func2(self):
        print('我是在类装饰器中追加的新方法 func2')


'''
expand() ==> obj ==> @obj(Demo) ==> __call__(Demo) ==> newfunc
'''
@expand() 
class Demo():
    def func(self):
        print('我是Demo类中定义的func方法')

obj = Demo()  # Demo() ==> newfunc() ==> obj
obj.func()
obj.func2()
print(obj.name)

在之前那么多案例过后,相信大家这一段代码应该能看的出来吧。

那我这个地方要处一个思考题了,请问:此时的 obj这个对象,是哪个类的对象。Demo还是expand?

print(obj)

---
???

这个问题的答案,我放在源码中了,大家要记得思考之后再去看答案。

那么,本节课的内容到这里也就结束了。

课程进行到这里,我们Python本身的所有内容就已经介绍完了。下节课开始,我们就要考试讲第三方库。

好,下课。