二十一、深入Python强大的装饰器
「@Author: Runsen」
最近有同学在问关于Python中装饰器的问题 , 说不太理解装饰器的装饰过程 。
那么在下面Runsen来给大家深入讲解一下装饰器的整个实现过程的 。
闭包想要理解Python中的装饰器 , 不得不先理解闭包(closure)这一概念 。
闭包就应该想起了嵌套函数 , 也可以将闭包理解为一种特殊的函数 , 这种函数由两个函数的嵌套组成 , 外函数和内函数 。
def 外层函数(参数):def 内层函数():print("内层函数执行", 参数)return 内层函数内层函数的引用 = 外层函数("传入参数")内层函数的引用()
在一个外函数中定义了一个内函数 , 内函数里运用了外函数的临时变量 , 并且外函数的返回值是内函数的引用 。 这样就构成了一个闭包 。
下面举一个具体的闭包函数的实例 , 代码如下 。
# outer是外部函数def outer(a):# inner是内函数def inner( b ):#在内函数中 用到了外函数的临时变量print(a+b)# 外函数的返回值是内函数的引用return innerret = outer(5) #ret = innerret(10) #15 ret 存了外函数的返回值 , 也就是inner函数的引用 , 这里相当于执行inner函数
装饰器装饰器 , 顾名思义 , 就是用来“装饰”的 。 比如@Runsen就是一个装饰器 , 其中"Runsen"是你的装饰器的名字 。 它能装饰的东西有:函数、类 。 装饰器一般在函数、类的上面用@符号定义 。
装饰器本质上是一个Python函数(一定有参数) , 如果严格来说 , 装饰器只是语法糖 , 也可以将装饰器叫做一种特殊的闭包 。
装饰器是可调用的对象 , 可以像常规的可调用对象那样调用 , 特殊的地方是装饰器的参数是一个函数名 。
下面就是最简单的装饰器 , 代码来自Python3官方文档 。
def warp(obj):return obj@warp# 等价于 foo = warp(foo)def foo():print('hello decorator!') foo()# => hello decorator!
上面使用了装饰器的代码 , 其实我们可以通过其它方式达到相同的效果 , 具体见下 。
def foo():print('hello decorator!') foo = warp(foo)foo()# => hello decorator!
嵌套函数的装饰器在上面代码中装饰器都只是一个普通的函数 , 如果该函数是嵌套函数 , 那么函数传入的参数 , 在内部函数依然可以使用 。
先看两段代码 , 在这里my_decorator就是一个装饰器 。
def my_decorator(func):def wrapper():print('wrapper of decorator')func()return wrapperdef greet():print('hello world')greet = my_decorator(greet)greet()# 输出wrapper of decoratorhello world
my_decorator函数传入greet函数名方法 , 中间有一个wrapper内函数方法 ,而return wrapper说明要执行wrapper内函数 , wrapper内函数 , 于是执行greet函数名方法 。
其实 , greet = my_decorator(greet)这个代码可以用装饰器来替代 , 在greet上面加一个@my_decorator , 然后直接执行greet() , 最终输出一样 。
def my_decorator(func):def wrapper():print('wrapper of decorator')func()return wrapper@my_decoratordef greet():print('hello world')greet()wrapper of decoratorhello world
装饰器就是继承了 my_decorator函数 , 因此先调用my_decorator中的wrapper打印出 wrapper of decorator , 然后func()被调用 , 传入的参数是greet , 因此指的就是greet(),所以在打印出hello world
带参数嵌套函数的装饰器有时候嵌套函数需要传入参数到内部函数 , 这时候用*args, **kwargs接受就可以了 。 *args接收元组 , **kwargs接受字典 。
下面 , 我们来看一个例子 。
# repeat重复输出 , num指重复输出次数def repeat(num):def my_decorator(func):def wrapper(*args, **kwargs):for i in range(num):print('wrapper of decorator')func(*args, **kwargs)return wrapperreturn my_decorator@repeat(4)def greet(message):print(message)greet('hello world')# 输出:wrapper of decoratorhello worldwrapper of decoratorhello worldwrapper of decoratorhello worldwrapper of decoratorhello world
上面代码的意思:@repeat(4)将会执行greet(4) , 由于存在return my_decorator , 所以下一步执行my_decorator(greet).由于又存在return wrapper 。 所以下一步将会执行wrapper(*args, **kwargs) 。 这里的*args, **kwargs指的是hello world字符串 。 因此最终打印四次wrapper of decorator和hello world 。
但是自定义参数的装饰器将改变函数本身的元信息 , 即函数不再是本身的函数
greet.__name__## 输出'wrapper'
这时 , 需要使用内置模块functools.wrap会保留原函数的元信息 。
import functoolsdef repeat(num):def my_decorator(func):@functools.wraps(func)def wrapper(*args, **kwargs):for i in range(num):print('wrapper of decorator')func(*args, **kwargs)return wrapperreturn my_decorator@repeat(4)def greet(message):print(message)greet.__name__# 输出 不是wrapper'greet'
- 空调|让格力、海尔都担忧,中国取暖“新潮物”强势来袭,空调将成闲置品?
- 占营收|华为值多少钱
- 俄罗斯手机市场|被三星、小米击败,华为手机在俄罗斯排名跌至第三!
- 页面|如何简单、快速制作流程图?上班族的画图技巧get
- 操盘|中兴统一操盘中兴、努比亚、红魔三大品牌
- 印度|拒绝华为后,印度、英国斥资数十亿求助日本
- 华为|台积电、高通、华为、小米接连宣布!美科技界炸锅:怎么会这样!
- 拍照|iPhone12还没捂热13就曝光了,屏幕、信号、拍照均有升级!
- 路由器|家里无线网经常断网、网速慢怎么办?教你几个小窍门,轻松解决
- 一图看懂!数字日照、新型智慧城市这样建(上篇)|政策解读 | 新型