python装饰器详解之语法
引言
装饰器是python语言中一个非常好的特色,它可以让程序变的更加优雅。装饰器的主要作用还是为了提高代码可扩展性、复用率等,让代码变的简单,如同KISS原则:Keep it Simple, Stupid。装饰器对于python初学者来说确实有些难度,博主一开始学的时候也有很多疑惑:一开始是觉的语法有些难懂,后来语法懂了,也不知可以用到什么地方。本文将着重以简单的语言来讲述装饰器的语法,关于装饰器的应用将在另一篇博文中做详解《python装饰器详解之应用》。
装饰器分类
python装饰器可分为函数装饰器
和类装饰器
,根据有无参数又可以分为有参装饰器
和无参装饰器
。如下:
接下来将对以上四种装饰器的基本语法进行详细介绍。
函数装饰器
闭包
函数装饰器其实是基于闭包实现的,何为闭包, 百度百科这样说:闭包就是能够读取其他函数内部变量的函数。缩句之后就是:闭包是函数。在python中能访问其他函数内部变量的函数,其实就是定义在函数内部的函数:
# 示例1
def outer():
info = 'I am outer'
def closure():
print(info)
closure()
outer()
'''
函数输出:I am outer
'''
代码示例1中 closure() 函数就是一个闭包,当然也许这样说还不够严谨,因为这里有个问题是 closure() 目前只能在 outer() 函数内部调用,这样的闭包好像并没有太大的用处,也不能称为闭包。对此我们做了如下改进:
# 示例2
def outer():
info = 'I am outer'
def closure():
print(info)
return closure
f = outer()
f() # 此处即调用的 closure()
'''
函数输出:I am outer
'''
改进之后的代码可以在外部调用 closure() 函数,此时的 closure() 才是真正的闭包,关于闭包的语法及应用此处不做赘述,只做基础知识铺垫。
无参函数装饰器
装饰器本质上是闭包的一个应用,顾名思义它可以起到装饰作用,举例如下:
# 示例3
def decorator(func):
info = 'I am decorator'
def wrapper():
print(info)
print("before work")
func()
print("after work")
return wrapper
def work():
print("do work")
work = decorator(work) # 手动绑定装饰器
work()
'''
执行后输出:
I am decorator
before work
do work
after work
'''
示例3所示代码展示了一个装饰器以及其应用,decorator() 是一个函数装饰器,work() 函数记录了我们实际的业务逻辑。示例3中的重点是手动绑定装饰器的一行
work = decorator(work)
python中一切皆对象,函数名也是一个对象变量,可以像普通变量一样操作,在手动绑定装饰器之后,work
变量实际指向了 wrapper() 函数,wrapper() 函数中的func
变量则指向原来的work() 函数,当我们执行work() 的时候,实际上执行了 wrapper(),而wrapper() 通过func 变量调用了原work() 函数。我们可以通过函数的__name__
属性来验证我们的这一说法:
# 为了节省篇幅此处不贴全部代码,具体如下
# wrapper() 函数中增加:
print(func.__name__)
# 程序最后增加
print(work.__name__)
至此,我们基本可以明白,函数装饰器的基本原理。通过以上代码我们也能看出装饰器的作用,装饰器实际上是在不修改原函数的情况下,扩展了函数的功能。所以装饰器提高了python语言的灵活性,对于decorator() 来讲它重用了 work() 函数实现了一个新功能,提高了重用性,由此看来,装饰器的作用还是十分明显的。
@语法糖
在示例3我们通过一条语句:
work = decorator(work)
来手动绑定了原函数和装饰器,按说这样已经可以正常工作了,但是致力于提高编程效率的python并不满足于此,于是有了@
语法糖:
# 示例4
def decorator(func):
info = 'I am decorator'
def wrapper():
print(info)
print("before work")
func()
print("after work")
return wrapper
# 自动绑定装饰器
@decorator
def work():
print("do work")
work()
'''
执行后输出:
I am decorator
before work
do work
after work
'''
@decorator 等价于 work = decorator(work)
有了@
语法糖,python程序更加简洁,读到此处想必大家再看到@
符号的时候应该不会感到头大了。
有参函数装饰器
示例3中的函数装饰器实际上是没有参数的,这个参数是指的装饰器的参数,不是work()函数的参数,有参函数装饰器的定义会有所不同:
# 示例5
def outer(_info):
def decorator(func):
info = _info
def wrapper():
print(info)
print("before work")
func()
print("after work")
return wrapper
return decorator
# 自动绑定装饰器
@outer("hello")
def work():
print("do work")
work()
'''
执行后输出:
hello
before work
do work
after work
'''
有参装饰器实际上比无参装饰器多了一步操作,@outer("hello")
可以理解为:
f = outer("hello")
work = f(work)
读者可以使用此两句替换掉@outer("hello")
,最后的效果是相同的。
有参函数装饰器使得装饰器变的更加灵活,提高了装饰器本身的代码重用性。无参装饰器基本上是固定的,可以认为只能干一件事情;有参装饰器通过设置不同的参数,使得装饰器可以做更多的事情。无参装饰器在定义上更简单,所以并不存在有参可以取代无参这种说法。
被装饰函数的参数及返回值
看完上面的讲述,可能有人会想到work()函数也可能有参数,但是上面的示例都是无参的,那如果有参数或者有返回值要怎么办呢?好问题,我们接下来讨论,被装饰函数的参数和返回值问题。其实这个很简单,如下示例:
# 示例6
def outer(_info):
def decorator(func):
info = _info
def wrapper(name):
print(info)
print("before work")
m=func(name)
print("after work")
return m
return wrapper
return decorator
# 自动绑定装饰器
@outer("hello")
def work(name):
return name+": do work"
print(work("Tom") )
'''
执行后输出:
hello
before work
after work
Tom: do work
'''
函数的参数和返回值都是通过闭包wrapper()
传递的,这一点可很好理解,实际发生的事情如下:
work("Tom")
实际调用的是 wrapper("Tom")
,wrapper()把参数传递给work(),work()返回的值最后通过wrapper() 返回,因此这里有几个注意事项:
- 被装饰函数有参数和返回值时,wrapper() 函数要做相应的匹配,否则可能会出现问题。
- wrapper() 的参数不一定要和work() 的参数一致,但一定要满足work() 对参数的要求
- work() 有返回值时,wrapper() 不一定要有返回值,但如果需要用到 work() 的返回值时,必须通过 wrapper() 传递出来,否则无法使用。
多重装饰器
以上内容都是单个装饰器的情形,但是一个函数可以被多个装饰器修饰,在示例6的基础上增加一个装饰器,如下:
# 示例7
def outer(_info):
def decorator(func):
info = _info
def wrapper1(name):
print(info)
print("before work")
m=func(name)
print("after work")
return m
return wrapper1
return decorator
def decorator2(func):
def wrapper2(name):
print("decorator 2")
return func(name)
return wrapper2
# 自动绑定装饰器
@outer("hello")
@decorator2
def work(name):
return name+": do work"
print(work("Tom") )
'''
执行后输出:
hello
before work
decorator 2
after work
Tom: do work
'''
多个装饰器时,装饰器的绑定和执行都是有顺序的。
绑定顺序:自下而上
执行顺序:自上而下
@outer("hello")
@decorator2
#等价于以下三行代码:
work = decorator2(work) # 此时 work变量指向 wrapper2,其中的func 指向 原work
f = outer("hello")
work = f(work) # 此时 work 指向 wrapper1,其中的func 指向wrapper2
通过上面的绑定,形成了一个函数链为:wrapper1 -> wrapper2 -> work,最后的执行顺序为:
wrapper1()
wrapper2()
work()
# 执行完毕后再一层层的返回到 wrapper1(),最后的返回值就是 原work() 的返回值
因为原work()
是有参数和返回值的,所以两个装饰器中的闭包(wrapper1()、wrapper2())都要提供必要的参数,并且将func()
执行的返回值返回,否则work() 就无法拿到参数、或者无法将返回值返回到最外层,这是在使用装饰器时需要额外注意的,很容易被遗忘。
总结
此处对函数装饰器进行一个语法总结,辅助记忆。
- 无参函数装饰器,两层函数嵌套,基本结构如下:
def decorator(func):
# user code
def wrapper(*args, **kw):
# user code
rtn = func(*args, **kw)
# user code
return wrapper
- 有参函数装饰器,三层函数嵌套,
在无参函数装饰器的基础上外加一层函数
,结构如下:
def para_decorator(*args):
# user code
def decorator(func):
#user code
def wrapper(*args, **kw):
# user code
rtn = func(*args, **kw)
# user code
return wrapper
return decorator
类装饰器
函数装饰器已经能解决大部分问题,类装饰器可以看做对函数装饰器的强化。毕竟类是面向对象的概念,函数时面向过程的概念,所以类装饰器可以解决一些函数装饰器做起来比较困难的事情。对于类装饰器的学习我们可以类比函数装饰器的语法去理解,因为类装饰器是也是通过其成员函数来实现的相关功能。
python类简介
__init__()和__call__() 是python类的两个成员函数(但不是必须被实现的)。__init__()可以理解为类的构造函数,在新建一个对象时__init__()会首先被调用;__call__()可以理解为对象函数化,可以让一个类对象如同函数一样被调用。具体如示例7所示:
# 示例7
class Test:
def __init__(self):
print('construct')
def __call__(self):
print('call()')
print('init: ')
t = Test()
print('call: ')
t()
'''
输出:
init:
construct
call:
call()
'''
t = Test()
调用了__init__() 函数,t()
则调用了__call__() 函数。类装饰器主要通过这两个函数来实现,所以类装饰器的绑定与调用也回到了函数的层面,所以我们说可以类比函数装饰器来理解类装饰器。
无参类装饰器
同样的道理我们通过手动绑定的方式来理解,最后使用@
语法糖:
# 示例8
class Decorator:
def __init__(self, func):
self.func = func
self.info = 'hello'
def __call__(self, name):
print(self.info)
print("before work")
msg = self.func(name)
print("after work")
return msg
def work(name):
return name+": do work"
work = Decorator(work) # 手动绑定类装饰器
rtn = work('Tom') # 调用 work
print(rtn )
'''
执行后输出:
hello
before work
after work
Tom: do work
'''
在手动绑定装饰器之后work = Decorator(work)
,work实际上成了一个Decorator对象,Decrator的成员变量self.func则指向原work函数;最后调用 work()
实际上调用了Decrator的成员函数__call__(),进而调用了原work函数。总结如下:
- 通过 __init__() 绑定装饰函数
work
指向 类对象, __call__()中的self.func
指向原work()
示例8中我们的work()函数是有参数和返回值的,这一点读者需要留意。
增加@
语法糖代码:
# 示例9
class Decorator:
def __init__(self, func):
self.func = func
self.info = 'hello'
def __call__(self, name):
print(self.info)
print("before work")
msg = self.func(name)
print("after work")
return msg
@Decorator
def work(name):
return name+": do work"
#work = Decorator(work) # 手动绑定类装饰器
rtn = work('Tom') # 调用 work
print(rtn )
'''
执行后输出:
hello
before work
after work
Tom: do work
'''
示例9使用了@
语法糖,使的代码更加精简。
有参类装饰器
类比函数装饰器,类装饰器也存在有参类装饰器,其结构与无参类装饰器略有不同:
# 示例10
class Decorator:
def __init__(self, _info):
self.info = _info
def __call__(self, func):
def wrapper(name):
print(self.info)
print("before work")
msg = func(name)
print("after work")
return msg
return wrapper
@Decorator("hello 2")
def work(name):
return name+": do work"
print(work('Tom'))
'''
执行输出:
hello 2
before work
after work
Tom: do work
'''
此处我们直接给出了@
语法糖版本的代码,其中@Decorator("hello 2")
写成手动绑定代码时如下:
f = Decorator("hello 2") # 调用__init__() 传递装饰器参数
work = f(work) # 调用__call__()绑定wrapper()
总结:
- 通过 __init__() 传递装饰器参数
- 通过 __call__() 绑定装饰器
work
指向 wrapper() ,wrapper()中的func
指向原work()
两种类装饰器的对比
为了方便记忆我们对两种类装饰器进行对比:
无参类装饰器 | 有参类装饰器 | |
---|---|---|
__init__() | 绑定装饰器 | 传递装饰器参数 |
__call__() | 实现装饰功能,调用原函数 | 绑定装饰器 |
wrapper()闭包 | 无 | 实现装饰功能,调用原函数 |
通过表格对比的方式,我们更容易记忆类装饰器的语法结构。在实际的开发工作中会更高效,毕竟不容易用到的知识是最容易遗忘的。
类装饰器的继承
类装饰器作为一个类,它具有类的所有特性,它可以被继承,继承它的派生类也可以作为类装饰器。
# 示例11
class Decorator:
def __init__(self, _info):
self.info = _info
self._id = "123"
def __call__(self, func):
def wrapper(name):
print(self.info)
print('id:'+self._id )
print("before work")
msg = func(name)
print("after work")
return msg
return wrapper
class SubDec(Decorator):
def __init__(self, _info):
super().__init__(_info)
self._id = "456"
@Decorator("hello 2")
def work(name):
return name+": do work"
@SubDec("special")
def specialWork(name):
return name+": do work"
print(work('Tom'))
print('========')
print(specialWork('Tom'))
'''
执行输出:
hello 2
id:123
before work
after work
Tom: do work
========
special
id:456
before work
after work
Tom: do work
'''
示例11中给出了两个类装饰器,他们是继承的关系。类Decorator中定义了成员变量_id ,这个变量在派生类SubDec重新进行赋值,我们可以看到父类和派生类输出的_id 值是不同的。所以,我们可以通过对类进行继承来实现不同的功能。
其他
@wraps()
装饰器还有一个作用,他会修改被装饰函数的属性,我们以函数名为例(函数的__name__属性)。
# 示例12
def work():
print('function name: '+work.__name__)
work()
'''
执行输出:
function name: work
'''
被装饰之后,函数属性发生了变化:
# 示例13
def decorator(func):
def wrapper():
func()
return wrapper
@decorator
def work():
print('function name: '+work.__name__)
work()
'''
执行输出:
function name: wrapper
'''
我们看到被装饰之后函数名变成 了装饰器中闭包的函数名,有些时候我们不需要使用函数属性,这种变化也无伤大雅,但是当我们需要使用函数属性的时候,这里就会出现问题,此时我们可以使用wraps()
修改此闭包,可以解决这个问题:
# 示例14
from functools import wraps
def decorator(func):
@wraps(func)
def wrapper():
func()
return wrapper
@decorator
def work():
print('function name: '+work.__name__)
work()
'''
执行输出:
function name: work
'''
结束语
本文主要讲解了python装饰器的语法,从装饰器的底层原理详细说明了装饰器的工作方式,其中涉及的代码还希望读者可以认真分析,在上面做一些调整试验,这样收获会更加深刻。此篇文章着重讲述语法,很多初学者看过也许能明白是怎么用的,但可能还是疑惑于装饰器应该用于什么场景,关于装饰器的应用会单独拉一篇文章讲解。
码字不易,如果喜欢可以点赞 关注,博主会将更多有意思的东西分享出来。