Python 装饰器是在面试过程高频被问到的问题,装饰器也是一个很是好用的特性,
熟练掌握装饰器会让你的编程思路更加宽广,程序也更加 pythonic。python
今天就结合最近的世界杯带你们理解下装饰器。程序员
6 月 17 日德国战墨西哥,小痴虽然是一个伪球迷,但每一年的世界杯仍是会了解下。而德国是上届的冠军,又是这届夺冠热门。德意志战车在 32 年间小组赛就没有输过!卧槽!虽然小痴不多赌球,但此次德国如此强大,确定会赢吧。搏一搏单车变摩托!随后小痴买了德国队赢。内心想着此次确定稳了!赢了会所嫩模!小痴连比赛都不看,美滋滋的敲着代码。面试
而后比赛结果倒是德国爆冷 0:1 输给墨西哥队,德国队输了比赛,小痴也下海干活。只是此时的天台有点挤,风还有大。编程
小痴含泪的写下了下面的代码:bash
def guess_win(func):
def rooftop_status():
result = func()
print('天台已满,请排队!')
return result
return rooftop_status
@guess_win
def german_team():
print('德国必胜!')
复制代码
输出结果:函数
德国必胜!
天台已满,请排队!
复制代码
首先咱们先来了解下什么是装饰器,严格来讲,装饰器只是语法糖,装饰器是可调用的对象,能够像常规的可调用对象那样调用,特殊的地方是装饰器的参数是一个函数。ui
装饰器的存在是为了适用两个场景,一个是加强被装饰函数的行为,另外一个是代码重用。spa
好比在上面的例子中咱们在压德国队赢的时候,本来的 german_team() 函数只是输出德国必胜,但在使用装饰器(guess_win)后,它的功能多了一项:输出「天台已满,请排队!」。这就是一个简单的装饰器,实现了「加强被装饰函数的行为」。3d
一个良好的装饰器必需要遵照两个原则:code
1 不能修改被装饰函数的代码
2 不能修改被装饰函数的调用方式
这里并不难以理解,在如今的生产环境中,不少代码是不能轻易的改写,由于这样有可能发送意想不到的影响。还有一点就是咱们在看大神的代码,咱们根本不懂如何改写。同时你也不能修改调用方式,由于你并不知道有在一个项目中,有多少处应用了此函数。
若是你想要很好的理解装饰器,那下面的两个内容须要你先有所认知。
1 函数名能够赋值给变量
2 高阶函数
咱们来看下这个例子:
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)
复制代码
输出结果:
德国必胜!
天台已满,请排队!
西班牙必胜!
天台已满,请排队!
赢了会所嫩模!输了下海干活!
复制代码
装饰器的本质是函数,其参数是另外一个函数(被装饰的函数)。装饰器一般会额外处理被装饰的函数,而后把它返回,或者将其替换成另外一个函数或可调用对象。行为良好的装饰器能够重用,以减小代码量。
对于这届的世界杯,我总结了下。
本文首发与公众号「痴海」,后台回复「1024」,领取 2018 最新 python 教程。