【摘要】 Python装饰器的学习笔记

1. 初识装饰器

接下来,我们通过一个例子来为大家讲解这个装饰器:

需求介绍:你现在xx科技有限公司的开发部分任职,领导给你一个业务需求让你完成:让你写代码测试小明同学写的函数的执行效率。

def effectiveness():print("华为云欢迎你")

版本1:

需求分析:你要想测试此函数的执行效率,你应该怎么做?应该在此函数执行前记录一个时间, 执行完毕之后记录一个时间,这个时间差就是具体此函数的执行效率。那么执行时间如何获取呢? 可以利用time模块,有一个time.time()功能。

import timeprint(time.time())#返回 1575997129.8832688

此方法返回的是格林尼治时间,是此时此刻距离1970年1月1日0点0分0秒的时间秒数。也叫时间戳,他是一直变化的。所以要是计算shopping_car的执行效率就是在执行前后计算这个时间戳的时间,然后求差值即可。

def effectiveness():time.sleep(2)print("华为云欢迎你")start_time = time.time()
effectiveness()
end_time = time.time()
print("此函数的执行效率为:" + str(end_time-start_time))

结果:

/usr/local/bin/python3 /home/yuchuantester/YuchuanData/PythonData/PythonProject/YuchuanDemo002.py
1575998021.69584
华为云欢迎你
此函数的执行效率为:2.0020952224731445Process finished with exit code 0

版本1分析:你现在已经完成了这个需求,但是有什么问题没有? 虽然你只写了四行代码,但是你完成的是一个测试其他函数的执行效率的功能,如果让你测试一下,小张,小李,小刘的函数效率呢? 你是不是全得复制:

重复代码太多了,所以要想解决重复代码的问题,怎么做?我们是不是学过函数,函数就是以功能为导向,减少重复代码,好我们继续整改。

版本2:

def effectiveness():time.sleep(1)print("华为云欢迎你")def calculate_func():start_time = time.time()effectiveness()end_time = time.time()return end_time - start_timeprint("此函数的执行效率为:" + str(calculate_func()))

结果:

/usr/local/bin/python3 /home/yuchuantester/YuchuanData/PythonData/PythonProject/YuchuanDemo002.py
1575998266.1427834
华为云欢迎你
此函数的执行效率为:1.0010716915130615Process finished with exit code 0

但是你这样写也是有问题的,你虽然将测试功能的代码封装成了一个函数,但是这样,你只能测试小明同学的的函数index,你要是测试其他同事的函数呢?你怎么做?

你要是像上面那么做,每次测试其他同事的代码还需要手动改,这样是不是太low了?所以如何变成动态测试其他函数?我们是不是学过函数的传参?能否将被装饰函数的函数名作为函数的参数传递进去呢?

版本3:

def effectiveness():time.sleep(1)print("华为云欢迎你")def diff_file():time.sleep(2.5)print("this is google")def calculate_func(func):start_time = time.time()func()end_time = time.time()return end_time - start_timeprint("effectiveness函数的执行效率为:" + str(calculate_func(effectiveness)))
print("diff_file函数的执行效率为:" + str(calculate_func(diff_file)))

结果:

/usr/local/bin/python3 /home/yuchuantester/YuchuanData/PythonData/PythonProject/YuchuanDemo002.py
1575998867.014351
华为云欢迎你
effectiveness函数的执行效率为:1.0010769367218018
this is google
diff_file函数的执行效率为:2.502596139907837Process finished with exit code 0

这样我将函数effectiveness的函数名作为参数传递给calculate_func函数,然后在calculate_func函数里面执行effectiveness函数,这样就变成动态传参了。好,你们现在将版本3的代码快速练一遍。 大家练习完了之后,发现有什么问题么? 对比着开放封闭原则说: 首先,effectiveness函数除了完成了自己之前的功能,还增加了一个测试执行效率的功能,对不?所以也符合开放原则。 其次,effectiveness函数源码改变了么?没有,但是执行方式改变了,所以不符合封闭原则。 原来如何执行? effectiveness() 现在如何执行? calculate_func(effectiveness),这样会造成什么问题? 假如effectiveness在你的项目中被100处调用,那么这相应的100处调用我都得改成calculate_func(effectiveness)。 非常麻烦,也不符合开放封闭原则。

