Python 装饰器是在面试过程高频被问到的问题,装饰器也是一个非常好用的特性,

熟练掌握装饰器会让你的编程思路更加宽广,程序也更加 pythonic。

今天就结合最近的世界杯带大家理解下装饰器。

德国战车

6 月 17 日德国战墨西哥,小痴虽然是一个伪球迷,但每年的世界杯还是会了解下。而德国是上届的冠军,又是这届夺冠热门。德意志战车在 32 年间小组赛就没有输过!卧槽!虽然小痴很少赌球,但这次德国如此强大,肯定会赢吧。搏一搏单车变摩托!随后小痴买了德国队赢。心里想着这次肯定稳了!赢了会所嫩模!小痴连比赛都不看,美滋滋的敲着代码。

然后比赛结果却是德国爆冷 0:1 输给墨西哥队,德国队输了比赛,小痴也下海干活。只是此时的天台有点挤,风还有大。

小痴含泪的写下了下面的代码:

def guess_win(func):
def rooftop_status():
result = func()
print('天台已满,请排队!')
return result
return rooftop_status
@guess_win
def german_team():
print('德国必胜!')

输出结果:

德国必胜! 天台已满,请排队! 复制代码

装饰器是什么


首先我们先来了解下什么是装饰器,严格来说,装饰器只是语法糖,装饰器是可调用的对象,可以像常规的可调用对象那样调用,特殊的地方是装饰器的参数是一个函数。

装饰器的存在是为了适用两个场景,一个是增强被装饰函数的行为,另一个是代码重用。

比如在上面的例子中我们在压德国队赢的时候,原本的 german_team() 函数只是输出德国必胜,但在使用装饰器(guess_win)后,它的功能多了一项:输出「天台已满,请排队!」。这就是一个简单的装饰器,实现了「增强被装饰函数的行为」。

一个良好的装饰器必须要遵守两个原则:

  • 1 不能修改被装饰函数的代码

  • 2 不能修改被装饰函数的调用方式

这里并不难以理解,在现在的生产环境中,很多代码是不能轻易的改写,因为这样有可能发送意想不到的影响。还有一点就是我们在看大神的代码,我们根本不懂如何改写。同时你也不能修改调用方式,因为你并不知道有在一个项目中,有多少处应用了此函数。

装饰器理解基础


如果你想要很好的理解装饰器,那下面的两个内容需要你先有所认知。

  • 1 函数名可以赋值给变量

  • 2 高阶函数

1 函数名可以赋值给变量


我们来看下这个例子:

def func(name):
print('我是{}!慌的一逼!'.format(name))
func('梅西')
y = func
y('勒夫')

输出结果:

我是梅西!慌的一逼! 我是勒夫!慌的一逼!

在代码中我们首先定义了函数 func,并调用了 func 函数,并且把 func 赋值给 y。y = func 表明了:函数名可以赋值给变量,并且不影响调用。

这样讲,可能还有些人不太明白。我们在来对比下我们常用的操作。这其实和整数、数字是一样的,下面的代码你肯定熟悉:

a = 1
b = a
print(a, b)

2 高阶函数

高阶函数满足如下的两个条件中的任意一个:a.可以接收函数名作为实参;b.返回值中可以包含函数名。

在 Python 标准库中的 map 和 filter 等函数就是高阶函数。

l = [1, 2, 4]
r = map(lambda x: x*3, l)
for i in r:
print('当前天台人数:', i)

输出结果:

当前天台人数: 3 当前天台人数: 6 当前天台人数: 12 复制代码

自定义一个能返回函数的函数,也是高阶函数:

def f(l):
return map(lambda x: x *5, l)
a = f(l)
for i in a:
print('当前天台人数:', i)

输出结果:

当前天台人数: 5 当前天台人数: 10 当前天台人数: 20 复制代码

实现一个类似的装饰器

现在你已经知道了「函数名赋值」和「高阶函数」,有了这两个基础,我们就可以尝试实现一个类似的装饰器。

def status(func):
print('慌的一逼!')
return func
def name():
print('我是梅西!')
temp = status(name)
temp()

输出结果:

慌的一逼! 我是梅西!

