专栏文章索引:Python
原文章:装饰器函数基础_装饰函数-CSDN博客
目录
1. 学习装饰器的基础
2.最简单的装饰器
3.闭包函数装饰器
4.装饰器将传入的函数中的值大写
5. 装饰器的好处
6. 多个装饰器的执行顺序
7. 装饰器传递参数
8. 结语
1. 学习装饰器的基础
学习装饰器之前, 先需要明白函数的一些特性 :
- 函数也是对象, 可以将函数分配给变量, 也可以作为参数传递给另外一个函数,甚至可以储存在数据结构中。
- 函数内部也可以定义函数, 我们常常称这种函数为闭包函数, 其可以使用父函数的局部状态(变量)而非全局状态(变量)
总之 ,python中一切皆对象, 不要把函数想的太高深。
概述:装饰器是可调用的, 将可调用的对象作为输入传入装饰器函数并返回另一个可调用对象。
2.最简单的装饰器
def hello(func):return funcdef greet():return "Hello World"greet = hello(greet)
print(greet()) # Hello World
我们定义了一个hello()函数, 它接受一个参数func, 其实在这里的func接受的是一个函数的内存地址, 这个函数就是后面定义的greet(), 它返回一句话, 将greet作为参数传给hello函数, 并接受返回值,这里需要注意的是, 返回值func在接受了greet()函数的内存地址后, 如果直接打印, 打印的会是一串内存地址
def hello(func):return funcdef greet():return "Hello World"greet = hello(greet)
print(greet) # <function greet at 0x02B27AE0>
因为在python中, 定义一个函数,相当于在内存空间开辟一块用于存放函数内容的空间 ,也就是我们案例中传给hello函数的值greet其实就是一块内存空间, 将它传给了func,这个时候内存空间的指向发生了变化, 不在执行greet函数而是赋值给了func。
不过这样做确实麻烦, 既然是装饰器, 装饰二字何来?,而且多层的函数调用嵌套容易发生错误, 所以python引用了@语法糖的形式, 使用@语法会立即修饰该函数,给它穿上一套准备好的衣服。修改一下代码:
def hello(func):return func@hello
def greet():return "Hello World"print(greet())
我们来理一下程序行走的流程, 经过hello函数, 不会立即执行, 预留一片储存空间, 存放返回的func, 接下来走到了@hello这里, 实际上就是greet = hello(greet),将greet作为参数传入hello函数, 并保留返回的结果func, 等待调用, 这个时候greet地址已经在hello函数中了,就等着调用了 ,所以最后执行调用, 程序进入func内存空间, 调用func地址中的内容, 也就是调用greet函数。
说到这里可能还是有点迷, 下面在举一个难点的例子, 用到了函数嵌套,闭包函数, 这也是常见的装饰器函数:
3.闭包函数装饰器
import timedef timer(func):def wapper(*args, **kwargs):print("开始计时")start_time = time.time()func()print('执行吗??')end_time = time.time()print('耗时: ', end_time - start_time)return wapper@timer
def run():print('To do the job')time.sleep(2)print('End the job')
run()
咋一看挺复杂, 下面来理一理。
程序走到了@timer这里 ,实际上就是 run = timer(run), 在这里是将run作为参数传递给func, 相当于一个载体, 承载着run进入wrapper函数, 此时程序并不执行, 还没有最终的调用,只是返回一个wrapper地址,紧接着执行run函数,最后调用run(),注意:这时候直接执行run()函数, 也就是直接执行刚刚返回的wrapper内存地址,打印 开始计时, 这时候因为函数内部调用了func(),所以执行run函数,静止2秒, 打印最后一条数据, 其实就是func代替、等于run, 执行run函数里面的命令。
总结: 最终的函数调用时, 先进入装饰器函数, 顺次执行, 遇到传入的函数地址进行调用, 在依次执行
运行结果:
开始计时
To do the job
End the job
执行吗??
耗时: 2.0005037784576416
4.装饰器将传入的函数中的值大写
# 这是一个装饰器函数
def upperfunc(func):def wrapper():original_result = func() # 接受func()函数的返回值, 也就是要变为大写的值transform_result = original_result.upper()return transform_resultreturn wrapper@upperfunc
def greet():return "hello world"print(greet())# HELLO WORLD
同样的, 大体流程是 返回一个wrpper地址空间等待执行, 装饰器装饰greet函数,
使其中的返回值大写, 最后调用greet函数,首先执行装饰器函数中的地址空间, 在空间中将hello world大写, 并返回结果。
5. 装饰器的好处
说到这里该说一说装饰器的好处, 就像上面这个例子, 假设有一个你在程序中遇到一个问题, 需要将一些字符串大写, 而恰巧这些字符串数量不少, 一个一个调用upper方法是不是感觉头痛, 这时使用装饰器不啻为一个好的选择。
6. 多个装饰器的执行顺序
如果我们想要定义多个装饰器且将这些装饰器装饰于一个函数, 那么装饰器的执行顺序是必须要清楚的,下面定义两个装饰器, 分别添加一些HTML标签, 由此观察装饰器的执行顺序
# div 标签包围
def div_tags(func):def wrapper():return '<div>' + func() + '</div>'return wrapper# a 标签包围
def a_tags(func):def wrapper():return '<a>' + func() + '</a>'return wrapper@a_tags
@div_tags
def greet():return "hello world"print(greet())
结果:
<a><div>hello world</div></a>
从结果可以看出多个装饰器是从下向上执行的,
回到上面说的装饰器的好处, 我们来看一下不用装饰器执行是怎么调用的:
decorated_greet = div_tas(a_tags(greet))
print(decorated_greet())
执行结果是一样的, 这样写不仅容易发生错误, 而且多重的嵌套很难让人提起兴趣去阅读代码是怎样执行的。
7. 装饰器传递参数
上面讲的都是一些简单无参的函数, 下面来介绍一下有参数传递的装饰器函数 ,在这里就需要用到 * 操作符了, 它会接受一个或多个位置参数, 而** 操作符接受关键字参数
如果这里不懂的可以百度一下, 应该都有
# 装饰器传递参数
def Prinname(func):def wapper(*args, **kwargs):func(**kwargs)return wapper@Prinname
def run(name):print('姓名:', name)def main():run(name='bai')if __name__ == '__main__':main()
在这里闭包函数中有两个形参, 他们基本上可以接受任何传入的参数, 所有下面run函数传入了关键字参数,类似于key1=value1, key2=value2, 只不过定义了个函数入口, 从这里开始执行,大体原理没变。
结果:
姓名: bai
8. 结语
以上是装饰器函数的一些基本内容, 要尝试着敲一遍才能理解, 如果第一遍看不懂的话,不要慌, 因为装饰器,闭包函数,*操作符等算是python中的中高级内容了, 我也是学习了好多遍,虽然实际中从没用过,但是学学对于理解pyhton的特性还是很有帮助的, 最后, 希望这篇文章可以帮到你!