版本4:实现真正的开放封闭原则:装饰器。

这个也很简单,就是我们昨天讲过的闭包,只要你把那个闭包的执行过程整清楚,那么这个你想不会都难。

你将上面的inner函数在套一层最外面的函数timer,然后将里面的inner函数名作为最外面的函数的返回值,这样简单的装饰器就写好了,一点新知识都没有加,这个如果不会就得多抄几遍,然后理解代码。

def diff_file():time.sleep(2.5)print("this is google")def calculate_func(func):def inner():start_time = time.time()func()end_time = time.time()print("函数的执行效率为:" + str(end_time - start_time))return innerret_func = calculate_func(diff_file)
ret_func()  # 等于调用了inner

结果:

/usr/local/bin/python3 /home/yuchuantester/YuchuanData/PythonData/PythonProject/YuchuanDemo002.py
1576000235.7557225
this is google
函数的执行效率为:2.5025622844696045Process finished with exit code 0

我们分析一下,代码,代码执行到这一行:ret_func= calculate_func(diff_file) 先执行谁?看见一个等号先要执行等号右边, calculate_func(diff_file) 执行calculate_func函数将diff_file函数名传给了func形参。内层函数inner执行么?不执行,inner函数返回 给ret_func变量。所以我们执行ret_func() 就相当于执行inner闭包函数。 ret_func(),这样既测试效率又执行了原函数,有没有问题?当然有啦!!版本4你要解决原函数执行方式不改变的问题,怎么做? 所以你可以把 ret_func换成 diff_file变量就完美了! diff_file = calculate_func(diff_file) , diff_file()带着同学们将这个流程在执行一遍,特别要注意 函数外面的diff_file实际是inner函数的内存地址而不是diff_file函数。让学生们抄一遍,理解一下,这个calculate_func就是最简单版本装饰器,在不改变原diff_file函数的源码以及调用方式前提下,为其增加了额外的功能,测试执行效率。

2. 带返回值的装饰器

你现在这个代码,完成了最初版的装饰器,但是还是不够完善,因为你被装饰的函数index可能会有返回值,如果有返回值,你的装饰器也应该不影响,开放封闭原则嘛。但是你现在设置一下试试:

def diff_file():time.sleep(2.5)print("this is google")return "姐姐"def calculate_func(func):def inner():start_time = time.time()func()end_time = time.time()print("函数的执行效率为:" + str(end_time - start_time))return innerret_func = calculate_func(diff_file)
ret_data = ret_func()  # 等于调用了inner
print(ret_data)

结果:

/usr/local/bin/python3 /home/yuchuantester/YuchuanData/PythonData/PythonProject/YuchuanDemo002.py
1576001398.9691799
this is google
函数的执行效率为:2.5025620460510254
NoneProcess finished with exit code 0

加上装饰器之后,他的返回值为None,为什么?因为你现在的ret_func不是函数名ret_func,这ret_func实际是inner函数名。所以ret_func() 等同于inner() 你的 '访问成功'返回值应该返回给谁?应该返回给ret_func,这样才做到开放封闭,实际返回给了谁?实际返回给了func,所以你要更改一下你的装饰器代码,让其返回给外面的ret_func函数名。 所以:你应该这么做:

def diff_file():time.sleep(2.5)print("this is google")return "姐姐"def calculate_func(func):def inner():start_time = time.time()ret = func()end_time = time.time()print("函数的执行效率为:" + str(end_time - start_time))return retreturn innerret_func = calculate_func(diff_file)
ret_data = ret_func()  # 等于调用了inner
print(ret_data)

结果:

/usr/local/bin/python3 /home/yuchuantester/YuchuanData/PythonData/PythonProject/YuchuanDemo002.py
1576001170.6579535
this is google
函数的执行效率为:2.50258469581604
姐姐Process finished with exit code 0

借助于内层函数inner,你将func的返回值,返回给了inner函数的调用者也就是函数外面的ret_func,这样就实现了开放封闭原则,ret_func返回值,确实返回给了'ret_data'。

3 被装饰函数带参数的装饰器

到目前为止,你的被装饰函数还是没有传参呢?按照我们的开放封闭原则,加不加装饰器都不能影响你被装饰函数的使用。所以我们看一下。