在这个例子中我们定义了一个 status 函数,status 接收一个函数名然后直接返回该函数名。这样我们实现了不修改原函数 name,并且添加了一个新功能的需求。但是这里有个缺陷就是函数的调用方式改变了。即不是原本的 name,而是 temp。

要解决这个问题很简单,相信 a = a*3 这样的表达式大家都见过,那么上述代码中的 temp = status(name) 同样可以修改为 name = status(name),这样我们就完美的解决了问题:既添加新功能又没有修改原函数和其调用方式。修改后的代码如下:

def status(func):
print('慌的一逼!')
return func
def name():
print('我是梅西!')
name = status(name)
name()

但这样的代码却有个不便之处,即每次使用这样的装饰器,我们都要写类似 name = status(name) 的代码。程序员都是懒的,所以才有那么多高级的语法。在 python 中为了简化这种情况,提供了一个语法糖 @,在每个被装饰的函数上方使用这个语法糖就可以省掉这一句代码 name = status(name),最后的代码如下:

def status(func):
print('慌的一逼!')
return func
@status
def name():
print('我是梅西!')
name()

这样我们就弄清楚了装饰器的工作原理:

  • 1 写一个高阶函数,即参数是函数,返回的也是函数。

  • 2 在利用语法糖@,简化赋值操作。

但是对比开头的例子,还是有些不一样。在开始的例子中,我们还实现了一个 rooftop_status 函数,来判断下当前的天台状是否人满。但是我们现在是直接返回了函数名,这样函数调用后我们就没办法做任何事情。梅西和德国慌了,我们也慌了,各个都要天台见,但在这之前我们也要考虑下天台的情况。

为了能判断天台的情况,所以此时我们需要在嵌套一层函数,将实现额外功能的部分写在内层函数中,然后将这个内层函数返回即可。这也是为什么装饰器都是嵌套函数的原因。

另外,开篇的例子并没有返回值,也没有参数,要对既有参数又有返回值的函数进行装饰的话,还需要进一步完善。 能够处理返回值的装饰器:

def guess_win(func):
def rooftop_status():
result = func()
print('天台已满,请排队!')
return result
return rooftop_status
@guess_win
def german_team():
print('德国必胜!')
return '赢了会所嫩模!输了下海干活!'
x = german_team()
print(x)

输出结果:

德国必胜! 天台已满,请排队! 赢了会所嫩模!输了下海干活!

能够处理参数的装饰器:

def guess_win(func):
def rooftop_status(*args, **kwargs):
result = func(*args, **kwargs)
print('天台已满,请排队!')
return result
return rooftop_status
@guess_win
def german_team(arg):
print('{}必胜!'.format(arg))
return '赢了会所嫩模!输了下海干活!'
x = german_team('德国')
y = german_team('西班牙')
print(x)

输出结果:

德国必胜! 天台已满,请排队! 西班牙必胜! 天台已满,请排队! 赢了会所嫩模!输了下海干活!

总结

装饰器的本质是函数,其参数是另一个函数(被装饰的函数)。装饰器通常会额外处理被装饰的函数,然后把它返回,或者将其替换成另一个函数或可调用对象。行为良好的装饰器可以重用,以减少代码量。

对于这届的世界杯,我总结了下。不说了,天台等着我。

更多精彩文章将会在公众号:CoXie带你学编程 发布

欢迎订阅

