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() 就无法拿到参数、或者无法将返回值返回到最外层,这是在使用装饰器时需要额外注意的,很容易被遗忘。

总结

此处对函数装饰器进行一个语法总结,辅助记忆。

  1. 无参函数装饰器,两层函数嵌套,基本结构如下:
def decorator(func):
	# user code
	def wrapper(*args, **kw):
		# user code
		rtn = func(*args, **kw)
		# user code
	return wrapper
  1. 有参函数装饰器,三层函数嵌套,在无参函数装饰器的基础上外加一层函数,结构如下:
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装饰器的语法,从装饰器的底层原理详细说明了装饰器的工作方式,其中涉及的代码还希望读者可以认真分析,在上面做一些调整试验,这样收获会更加深刻。此篇文章着重讲述语法,很多初学者看过也许能明白是怎么用的,但可能还是疑惑于装饰器应该用于什么场景,关于装饰器的应用会单独拉一篇文章讲解。

码字不易,如果喜欢可以点赞 关注,博主会将更多有意思的东西分享出来。

  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值