def diff_file(name):time.sleep(2.5)print(f"this is google what's your {name} ")return "姐姐"def calculate_func(func):def inner():start_time = time.time()ret = func()end_time = time.time()print("函数的执行效率为:" + str(end_time - start_time))return retreturn innerret_func = calculate_func(diff_file)
ret_data = ret_func("李白")  # 等于调用了inner
print(ret_data)

结果:

/usr/local/bin/python3 /home/yuchuantester/YuchuanData/PythonData/PythonProject/YuchuanDemo002.py
1576002133.1686425
Traceback (most recent call last):File "/home/yuchuantester/YuchuanData/PythonData/PythonProject/YuchuanDemo002.py", line 30, in <module>ret_data = ret_func("李白")  # 等于调用了inner
TypeError: inner() takes 0 positional arguments but 1 was givenProcess finished with exit code 1

上面那么做,显然报错了,为什么? 你的ret_func这个变量是谁?是inner,ret_func('李白')实际是inner('李白')但是你的'李白'这个实参应该传给谁? 应该传给diff_file函数,实际传给了谁?实际传给了inner,所以我们要通过更改装饰器的代码,让其将实参'李白'传给diff_file.

def diff_file(name):time.sleep(2.5)print(f"this is google what's your {name} ")return "姐姐"def calculate_func(func):def inner(name):start_time = time.time()ret = func(name)end_time = time.time()print("函数的执行效率为:" + str(end_time - start_time))return retreturn innerret_func = calculate_func(diff_file)
ret_data = ret_func("李白")  # 等于调用了inner
print(ret_data)

结果:

/usr/local/bin/python3 /home/yuchuantester/YuchuanData/PythonData/PythonProject/YuchuanDemo002.py
1576002491.6821125
this is google what's your 李白
函数的执行效率为:2.502556562423706
姐姐Process finished with exit code 0

这样你就实现了,还有一个小小的问题,现在被装饰函数的形参只是有一个形参,如果要是多个怎么办?有人说多少个我就写多少个不就行了,那不行呀,你这个装饰器可以装饰N多个不同的函数,这些函数的参数是不统一的。所以你要有一种可以接受不定数参数的形参接受他们。这样,你就要想到*args,**kwargs。

def effectiveness(name, age):time.sleep(1)print(f"华为云欢迎你{name},妹妹的年宁{age}")return "妹妹"def diff_file(name):time.sleep(2.5)print(f"this is google what's your {name} ")return "姐姐"def calculate_func(func):def inner(*args, **kwargs):start_time = time.time()ret = func(*args, **kwargs)end_time = time.time()print("函数的执行效率为:" + str(end_time - start_time))return retreturn innerret_func = calculate_func(diff_file)
ret_data = ret_func("李白")  # 等于调用了inner
print(ret_data)ret_func1 = calculate_func(effectiveness)
ret_data1 = ret_func1("妞妞", 16)  # 等于调用了inner
print(ret_data1)

结果:

/usr/local/bin/python3 /home/yuchuantester/YuchuanData/PythonData/PythonProject/YuchuanDemo002.py
1576002922.719054
this is google what's your 李白
函数的执行效率为:2.5026206970214844
姐姐
华为云欢迎你妞妞,妹妹的年宁16
函数的执行效率为:1.001091718673706
妹妹Process finished with exit code 0

这样利用*的打散与聚合的原理,将这些实参通过inner函数的中间完美的传递到给了相应的形参。

4. 标准版装饰器

代码优化:语法糖

根据我的学习,我们知道了,如果想要各给一个函数加一个装饰器应该是这样:

如果你想给diff_file加上装饰器,每次执行diff_file之前你要写上一句:ret_func = calculate_func(diff_func)这样你在执行ret_func函数 ret_data('妞妞',16) 才是真生的添加了额外的功能。但是每次写这一句也是很麻烦。所以,Python给我们提供了一个简化机制,用一个很简单的符号去代替这一句话。