利用世界杯,读懂 Python 装饰器的更多相关文章

  1. 学以致用三十六-----弄懂python装饰器

    看了海峰老师讲解的装饰器视频,讲解的非常棒.根据视频,记录笔记如下: 装饰器: 1.本质是函数,用def来定义.功能就是用来(装饰)其他函数,为其他函数添加附加功能 现有两个函数如下, def tes ...

  2. 一篇文章搞懂Python装饰器所有用法

    01. 装饰器语法糖 如果你接触 Python 有一段时间了的话,想必你对 @ 符号一定不陌生了,没错 @ 符号就是装饰器的语法糖. 它放在一个函数开始定义的地方,它就像一顶帽子一样戴在这个函数的头上 ...

  3. Python装饰器详解

    python中的装饰器是一个用得非常多的东西,我们可以把一些特定的方法.通用的方法写成一个个装饰器,这就为调用这些方法提供一个非常大的便利,如此提高我们代码的可读性以及简洁性,以及可扩展性. 在学习p ...

  4. python 装饰器 一篇就能讲清楚

    装饰器一直是我们学习python难以理解并且纠结的问题,想要弄明白装饰器,必须理解一下函数式编程概念,并且对python中函数调用语法中的特性有所了解,使用装饰器非常简单,但是写装饰器却很复杂.为了讲 ...

  5. 转发对python装饰器的理解

    [Python] 对 Python 装饰器的理解的一些心得分享出来给大家参考   原文  http://blog.csdn.net/sxw3718401/article/details/3951958 ...

  6. python 装饰器、递归原理、模块导入方式

    1.装饰器原理 def f1(arg): print '验证' arg() def func(): print ' #.将被调用函数封装到另外一个函数 func = f1(func) #.对原函数重新 ...

  7. Python装饰器,Python闭包

    可参考:https://www.cnblogs.com/lianyingteng/p/7743876.html suqare(5)等价于power(2)(5):cube(5)等价于power(3)(5 ...

  8. python 装饰器修改调整函数参数

    简单记录一下利用python装饰器来调整函数的方法.现在有个需求:参数line范围为1-16,要求把9-16的范围转化为1-8,即9对应1,10对应2,...,16对应8. 下面是例子: def fo ...

  9. python 装饰器学习(decorator)

    最近看到有个装饰器的例子,没看懂, #!/usr/bin/python class decorator(object): def __init__(self,f): print "initi ...

随机推荐

  1. Android星球效果实现

    在项目中看着这个旋转效果挺炫的,就抽取出来做个记录.主要是使用CarrouselLayout 稍微修改 CarrouselLayout代码Demo下载z地址:GitHub https://github ...

  2. OkHttp的封装和使用详解

    Github地址 compile 'cn.yuan.yu:library:1.0.2' 第一步:初始化我们的工具类 public class MyApplication extends Applica ...

  3. NoHttp封装--07 自定义异步任务框架

    MainActivity: public class MainActivity extends Activity implements View.OnClickListener { .... @Ove ...

  4. 解决VS2015单元测试“未能设置用于运行测试的执行上下文”问题

    VS的单元测试在进行测试时并不像普通Exe会为你提示xx文件未找到,而是类似下面这样: 测试名称: 部署文件到Linux测试全名: unittest::SmartDispatch::部署文件到Linu ...

  5. html之CSS样式学习笔记

    本文内容: 字体样式 文本样式 背景样式 尺寸样式 盒子模型 边框样式 边距样式 浮动布局 定位布局 [CSS3]伸缩布局 标签显示方式 列表样式 [CSS3]过渡样式 [CSS3]变换样式之2D变形 ...

  6. 品牌电脑硬盘损坏后,使用MediaCreationTool从微软官方下载正版Windows到USB做安装盘

    最近我的一台台式机电脑的硬盘损坏了.一开始是速度逐渐变慢,后来慢得难以忍受,有时半天无响应.查看 Windows event ,发现有 id 为 7 的磁盘报错.使用 Windows 8.1 家庭版自 ...

  7. mac 全角/半角标点符号切换

    快捷键:option+shift+H 背景是这样的,前段时间sublimeText新装了HTML/CSS/JS Prittify,JS代码格式化的快捷键是:command+shift+H. 记性有点差 ...

  8. L2-024. 部落

    在一个社区里,每个人都有自己的小圈子,还可能同时属于很多不同的朋友圈.我们认为朋友的朋友都算在一个部落里,于是要请你统计一下,在一个给定社区中,到底有多少个互不相交的部落?并且检查任意两个人是否属于同 ...

  9. 漫说996icu黑名单

    以实际行动声援996icu项目. https://github.com/996icu/996.ICU/blob/master/blacklist/blacklist.md 996公司黑名单,京东,华为 ...

  10. mssql sqlserver 模拟for循环的写法

    转自:http://www.maomao365.com/?p=6567 摘要: 下文讲述sql脚本模拟for循环的写法,如下所示: /* for样例 for('初始值','条件','执行后自增') 通 ...