每周,会用大白话,讲解一个 Python 中那些难以理解的知识点。希望通过这样的白话讲解,可以让大家更好的学会 Python。今天这篇文章的主题是:大白话讲解 Python 装饰器; Python 装饰器是在面试过程高频被问到的问题,装饰器也是一个非常好用的特性,熟练掌握装饰器会让你得到以下几个优势:
程序可读性高
代码复用性高
你的编程思路更加宽广
程序更加 pythonic,更像专业写 Python 代码
但对于新手来说 Python 装饰器非常难以理解:
不清楚什么是装饰器
不清楚装饰器有什么用
不清楚如何写装饰器代码
所以痴海今天的 P 百科,会带大家彻底的弄明白 Python 装饰器。 文章主要从下面 4 个方面进行讲解: 1 装饰器是什么2 装饰器理解基础3 实现一个装饰器4 装饰器总结
1 装饰器是什么
俗话说:艺术来源于生活。 痴海说:编程也来自生活。 编程的世界也是如此,很多编程世界里的概念,都是源自生活中的方方面面。 每个编程语言被创造出来,就是为了解决现实世界某个具体的问题。 而我们的今天的主角「装饰器」也是如此。 1.1 那什么是装饰器? 我们来看一个生活中具体的案例:手机钢化膜。 现在基本人手一台手机,大家也知道手机屏幕摔倒地板,是非常容易坏。 手机屏幕一坏,大家都非常心痛,因为如果拿出维修,费用都够重新购买一台新的手机了。 那我们要怎样在不破坏原本手机屏幕结构的同时,让我们的手机更耐坏呢? 这时候你只需要花几块钱购买一个钢化膜,来装饰我们的手机。 有了这块钢化膜,从此你就不怕手机摔倒地上。 手机钢化膜就是现实中的装饰器:钢化膜在不改变原有手机的结构下,让手机屏幕变的更耐摔。 而我们编程中的装饰器,同样的道理:装饰器可以在不改变原有函数的结构下,让程序更加强大。 装饰器本质上是一个 Python 函数或类,它可以让其他函数或类在不需要做任何代码修改的前提下增加额外功能,装饰器的返回值也是一个函数/类对象。 1.2 装饰器有什么作用 通过上面的例子你现在应该是能明白什么是装饰器:用来装饰函数用的,让函数更加强大。 但你现在还是不知道 Python 里的装饰器长什么样,先不着急,我们先来看下装饰器有什么作用。 那装饰器都有什么作用? 简单说:增强函数的功能。 如果你想在不改变原有函数的基础上,增加新的函数功能,那装饰器就非常适合你。 装饰器的存在是为了适用两个场景: 1 一个是增强被装饰函数的行为;2 另一个是代码复用; 具体举几个例子:
插入日志
性能测试
事务处理
缓存
权限校验
上面都是实际工作中会经常遇到的场景。 1.3 小结 看到这里你应该是可以明白以下几个知识点: a 什么是装饰器 装饰器可以在不改变原有函数的结构下,让程序更加强大。 b 装饰器有什么作用 增强函数的功能 c 装饰器适用的两个场景 a1 增强被装饰函数的行为;b1 代码复用; 有了以上 3 个学前知识,你现在应该对装饰器,有了基本的认知。 但你仍然不知道装饰器的代码如何编写,所以接下来我们就手把手教会大家如何编写一个 Python 装饰器代码。 同样在编写装饰器代码之前,有 2 个基础你一定要先知道。 a Python 中的函数也是变量b Python 中的高阶函数 我们来具体讲解这个 2 个基础。 2 装饰器理解的基础
2.1 Python 中的函数也是变量
Python 中的函数可以像普通变量一样,当做参数传递给另外一个函数。 我们来看下这个案例1:输出“痴海666”
1def chihai():
2 print('痴海666')
3
4chihai()
5y = chihai
6y()
输出结果:
1痴海666
2痴海666 在代码中我们首先定义了函数 chihai,并调用了 chihai 函数,并且把 chihai 赋值给 y。 y = chihai 表明了:函数名可以赋值给变量,并且不影响调用。
这样讲,可能还有些人不太明白。 我们在来对比下我们常用的操作。 这其实和整数、数字是一样的,
下面的代码你肯定熟悉:
1a = 1
2b = a
3print(a, b)
2.2 Python 中的高阶函
什么是高阶函数?
非常简单,只要满足如下的两个条件中的任意一个: a. 可以接收函数名作为实参;b. 返回值中可以包含函数名。 一个函数接收另一个函数作为参数,这种函数称之为高阶函数。 这样的一个函数就是高阶函数,同样我们改造下之前的代码,让它变成高阶函数: 案例2:高阶函数输出“痴海666”
1def chihai(name):
2 print('{}666'.format(name))
3
4def name(func):
5 func()
6
7name(chihai('痴海'))
输出结果:
1痴海666
在案例2中我们定义了两个函数:chihai 和 name,其中 pname函数我们定义了一个 func 的参数。 这个参数我们是作为一个函数调用,也就是 chihai 这个函数,从而编写出了一个高阶函数。 在 Python 标准库中有许多的高阶函数,比如 map、filter、reduce、sorted。
3 实现一个装饰器
现在你已经知道了「函数名赋值」和「高阶函数」,有了这两个基础,我们就可以尝试实现一个类似的装饰器。 还是刚才例子,我们继续改造:实现不修改 chihai 函数的结构,多输出一句“我是痴海”。 案例3:类似装饰器实现输出“痴海666”的同时,输出“我是痴海”。
1def name(func):
2 print('我是痴海')
3 return func
4
5def chihai():
6 print('痴海666')
7
8temp = name(chihai)
9temp()
输出结果:
1我是痴海
2痴海666
在这个例子中我们定义了一个 name 函数,name 接收一个函数名,然后直接返回该函数名。 这样我们实现了不修改原函数 chihai,并且添加了一个新功能的需求:多输出了“我是痴海” 但是这里有个缺陷就是函数的调用方式改变了。 即不是原本的 name,而是 temp。 要解决这个问题很简单,相信 a = a*3 这样的表达式大家都见过。 那么上述代码中的 temp = name(chihai),同样可以修改为:
name = name(chihai) 这样我们就完美的解决了问题:既添加新功能又没有修改原函数和其调用方式。 修改后的代码如下:
1def name(func):
2 print('我是痴海')
3 return func
4
5def chihai():
6 print('痴海666')
7
8name = name(chihai)
9name()
但这样的代码却有个不便之处,即每次使用这样的装饰器,我们都要写类似 name = status(name) 的代码。 程序员都是懒的,所以才有那么多高级的语法。 在 python 中为了简化这种情况,提供了一个语法糖 @。 @符号是装饰器的语法糖,在定义函数的时候使用,避免再一次赋值操作。 在每个被装饰的函数上方,使用这个语法糖,就可以省掉这一句代码 name = chihai(name)。 最后的代码如下:
1def name(func):
2 print('我是痴海')
3 return func
4
5@name
6def chihai():
7 print('痴海666')
8
9chihai()
这样我们就弄清楚了装饰器的工作原理: 1 写一个高阶函数,即参数是函数,返回的也是函数;2 在利用语法糖@,简化赋值操作; 案例3 最后的代码,就是一个非常简单的装饰器实现代码。 但这样的代码是有缺陷:name 函数直接返回了函数名,这样函数调用后我们就没办法做任何事情。 能看这篇文章的同学,肯定是喜欢 Python 这门语言的,但不一定有关注痴海的公众号。 所以我要在写一个函数,来模拟判断大家是否关注了痴海的公众号。 为了能判断大家是否关注我,此时我就需要在嵌套一层函数。 将实现额外功能的部分写在内层函数中,然后将这个内层函数返回即可。 这也是为什么装饰器都是嵌套函数的原因。 下面是实现了能够处理返回值的装饰器:
1def name(func):
2 def follow_state():
3 result = func()
4 print(result)
5 return result
6 print('我是痴海')
7 return follow_state
8
9@name
10def chihai():
11 print('痴海666')
12 return '学 Python 找痴海!我已关注!'
13
14chihai()
输出结果:
1我是痴海
2痴海666
3学 Python 找痴海!我已关注!
现在我们实现了能够处理返回值的装饰器,但可能你的名称不是叫痴海,可能叫小海、小痴,对不对。 所以我们还需要实现一版带参数的装饰器,这样就能修改我是谁:
1def name(name):
2 def judgen_follow(func):
3 def follow_state():
4 result = func()
5 print(result)
6 return func
7 return follow_state
8 print("我是{}".format(name))
9 return judgen_follow
10
11@name(name='小痴')
12def chihai():
13 print('痴海666')
14 return '学 Python 找痴海!我已关注!'
15
16chihai()
输出结果:
1我是小痴
2痴海666
3学 Python 找痴海!我已关注!
上面的 name 是允许带参数的装饰器。 它实际上是对原有装饰器的一个函数封装,并返回一个装饰器。 我们可以将它理解为一个含有参数的闭包。 当我们使用@name(name="小痴")调用的时候,Python 能够发现这一层的封装,并把参数传递到装饰器的环境中。 @name(name="warn")等价于@judgen_follow,这样我们就实现了一个可以带参数的装饰器。
4 装饰器总结 最后我把今天重要的知识点,全部给大家一起总结出来:
1 什么是装饰器 a. 大白话总结装饰器:保护手机的手机壳b. 专业话总结装饰器:处理函数的函数 2 装饰器有什么用 a. 增强函数的功能 3 装饰器使用场景 a. 增强被装饰函数的行为b. 代码复用 4 装饰器理解的基础 a. Python 中的函数也是变量b. 一个函数接收另一个函数作为参数,这种函数称之为高阶函数。c. @符号是装饰器的语法糖,在定义函数的时候使用,避免再一次赋值操作。
装饰器的本质是函数,其参数是另一个函数(被装饰的函数)。 装饰器通常会额外处理被装饰的函数,然后把它返回,或者将其替换成另一个函数或可调用对象。 行为良好的装饰器可以重用,以减少代码量。 对于一个最简单的二层装饰器,其实就是外层函数传入被装饰的函数的函数名。 内层函数要么调用这个被装饰的函数,要么返回这个被装饰的函数的函数名。 外层函数返回内层函数的函数名。 装饰的过程在内层函数中完成。 对于今天的文章,我总结了下:
1我是痴海,一位 95 后的 coder。
2痴海666
3学 Python 找痴海!我已关注!