Python装饰器

装饰器说明

器指的工具(只要是工具,就应该想到函数),装饰指的是为被装饰对象添加新功能,需要注意的是:项目一旦上线之后,就应该遵循开发封闭的原则。开发封闭指的是对修改函数内的源代码和调用方式是封闭的,对功能的扩展是开放的。

看起来有点矛盾,在这样的要求下,必须找到一种解决方案,能够在不修改一个函数源代码以及调用方式的前提下,为其添加新功能,这就用到了装饰器,它能够在不修改被装饰对象源代码与调用方式的前提下,为被装饰对象添加新功能。

简单理解,就是如果在不修改函数的情况下,怎么给函数添加新功能。我们可以很轻松地想到,将这个函数套在另外一个函数中(这个函数就是装饰器)就可以了。当要给函数添加新功能时,在不修改原函数的情况下,新添加的功能可以在新函数中实现。

比如函数fun1()实现功能ast1,现在要求fun1()函数实现ast1和ast2功能,但是要求不能修改函数fun1()的源代码,这个时候可以新建一个函数fun2(),函数fun2()以函数fun1()作为参数,同时在fun2()中实现ast2功能,这样就既能实现功能ast1,ast2,同时又不用修改fun1()函数。

def fun1():
	ast1

def fun2(fun1):
	fun1()
	ast2

那这里就有点疑惑了,这样在调用函数的时候,不就是调用fun2()了吗?fun1()没有被显式地调用啊。为了解决这个问题,就用到了我们说的装饰器。现在可以先简单理解,装饰器的作用是为原函数添加新功能,同时又不修改原函数,同时又能做到显式调用原函数而不是调用装饰器。

装饰器分为无参装饰器和有参装饰器,下面我们分别说明一下无参装饰器和有参装饰器。

1、无参装饰器

无参装饰器指装饰器本身没有参数。

先抛弃装饰器,假设现在要给index函数添加一个统计时间的功能,index函数原型如下:

import time  # 负责计时的模块

def index():
    time.sleep(3)  # 停止3秒
    print('Wlecome to index page')

index()

方法一:使用time.time()函数

import time  # 负责计时的模块

def index():
    time.sleep(3)  # 停止3秒
    print('Wlecome to index page')

start_time = time.time()
index()
end_time = time.time()
print('running time is {} s'.format(end_time - start_time))

这种方法简单地实现了对index()函数的计算,但是如果需要对多个函数进行计时,这种办法的缺点就暴露了,我们需要为每个函数都调用time.time(),这样会造成代码大量重复。

因此,我们可以将计时功能通过集成为一个函数,函数的输入参数为我们要进行计时的函数index(),这就是我们要介绍的方法二。

方法二:创建计时函数实现计时功能

def index():
    time.sleep(3)  # 停止3秒
    print('Wlecome to index page')

def wrapper(func):
    start_time = time.time()
    func()  # index()
    end_time = time.time()
    print('running time is {} s'.format(end_time - start_time))
    
wrapper(index)

代码中wrapper()函数实现了计时功能,wrapper()函数有一个输入参数func,func是需要被计时的函数,这里我们使用index()函数,这是通过函数调用的方式实现计时,当多个函数需要被计时时,可以直接通过调用函数实现。

但是这里又出现了一个问题,我们是希望在index()中添加计时功能,可以直接调用index()实现计时,但是方法二中需要显式调用wrapper()才能实现计时,这显然不是我们的需求。所以还需要改进,在方法三种我们使用闭包函数来实现使用index()就能进行计时。

方法三:使用闭包函数避免显式调用wrapper()函数

import time  # 负责计时的模块

def index():
    time.sleep(3)  # 停止3秒
    print('Wlecome to index page')

def timer(func):  # 闭包函数
    # func = index
    def wrapper():  # 注意wrapper不需要传入参数
        start_time = time.time()
        func()  # index()
        end_time = time.time()
        print('running time is {} s'.format(end_time - start_time))
    return wrapper

index = timer(index)  # 赋值给index覆盖原来的index,index=wrapper
index()  # wrapper()

这种情况下,我们就能通过调用index()函数实现计时功能,同时index()源码也没有被修改。outer()就是一个装饰器,也就是说我们一步步得到了装饰器,装饰器在不修改原函数源代码的情况下,给原函数添加了新功能。当然了,这不是最终的装饰器,只能说这个方法方法基本实现了装饰器的功能。

也就是说,装饰器其实是一个函数,这个函数的作用是新定义一个函数,并且将这个函数返回。