def calculate_func(func):def inner(*args, **kwargs):start_time = time.time()ret = func(*args, **kwargs)end_time = time.time()print("函数的执行效率为:" + str(end_time - start_time))return retreturn inner@calculate_func
def effectiveness(name, age):time.sleep(1)print(f"华为云欢迎你{name},妹妹的年宁{age}")return "妹妹"@calculate_func
def diff_file(name):time.sleep(2.5)print(f"this is google what's your {name} ")return "姐姐"# @calculate_func <====> ret_func = calculate_func(diff_file)
ret_data = diff_file("李白")  # 等于调用了inner
print(ret_data)# @calculate_func <====> ret_func1 = calculate_func(effectiveness)
ret_data1 = effectiveness("妞妞", 16)  # 等于调用了inner
print(ret_data1)

结果:

/usr/local/bin/python3 /home/yuchuantester/YuchuanData/PythonData/PythonProject/YuchuanDemo002.py
1576003516.4458728
this is google what's your 李白
函数的执行效率为:2.5025768280029297
姐姐
华为云欢迎你妞妞,妹妹的年宁16
函数的执行效率为:1.0010745525360107
妹妹Process finished with exit code 0

你看此时我调整了一下位置,你要是不把装饰器放在上面,calculate_func是找不到的。diff_file函数如果想要加上装饰器那么你就在diff_file函数上面加上@calculate_func,就等同于那句话 ret_func = calculate_func(diff_file)。这么做没有什么特殊意义,就是让其更简单化,比如你在影视片中见过野战军的作战时由于不方便说话,用一些简单的手势代表一些话语,就是这个意思。

至此标准版的装饰器就是这个样子:

def wrapper(func):def inner(*args, **kwargs):"""装饰前增加的功能代码"""ret = func(*args, **kwargs)"""装饰后增加的功能代码"""return retreturn inner

这个就是标准的装饰器,完全符合代码开放封闭原则。这几行代码一定要背过,会用。

此时我们要利用这个装饰器完成一个需求:简单版模拟博客园登录。 此时带着学生们看一下博客园,说一下需求: 博客园登陆之后有几个页面,diary,comment,home,如果我要访问这几个页面,必须验证我是否已登录。 如果已经成功登录,那么这几个页面我都可以无阻力访问。如果没有登录,任何一个页面都不可以访问,我必须先登录,登录成功之后,才可以访问这个页面。我们用成功执行函数模拟作为成功访问这个页面,现在写三个函数,写一个装饰器,实现上述功能。

flags_login = Truedef authenticate(func):"""这是一个装饰器函数:return:"""def inner(*args, **kwargs):"""执行代码前增加功能"""global flags_loginif flags_login:username = input("请输入用户名:")password = input("请输入秘密:")if username == "yuchuanduan" and password == "123456":ret = func(*args, **kwargs)flags_login = Falsereturn retelse:ret = func(*args, **kwargs)"""执行代码后增加功能"""return retreturn inner@authenticate
def diary():print("欢迎来到diary页面!")@authenticate
def comment():print("欢迎来到comment页面?")@authenticate
def home():print("欢迎来到home页面")diary()
comment()
home()

结果:

/usr/local/bin/python3 /home/yuchuantester/YuchuanData/PythonData/PythonProject/YuchuanDemo002.py
1576006578.762892
请输入用户名:yuchuanduan
请输入秘密:123456
欢迎来到diary页面!
欢迎来到comment页面?
欢迎来到home页面Process finished with exit code 0
login_statues = {"username": None,"status": True
}def authenticate(func):"""这是一个装饰器函数:return:"""def inner(*args, **kwargs):"""执行代码前增加功能"""if login_statues["status"]:login_statues["username"] = input("请输入用户名:")password = input("请输入秘密:")if login_statues["username"] == "yuchuanduan" and password == "123456":ret = func(*args, **kwargs)login_statues["status"] = Falsereturn retelse:ret = func(*args, **kwargs)"""执行代码后增加功能"""return retreturn inner@authenticate
def diary():print("欢迎来到diary页面!")@authenticate
def comment():print("欢迎来到comment页面?")@authenticate
def home():print("欢迎来到home页面")diary()
comment()
home()

结果:

/usr/local/bin/python3 /home/yuchuantester/YuchuanData/PythonData/PythonProject/YuchuanDemo002.py
1576006823.0322616
请输入用户名:yuchuanduan
请输入秘密:123456
欢迎来到diary页面!
欢迎来到comment页面?
欢迎来到home页面Process finished with exit code 0

5. 带参数的装饰器