但是现在这个方法有一个问题,如果我们需要被装饰的函数有返回值,上面这个方法得到的函数是没有返回值的,因此我们还需要对这个方法进行修改,使其可以返回值。

通过上面的代码我们可以知道,index实际上变为wrapper函数,如果原来的index()函数拥有返回值,那么只需要在wrapper()函数中保存index()的返回值,并在wrapper()函数最后返回该值就可以了。

具体的实现可以看方法四。

方法四:解决没有返回值问题

import time  # 负责计时的模块

def index():
    time.sleep(3)  # 停止3秒
    print('Wlecome to index page')
    return 1  # index()返回值

def outer(func):
    # func = index
    def wrapper():
        start_time = time.time()
        res = func()  # index()  # wrapper保存index的返回值
        end_time = time.time()
        print('running time is {} s'.format(end_time - start_time))
        return res  # 返回index的返回值
    return wrapper

index = outer(index)  # 赋值给index覆盖原来的index,index=wrapper
res = index()  # wrapper()
print(res)

通过上面的方法我们就实现了无法返回返回值的问题。

现在我们实现了返回值的问题,但是还有另外一个问题,如果index()函数有参数,比如index(N),这种情况下wrapper()怎么识别index()函数是否有参数呢?

实现也很简单,index最后其实就是wrapper函数,只要将wrapper的参数和index的参数进行关联就可以了,具体实现参数传递可以看方法五。

方法五:解决参数传递问题

import time  # 负责计时的模块

def index(N):  # 参数N是停止时间参数
    time.sleep(N)  # 停止N秒
    print('Wlecome to index page')
    return 1

def outer(func):
    # func = index
    def wrapper(*args,**kwargs):  # *args和**kwargs用于接收index的参数
        start_time = time.time()
        res = func(*args,**kwargs)  # *args和**kwargs和wrapper的参数是关联的
        end_time = time.time()
        print('running time is {} s'.format(end_time - start_time))
        return res
    return wrapper

index = outer(index)  # 赋值给index覆盖原来的index,index=wrapper
res = index(4)  # wrapper(N),需要传入参数
print(res)

方法中的*args和**kwargs的功能是获取参数,参数的个数不限定,wrapper获取参数后,会传递给index执行,这样就解决了参数传递问题。

装饰器模板

这样从方法一到方法五,我们从零实现了无参装饰器并解决了装饰器中的问题,使其得到完善。现在我们可以得到无参装饰器的模板:

def outer(func):
	def inner(*args,**kwargs):
		"""
		这里写装饰器逻辑,添加函数功能
		:param args:任意位置参数
		:param kwargs:任意关键参数
		:return:一个函数对象
		res = func(*args,**kwargs)  # 传递参数和返回值
		return res

通过以上方法我们得到了无参装饰器的模板,在我们实际使用装饰器时,我们会使用到装饰器语法糖,下面我们介绍一下什么是装饰器语法糖。

装饰器语法糖

装饰器语法糖就是在被装饰对象正上方单独一行添加@timer()。

import time

# 装饰器也是一个函数,使用函数必须先定义,所以装饰器放在最上方
def timer(func):
	def wrapper(*args,**kwargs):
		start_time = time.time()
		res = func(*args,**kwargs)
		stop_time = time.time()
		print(end_time - begin_time)
		return res
	return wrapper

# 装饰器的作用是定义一个新函数,并返回这个新函数

@timer  # 在被装饰对象正上方单独一行添加,相当于执行index=timer(index)
def index(N):
	time.sleep(N)
	print('Welcome to index page')
	return 1

res = index(3)
print(res)

2、有参装饰器

装饰器本身是一个函数,函数就可以有参数,有参装饰器就是装饰器本身有一个参数。我们现在看一下有参装饰器和无参装饰器的区别:

import time

# 装饰器也是一个函数,使用函数必须先定义,所以装饰器放在最上方
def decorator(*args):
    print('This is a',*args)
    def timer(func):
        def wrapper(*args,**kwargs):
            start_time = time.time()
            res = func(*args,**kwargs)
            stop_time = time.time()
            print(start_time - stop_time)
            return res
        return wrapper
    return timer

# 装饰器的作用是定义一个新函数,并返回这个新函数

@decorator('decorator')  # 在被装饰对象正上方单独一行添加,相当于执行index=timer(index)
def index(N):
    time.sleep(N)
    print('Welcome to index page')
    return 1

res = index(4)
print(res)

对于原来的无参装饰器,有参装饰器在timer()函数外再套用了一层函数,这样在@decorator(‘decorator’) 的时候,实际得到的是@timer(func),因为有参装饰器在传入参数’decorator’之后返回的是一个函数timer。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值