我们看,装饰器其实就是一个闭包函数,再说简单点就是两层的函数。那么是函数,就应该具有函数传参功能。

login_statues = {"username": None,"status": True
}def authenticate(func):"""这是一个装饰器函数:return:"""def inner(*args, **kwargs):"""执行代码前增加功能"""if login_statues["status"]:login_statues["username"] = input("请输入用户名:")password = input("请输入秘密:")if login_statues["username"] == "yuchuanduan" and password == "123456":ret = func(*args, **kwargs)login_statues["status"] = Falsereturn retelse:ret = func(*args, **kwargs)"""执行代码后增加功能"""return retreturn inner

你看我上面的装饰器,不要打开,他可以不可在套一层:

def authenticate(my_args):def authenticate1(func):"""这是一个装饰器函数:return:"""def inner(*args, **kwargs):"""执行代码前增加功能"""if login_statues["status"]:login_statues["username"] = input("请输入用户名:").strip()password = input("请输入秘密:").strip()if login_statues["username"] == "yuchuanduan" and password == "123456":ret = func(*args, **kwargs)login_statues["status"] = Falsereturn retelse:ret = func(*args, **kwargs)"""执行代码后增加功能"""return retreturn innerreturn authenticate1

举例说明:抖音:绑定的是微信账号密码。 皮皮虾:绑定的是qq的账号密码。 你现在要完成的就是你的装饰器要分情况去判断账号和密码,不同的函数用的账号和密码来源不同。 但是你之前写的装饰器只能接受一个参数就是函数名,所以你写一个可以接受参数的装饰器。

login_statues = {"username": None,"status": True
}def authenticate(my_args):def authenticate1(func):"""这是一个装饰器函数:return:"""def inner(*args, **kwargs):"""执行代码前增加功能"""if login_statues["status"]:if my_args == "Wechat":login_statues["username"] = input("请输入Wechat用户名:").strip()password = input("请输入Wechat秘密:").strip()if login_statues["username"] == "yuchuanduan" and password == "123456":ret = func(*args, **kwargs)login_statues["status"] = Falsereturn retelif my_args == "qq":login_statues["username"] = input("请输入QQ用户名:").strip()password = input("请输入QQ秘密:").strip()if login_statues["username"] == "yuchuanduan" and password == "123456":ret = func(*args, **kwargs)login_statues["status"] = Falsereturn retelse:ret = func(*args, **kwargs)"""执行代码后增加功能"""return retreturn innerreturn authenticate1@authenticate("Wechat")
def diary():print("欢迎来到diary页面!")@authenticate("qq")
def comment():print("欢迎来到comment页面?")@authenticate("Wechat")
def home():print("欢迎来到home页面")diary()
comment()
home()

结果:

/usr/local/bin/python3 /home/yuchuantester/YuchuanData/PythonData/PythonProject/YuchuanDemo002.py
1576008396.1496649
请输入Wechat用户名:dsfs
请输入Wechat秘密:dsd
请输入QQ用户名:dsf
请输入QQ秘密:ds
请输入Wechat用户名:yuchuanduan
请输入Wechat秘密:123456
欢迎来到home页面Process finished with exit code 0

@authenticate('Wechat') :分两步:

    第一步先执行authenticate('Wechat')函数,得到返回值authenticate1

    第二步@与authenticate1结合,形成装饰器@authenticate1 然后在依次执行。

这样就是带参数的装饰器,参数可以传入多个,一般带参数的装饰器在以后的工作中都是给你提供的, 你会用就行,但是自己也一定要会写,面试经常会遇到。

作者:Yuchuan

一文读懂Python 装饰器函数相关推荐

  1. 一文读懂 Python 装饰器

    Python 是一种对新手很友好的语言.但是,它也有很多较难掌握的高级功能,比如装饰器(decorator).很多初学者一直不理解装饰器及其工作原理,在这篇文章中,我们将介绍装饰器的来龙去脉. 在 P ...

  2. 一文读懂 @Decorator 装饰器——理解 VS Code 源码的基础

    作者:easonruan,腾讯 CSIG 前端开发工程师 1. 装饰器的样子 我们先来看看 Decorator 装饰器长什么样子,大家可能没在项目中用过 Decorator 装饰器,但多多少少会看过下 ...

  3. 利用世界杯,读懂 Python 装饰器

    Python 装饰器是在面试过程高频被问到的问题,装饰器也是一个非常好用的特性, 熟练掌握装饰器会让你的编程思路更加宽广,程序也更加 pythonic. 今天就结合最近的世界杯带大家理解下装饰器. 德 ...

  4. 利用世界杯,读懂 Python 装饰器 1

    Python 装饰器是在面试过程高频被问到的问题,装饰器也是一个非常好用的特性, 熟练掌握装饰器会让你的编程思路更加宽广,程序也更加 pythonic. 今天就结合最近的世界杯带大家理解下装饰器. 德 ...

  5. 老司机教你 5 分钟读懂 Python 装饰器

    1. 写在前面 在介绍python装饰器之前,首先介绍python的一个概念,对象.在python里,所有的一切皆对象.常用的python对象有整型对象,浮点型对象,字符串对象,列表对象,元组对象,字 ...

  6. 利用亚运会,读懂 Python装饰器

    阅读文本大概需要 5 分钟. 2018 年印度雅加达亚运会已接近尾声,中国在金牌榜和总奖牌榜都遥遥领先于第二名的日本,我也是一名体育爱好者,平时有比赛也会看.看到中国的国旗在海外飘扬,内心会格外的自豪 ...

  7. 【Python】一文弄懂python装饰器(附源码例子)

    目录 前言 一.什么是装饰器 二.为什么要用装饰器 三.简单的装饰器 四.装饰器的语法糖@ 五.装饰器传参 六.带参数的装饰器 七.类装饰器 八.带参数的类装饰器 九.装饰器的顺序 总结 写在后面 前 ...

  8. 廖雪峰讲python高阶函数求导公式_一文读懂Python 高阶函数

    高阶函数 将函数作为参数传入,这样的函数称为高阶函数.函数式编程就是指这种高度抽象的编程范式. 变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函 ...

  9. python读取枚举_一文读懂Python 枚举

    enum是一组绑定到唯一常数值的符号名称,并且具备可迭代性和可比较性的特性.我们可以使用 enum 创建具有良好定义的标识符,而不是直接使用魔法字符串或整数,也便于开发工程师的代码维护. 创建枚举 我 ...

最新文章

  1. mysql 事务处理
  2. 看《你必须知道的.NET》有感--工厂模式的另类解读
  3. Vue route页面跳转,传递参数接收到的参数为空
  4. python基础之if、while、for语句
  5. Cross-Validation(交叉验证)详解
  6. java包 类 方法_Java中包与包之间方法的调用及其关键字区分(基础)
  7. 前端学习(2471):vue-echarts和echarts的区别:
  8. 大数据里的婚姻:婚后两年,出轨高峰……
  9. python selenium刷新页面_关于python-selenium的页面元素刷新的问题,详细见内容
  10. OSChina 周五乱弹 ——变态要从娃娃抓起
  11. 击破区块链应用落地之痛,爆款公链太硬核!
  12. Vulkan系列教程—VMA教程(七)—Defragmentation(碎片整理)
  13. 计算机考试字体大小怎么看,Win7电脑便签字体大小怎么查看?
  14. 嵌入式linux快速入门
  15. 从Excel中随机取出几行
  16. 厦门大学计算机保研学校,厦门大学计算机科学系(专业学位)计算机技术保研夏令营...
  17. GPU与CPU的区别
  18. OpenCV开发笔记(七十二):红胖子8分钟带你使用opencv+dnn+tensorFlow识别物体
  19. win10系统wifi能连上但不能上网怎么办
  20. java 代码的规范

热门文章

  1. java日期算前一天_java 根据系统日期获取前一天、后一天时间(根据初始日期推算出期望(向前/向后)日期)...
  2. [ubuntu]deb软件源
  3. 算法不归路之最大子序列(C++版)
  4. 经典算法(61~90)
  5. Nhibernate 3.0 Cookbook学习笔记 利用XML映射类
  6. 在ASP.NET 中实现单用户登录(利用Cache, 将用户信息保存在服务器缓存中)[转]
  7. [代码] DataGrid GridView 使用区别
  8. Web服务器网管交流一下
  9. 交换两个变量的值(三种方式、完整代码)
  10. C++ STL set集合的使用