Python三大器之装饰器

开放封闭原则

一个良好的项目必定是遵守了开放封闭原则的,就比如一段好的Python代码必定是遵循PEP8规范一样。那么什么是开放封闭原则?具体表现在那些点?

  开放封闭原则的核心的思想是软件实体是可扩展,而不可修改的。 也就是说,对扩展是开放的,而对修改是封闭的。 即使迫不得已要进行修改,也最好不要改变它原本的代码。

  具体表现的点:

    1.写好的项目,后期可以很方便的为其添加新的功能

    2.项目修改应该尽量少的改动原本逻辑代码。而是通过某种补丁的形式完善其功能

初识装饰器

什么是装饰器


  装饰器应该拆开来进行讲解:

  "器"指的是器具,工具。可以理解为函数

  "装饰"指的是为其他事物添加额外的东西点缀。

  "装饰器"就是指定义一个函数,而该函数的主要作用便是用于为其他函数做功能上的一个补充(添加新的功能)。

为什么要有装饰器


  补充一个函数的功能非常简单。但是要在遵循开放封闭原则的前提下为一个函数做功能的补充就不是那么简单了,装饰器就是为了解决这种场景而诞生的。

  装饰器可以在遵循开放封闭原则的前提下为被装饰对象添加新的功能(即不修改源代码及其调用方式的前提下为其添加新功能)

怎么使用装饰器


  装饰器说白了就是闭包函数加上*args以及**kwargs的一个综合应用。下面的内容将带你深入浅出的了解装饰器的定义与使用。

装饰器实现

需求分析


  1. import time
  2. import random


  3. # 通过time模块和random模块模拟下载,上传功能所花费的时间

  4. # 以下两个函数都部署在线上服务器上:
  5. # 要求为其增添新的功能,不违反开放封闭性原则的前提下统计下载和上传花费的时长。
  6. # (即:不修改download和upload的调用方式和源码的情况下为其添加统计时长的功能)
  7. def download(url):
  8. """下载..."""
  9. print("正在下载{0}的资源...".format(url))
  10. time.sleep(random.randint(1, 3))
  11. print("下载完成...")


  12. def upload(url):
  13. """上传..."""
  14. print("正在向{0}上传资源...".format(url))
  15. time.sleep(random.randint(1, 3))
  16. print("上传完成...")


  17. download("www.cnblogs.com/xxx/file/x.jpg")
  18. download("www.cnblogs.com/yyy/file/y.jpg")
  19. upload("www.cnblogs.com/zzz/file/img")

解决方案一


  既然要在不修改源码与调用方式的前提下增添新功能。那么我们可以这么做:

  1. import time
  2. import random


  3. # 通过time模块和random模块模拟下载,上传功能所花费的时间

  4. # 以下两个函数都部署在线上服务器上:
  5. # 要求为其增添新的功能,不违反开放封闭性原则的前提下统计下载和上传花费的时长。
  6. # (即:不修改download和upload的调用方式和源码的情况下为其添加统计时长的功能)
  7. def download(url):
  8. """下载..."""
  9. print("正在下载{0}的资源...".format(url))
  10. time.sleep(random.randint(1, 3))
  11. print("下载完成...")


  12. def upload(url):
  13. """上传..."""
  14. print("正在向{0}上传资源...".format(url))
  15. time.sleep(random.randint(1, 3))
  16. print("上传完成...")

  17. start = time.time()
  18. download("www.cnblogs.com/xxx/file/x.jpg")
  19. end = time.time()
  20. print("下载www.cnblogs.com/xxx/file/x.jpg资源所耗费的时长为:{0:.2f}秒".format(end-start))

  21. start = time.time()
  22. download("www.cnblogs.com/yyy/file/y.jpg")
  23. end = time.time()
  24. print("下载www.cnblogs.com/yyy/file/y.jpg资源所耗费的时长为:{0:.2f}秒".format(end-start))

  25. start = time.time()
  26. upload("www.cnblogs.com/zzz/file/img")
  27. end = time.time()
  28. print("上传资源至www.cnblogs.com/zzz/file/img所耗费的时长为:{0:.2f}秒".format(end-start))

  29. # ==== 执行结果 ===

  30. """
  31. 正在下载www.cnblogs.com/xxx/file/x.jpg的资源...
  32. 下载完成...
  33. 下载www.cnblogs.com/xxx/file/x.jpg资源所耗费的时长为:3.00秒
  34. 正在下载www.cnblogs.com/yyy/file/y.jpg的资源...
  35. 下载完成...
  36. 下载www.cnblogs.com/yyy/file/y.jpg资源所耗费的时长为:1.00秒
  37. 正在向www.cnblogs.com/zzz/file/img上传资源...
  38. 上传完成...
  39. 上传资源至www.cnblogs.com/zzz/file/img所耗费的时长为:1.00秒
  40. """

  问题解决,不修改源代码以及调用方式的前提下为downloadupload函数添加好了新功能,但是这种解决方案是非常愚蠢的。

  写重复代码,程序可读性非常差而且非常耗费人力去单纯的做复制粘贴。

解决方案二


  1. import time
  2. import random


  3. # 通过time模块和random模块模拟下载,上传功能所花费的时间

  4. # 以下两个函数都部署在线上服务器上:
  5. # 要求为其增添新的功能,不违反开放封闭性原则的前提下统计下载和上传花费的时长。
  6. # (即:不修改download和upload的调用方式和源码的情况下为其添加统计时长的功能)
  7. def download(url):
  8. """下载..."""
  9. print("正在下载{0}的资源...".format(url))
  10. time.sleep(random.randint(1, 3))
  11. print("下载完成...")


  12. def upload(url):
  13. """上传..."""
  14. print("正在向{0}上传资源...".format(url))
  15. time.sleep(random.randint(1, 3))
  16. print("上传完成...")


  17. def outer(func): # func 是函数的内存地址
  18. def warpper(url):
  19. start = time.time()
  20. func(url) # 这里才是真正运行的 download 或者 upload 函数。
  21. end = time.time()
  22. if func.__name__ == "download": # func.__name__ 可以拿到函数名
  23. print("下载{0}资源所耗费的时长为:{1:.2f}秒".format(url, (end - start)))
  24. else:
  25. print("上传资源至{0}所耗费的时长为:{1:.2f}秒".format(url, (end - start)))

  26. return warpper


  27. download = outer(download) # download函数内存地址传递进去。 变量值download接收,返回的是 warpper 函数的内存地址。此download非彼download
  28. upload = outer(upload)

  29. download("www.cnblogs.com/xxx/file/x.jpg") # 由于warpper指向的函数需要一个参数url,所以直接传即可。
  30. download("www.cnblogs.com/yyy/file/y.jpg")
  31. upload("www.cnblogs.com/zzz/file/img")

  32. # ==== 执行结果 ===

  33. """
  34. 正在下载www.cnblogs.com/xxx/file/x.jpg的资源...
  35. 下载完成...
  36. 下载www.cnblogs.com/xxx/file/x.jpg资源所耗费的时长为:1.02秒
  37. 正在下载www.cnblogs.com/yyy/file/y.jpg的资源...
  38. 下载完成...
  39. 下载www.cnblogs.com/yyy/file/y.jpg资源所耗费的时长为:2.01秒
  40. 正在向www.cnblogs.com/zzz/file/img上传资源...
  41. 上传完成...
  42. 上传资源至www.cnblogs.com/zzz/file/img所耗费的时长为:2.02秒
  43. """

  解决方案二已经完美完成需求了。但是还能再做一些修改,基于开放封闭原则,该解决方案的扩展性还不是很强,如果后期upload或者download的源码真的发生改变所需要的参数增多了。那我们的这个outer函数也需要去做改变,很麻烦。所以我们需要在此基础上做一个改进。

解决方案三


  1. import time
  2. import random


  3. # 通过time模块和random模块模拟下载,上传功能所花费的时间

  4. # 以下两个函数都部署在线上服务器上:
  5. # 要求为其增添新的功能,不违反开放封闭性原则的前提下统计下载和上传花费的时长。
  6. # (即:不修改download和upload的调用方式和源码的情况下为其添加统计时长的功能)
  7. def download(url):
  8. """下载..."""
  9. print("正在下载{0}的资源...".format(url))
  10. time.sleep(random.randint(1, 3))
  11. print("下载完成...")


  12. def upload(url):
  13. """上传..."""
  14. print("正在向{0}上传资源...".format(url))
  15. time.sleep(random.randint(1, 3))
  16. print("上传完成...")


  17. def outer(func): # func 是函数的内存地址
  18. def warpper(*args,**kwargs): # 优化1:参数转接,warpper直接对接func,多少参数都没问题。取决于func
  19. start = time.time()
  20. res = func(*args,**kwargs) # 这里才是真正运行的 download 或者 upload 函数。 优化2:如果函数有了返回值,我们就可以对他进行返回
  21. end = time.time()
  22. if func.__name__ == "download": # func.__name__ 可以拿到函数名
  23. print("下载{0}资源所耗费的时长为:{1:.2f}秒".format(args[0], (end - start))) # 优化3:这里改成args[0]即可。位置传参url必须一一对应,其实有个小bug,关键字传参有问题。
  24. else:
  25. print("上传资源至{0}所耗费的时长为:{1:.2f}秒".format(args[0], (end - start)))
  26. return res # 优化2:返回func的返回值
  27. return warpper


  28. download = outer(download) # download函数内存地址传递进去。 变量值download接收,返回的是 warpper 函数的内存地址。此download非彼download
  29. upload = outer(upload)

  30. download("www.cnblogs.com/xxx/file/x.jpg") # 由于warpper指向的函数需要一个参数url,所以直接传即可。
  31. download("www.cnblogs.com/yyy/file/y.jpg")
  32. upload("www.cnblogs.com/zzz/file/img")

  33. # ==== 执行结果 ===

  34. """
  35. 正在下载www.cnblogs.com/xxx/file/x.jpg的资源...
  36. 下载完成...
  37. 下载www.cnblogs.com/xxx/file/x.jpg资源所耗费的时长为:1.02秒
  38. 正在下载www.cnblogs.com/yyy/file/y.jpg的资源...
  39. 下载完成...
  40. 下载www.cnblogs.com/yyy/file/y.jpg资源所耗费的时长为:2.01秒
  41. 正在向www.cnblogs.com/zzz/file/img上传资源...
  42. 上传完成...
  43. 上传资源至www.cnblogs.com/zzz/file/img所耗费的时长为:2.02秒
  44. """

无参装饰器模板

  1. # ==== 无参普通方式调用 ====

  2. def test(x,y,z):
  3. print(x,y,z)
  4. return "结果"

  5. def outer(func): # 装饰器名字应该取好,func代表被装饰函数。
  6. def warpper(*args,**kwargs): # warpper函数对应func,也就是被执行的函数。这里做对接
  7. # 功能扩展区,函数运行前
  8. res = func(*args,**kwargs) # 接收func的return值
  9. # 功能扩展区,函数运行后
  10. return res # 将func的返回值返回出去
  11. return warpper

  12. test = outer(test) # test变量名执行warpper内存地址。
  13. res = test(1,2,3) # 拿到warpper的return值,看起来在执行test,实际上在执行warpper
  14. print(res)

无参普通方式调用

  1. # ==== 无参wraps普通方式调用 ====
  2. from functools import wraps

  3. def test(x,y,z):
  4. print(x,y,z)
  5. return "结果"

  6. def outer(func): # 装饰器名字应该取好,func代表被装饰函数。
  7. @wraps(func) # 这里主要完全将warpper的所有文档信息等等都与func保持一致。下面会讲
  8. def warpper(*args,**kwargs): # warpper函数对应func,也就是被执行的函数。这里做对接
  9. # 功能扩展区,函数运行前
  10. res = func(*args,**kwargs) # 接收func的return值
  11. # 功能扩展区,函数运行后
  12. return res # 将func的返回值返回出去
  13. return warpper

  14. test = outer(test)
  15. res = test(1,2,3) # 拿到warpper的return值
  16. print(res)

无参wraps普通方式调用

  1. # ==== 无参wraps@语法糖方式调用 ====

  2. from functools import wraps
  3. def outer(func): # 装饰器名字应该取好,func代表被装饰函数。
  4. @wraps(func) # 这里主要完全将warpper的所有文档信息等等都与func保持一致。下面会讲
  5. def warpper(*args,**kwargs): # warpper函数对应func,也就是被执行的函数。这里做对接
  6. # 功能扩展区,函数运行前
  7. res = func(*args,**kwargs) # 接收func的return值
  8. # 功能扩展区,函数运行后
  9. return res # 将func的返回值返回出去
  10. return warpper

  11. @outer # 将下面一坨被装饰函数放入func中。相当于 --> test = outer(test)。此时执行test就相当于执行warpper。
  12. def test(x,y,z):
  13. print(x,y,z)
  14. return "结果"

  15. res = test(1,2,3) # 拿到warpper的return值
  16. print(res)

无参wraps@语法糖方式调用

  (对于@语法糖调用形式来说):outer层的参数只应该有一个func形参来接收被装饰的函数对象,wapper层就只该有*args**kwargs,为的是和传入的func(函数对象)做参数对接,参数不能多传递。

@语法糖

@调用装饰器


  上面的第三种解决方案其实就是装饰器。那么我们可以看到他还是有一些复杂的地方,比如:

  其实Python为了简化这一步骤。提供了@语法糖,我们看一下它是如何使用的。

  1. # ==== @语法糖的使用 ====

  2. # 第一步:装饰器所有代码拖上来,拖到被装饰对象的上面。
  3. def outer(func): # func 是函数的内存地址
  4. def warpper(*args, **kwargs): # 优化1:参数转接,warpper直接对接func,多少参数都没问题。取决于func
  5. start = time.time()
  6. res = func(*args, **kwargs) # 这里才是真正运行的 download 或者 upload 函数。 优化2:如果函数有了返回值,我们就可以对他进行返回
  7. end = time.time()
  8. if func.__name__ == "download": # func.__name__ 可以拿到函数名
  9. print("下载{0}资源所耗费的时长为:{1:.2f}秒".format(args[0], (end - start))) # 优化3:这里改成args[0]即可。位置传参必须一一对应,其实有个小bug,关键字传参有问题。
  10. else:
  11. print("上传资源至{0}所耗费的时长为:{1:.2f}秒".format(*args[0], (end - start)))
  12. return res # 优化2:返回func的返回值

  13. return warpper

  14. # 第二步:@后面跟上装饰器名称,
  15. # 其实@outer就相当于把下面一坨被装饰函数当做参数传给outer --> download = outer(download)。而下面执行download的时候相当于就是执行返回值warpper函数
  16. @outer
  17. def download(url):
  18. """下载..."""
  19. print("正在下载{0}的资源...".format(url))
  20. time.sleep(random.randint(1, 3))
  21. print("下载完成...")

  22. @outer
  23. def upload(url):
  24. """上传..."""
  25. print("正在向{0}上传资源...".format(url))
  26. time.sleep(random.randint(1, 3))
  27. print("上传完成...")

  28. # 第三步: 删除偷梁换柱的操作

  29. download("www.cnblogs.com/xxx/file/x.jpg") # 由于warpper指向的函数需要一个参数url,所以直接传即可。
  30. download("www.cnblogs.com/yyy/file/y.jpg")
  31. upload("www.cnblogs.com/zzz/file/img")

@到底做了什么


  想要了解@到底做了什么,先介绍一个内置函数叫做help(),它可以获得一些对于当前函数的帮助信息。

  1. # ==== @内部做了那些事 ====

  2. def test():
  3. """这是一个测试"""
  4. pass

  5. res = help(test) # 获得帮助。 # help的返回值是None,并且help会自动调用print打印出结果。注意!!!
  6. print(res) # 这里只是打印help方法的返回值。

  7. # ==== 执行结果 ====

  8. """
  9. Help on function test in module __main__:

  10. test()
  11. 这是一个测试

  12. None # <--这里是print的结果,上面全是help的结果,help内部自动调用print方法打印出了上面的信息
  13. """

  我们再来测试看结果:

  1. # ==== @内部做了那些事 ====

  2. @help # 由于help内部会自动调用print,所以会先出现它的信息
  3. def test():
  4. """这是一个测试"""
  5. pass

  6. test()

  7. # ==== 执行结果 ====

  8. """
  9. Help on function test in module __main__:

  10. test()
  11. 这是一个测试

  12. Traceback (most recent call last):
  13. File "C:/Users/Administrator/PycharmProjects/learn/FunctionLearn.py", line 82, in <module>
  14. test()
  15. TypeError: 'NoneType' object is not callable # <-- 注意,意思是说空类型不可被调用的意思
  16. """

  结论如下:

  1. # # ==== @内部做了那些事(结论) ====

  2. # @后面跟着函数名。会自动将被装饰函数当做参数拿进来(如果手动加括号,被装饰函数就不能灵活的传进来,这是一种限制),help(test),并且会将它返回给变量test,那么便是 test = help(test)...
  3. # 由于help的返回值为None,所以...

  4. @help
  5. def test():
  6. """这是一个测试"""
  7. pass

  8. test() # 现在的test,就是help的返回结果。也就是None,一个名字加括号代表要执行它。而None类型不可被执行。

  9. # ==== 执行结果 ====

  10. """
  11. Help on function test in module __main__:

  12. test()
  13. 这是一个测试

  14. Traceback (most recent call last):
  15. File "C:/Users/Administrator/PycharmProjects/learn/FunctionLearn.py", line 82, in <module>
  16. test()
  17. TypeError: 'NoneType' object is not callable
  18. """

  现在我们知道了。@会自动调用的(),并且将下面的被装饰函数当做参数传递进去,如果我们手动做这件事会发生什么呢?

  1. # ==== @手动传参 ====

  2. @help(print)
  3. def test():
  4. """这是一个测试"""
  5. pass

  6. test() # 现在的test,就是help的返回结果。也就是None,一个名字加括号代表要执行它。而None类型不可被执行。

  7. # ==== 执行结果 ==== Ps:可以看到,它打印出了print的帮助信息。而test的帮助信息并没有传递进去。

  8. """
  9. Help on built-in function print in module builtins:

  10. print(...)
  11. print(value, ..., sep=' ', end='\n', file=sys.stdout, flush=False)
  12.  
  13. Prints the values to a stream, or to sys.stdout by default.
  14. Optional keyword arguments:
  15. file: a file-like object (stream); defaults to the current sys.stdout.
  16. sep: string inserted between values, default a space.
  17. end: string appended after the last value, default a newline.
  18. flush: whether to forcibly flush the stream.

  19. Traceback (most recent call last):
  20. File "C:/Users/Administrator/PycharmProjects/learn/FunctionLearn.py", line 80, in <module>
  21. def test():
  22. TypeError: 'NoneType' object is not callable
  23. """

多个@装饰器分析


  如果一次性使用多个@装饰器,会发生什么?

  记住一点:

  多个装饰器的执行顺序自下而上

  1. # ==== 多个@装饰器执行顺序 ====

  2. def f1(func):
  3. print("执行f1装饰器了")
  4. def warpper(*args,**kwargs):
  5. print("我是f1中的warpper.我的参数是..", func)
  6. res = func(*args,**kwargs)
  7. return res
  8. return warpper

  9. def f2(func):
  10. print("执行f2装饰器了")
  11. def warpper(*args,**kwargs):
  12. print("我是f2中的warpper.我的参数是..", func)
  13. res = func(*args,**kwargs)
  14. return res
  15. return warpper

  16. def f3(func):
  17. print("执行f3装饰器了")
  18. def warpper(*args,**kwargs):
  19. print("我是f3中的warpper.我的参数是..", func)
  20. res = func(*args,**kwargs)
  21. return res
  22. return warpper

  23. @f1
  24. @f2
  25. @f3
  26. def test():
  27. print("我是test,我没有参数")

  28. test()

  29. # 注意:@装饰器的执行顺序是从下至上
  30. # 第一步:test = f3(test),打印第1句,返回的是f3中的warpper。
  31. # 第二步:test = f2(test),打印第2句,此时的test是f3中的warpper,返回的是f2中的warpper
  32. # 第三步:test = f1(test),打印第3句,此时的test是f2中的warpper,返回的是f1中的warpper
  33. # 第四步,开始执行test.也就是执行f1中的warpper。打印第4句,运行func
  34. # 第五步:此时运行的func是f2中的warpper,所以打印第5句,继续运行func
  35. # 第六步:此时运行的func是f3中的warpper,所以打印第6句,继续运行func
  36. # 第七步:此时运行的func才是真正的test,打印第7句。分析结束。

  37. # ==== 执行结果 ====

  38. """
  39. 执行f3装饰器了
  40. 执行f2装饰器了
  41. 执行f1装饰器了
  42. 我是f1中的warpper.我的参数是.. <function f2.<locals>.warpper at 0x00000246A34F6940>
  43. 我是f2中的warpper.我的参数是.. <function f3.<locals>.warpper at 0x00000246A34F68B0>
  44. 我是f3中的warpper.我的参数是.. <function test at 0x00000246A34F6820>
  45. 我是test,我没有参数
  46. """

有参装饰器

有参装饰器应用场景


  无参装饰器已经能够解决百分之九十的应用场景。但是有的时候还是需要有参装饰器来解决一些特定的需求,在不允许为内部函数传参的情况下就该使用到有参装饰器。它其实也是利用了闭包函数中传参的这一作用,直接再往上封装一层就好了。

  基于无参装饰器,也就是解决方案三。我们再提一个需求:

  下载或上传的时候如果用户是普通用户,则拒绝他通过本软件进行下载或上传,如果是VIP用户,则可以使用我们的所有功能。

  解决方案一 不使用@语法糖:不使用@语法糖可以很轻松的解决,只需要为outer加上一个参数传进去即可。

  1. import time
  2. import random

  3. user_genre = random.randint(0, 1) # 这里懒得做登录了,用随机数代替。随机数是0则代表普通用户,随机数是1就代表vip用户。


  4. def outer(func, genre): # func 是函数的内存地址,genre是用户类型。
  5. def warpper(*args, **kwargs): # 优化1:参数转接,warpper直接对接func,多少参数都没问题。取决于func
  6. if genre == 1:
  7. start = time.time()
  8. res = func(*args, **kwargs) # 这里才是真正运行的 download 或者 upload 函数。 优化2:如果函数有了返回值,我们就可以对他进行返回
  9. end = time.time()
  10. if func.__name__ == "download": # func.__name__ 可以拿到函数名
  11. print("下载{0}资源所耗费的时长为:{1:.2f}秒".format(args[0], (
  12. end - start))) # 优化3:这里改成args[0]即可。位置传参必须一一对应,其实有个小bug,关键字传参有问题。
  13. else:
  14. print("上传资源至{0}所耗费的时长为:{1:.2f}秒".format(args[0], (end - start)))
  15. return res # 优化2:返回func的返回值
  16. else:
  17. print("不允许下载或上传")

  18. return warpper


  19. def download(url):
  20. """下载..."""
  21. print("正在下载{0}的资源...".format(url))
  22. time.sleep(random.randint(1, 3))
  23. print("下载完成...")


  24. def upload(url):
  25. """上传..."""
  26. print("正在向{0}上传资源...".format(url))
  27. time.sleep(random.randint(1, 3))
  28. print("上传完成...")


  29. download = outer(download, user_genre) # download函数内存地址传递进去。 变量值download接收,返回的是 warpper 函数的内存地址。此download非彼download
  30. upload = outer(upload, user_genre) # 这里直接将用户类型传递进去。(图省事,没做登录这些,求一个精简)

  31. download("www.cnblogs.com/xxx/file/x.jpg") # 由于warpper指向的函数需要一个参数url,所以直接传即可。
  32. download("www.cnblogs.com/yyy/file/y.jpg")
  33. upload("www.cnblogs.com/zzz/file/img")

  34. # ==== 执行结果 ===

  35. """
  36. 不允许下载或上传
  37. 不允许下载或上传
  38. 不允许下载或上传
  39. """

  解决方案二 使用@语法糖:由于@语法糖的限制,我们的outer函数应当只该去接收func,而不应该有其他的变量(实际上也接收不了其他的变量,不信你可以试试)。所以,我们需要通过闭包嵌套再给他传入一个变量。

  1. import time
  2. import random

  3. user_genre = random.randint(0, 1) # 这里懒得做登录了,用随机数代替。随机数是0则代表普通用户,随机数是1就代表vip用户。


  4. def outer(genre): # genre是用户类型。
  5. def inner(func): # func 是函数的内存地址,由于改为第二层,故改名inner。
  6. def warpper(*args, **kwargs): # 优化1:参数转接,warpper直接对接func,多少参数都没问题。取决于func
  7. if genre == 1:
  8. start = time.time()
  9. res = func(*args, **kwargs) # 这里才是真正运行的 download 或者 upload 函数。 优化2:如果函数有了返回值,我们就可以对他进行返回
  10. end = time.time()
  11. if func.__name__ == "download": # func.__name__ 可以拿到函数名
  12. print("下载{0}资源所耗费的时长为:{1:.2f}秒".format(args[0], (
  13. end - start))) # 优化3:这里改成args[0]即可。位置传参必须一一对应,其实有个小bug,关键字传参有问题。
  14. else:
  15. print("上传资源至{0}所耗费的时长为:{1:.2f}秒".format(args[0], (end - start)))
  16. return res # 优化2:返回func的返回值
  17. else:
  18. print("不允许下载或上传")

  19. return warpper

  20. return inner


  21. @outer(user_genre) # 手动传值进去,否则运行不了。传入用户类型,如果不手动传值将会把下面被装饰函数传入进去。导致认为inner是warpper。
  22. def download(url):
  23. """下载..."""
  24. print("正在下载{0}的资源...".format(url))
  25. time.sleep(random.randint(1, 3))
  26. print("下载完成...")


  27. @outer(user_genre)
  28. def upload(url):
  29. """上传..."""
  30. print("正在向{0}上传资源...".format(url))
  31. time.sleep(random.randint(1, 3))
  32. print("上传完成...")


  33. download("www.cnblogs.com/xxx/file/x.jpg") # 由于warpper指向的函数需要一个参数url,所以直接传即可。
  34. download("www.cnblogs.com/yyy/file/y.jpg")
  35. upload("www.cnblogs.com/zzz/file/img")

  36. # ==== 执行结果 ===

  37. """
  38. 不允许下载或上传
  39. 不允许下载或上传
  40. 不允许下载或上传
  41. """

有参装饰器的实现模板


  1. # ==== 有参wraps@语法糖方式调用 ====

  2. from functools import wraps
  3. def outer(x):
  4. def inner(func): # 装饰器名字应该取好,func代表被装饰函数。
  5. @wraps(func) # 这里主要完全将warpper的所有文档信息等等都与func保持一致。下面会讲
  6. def warpper(*args,**kwargs): # warpper函数对应func,也就是被执行的函数。这里做对接
  7. # 功能扩展区,函数运行前
  8. res = func(*args,**kwargs) # 接收func的return值
  9. # 功能扩展区,函数运行后
  10. return res # 将func的返回值返回出去
  11. return warpper
  12. return inner

  13. @outer(1) # 执行outer(注意:必须手动传入参数,不然会将test传进去),拿到返回值inner.相当于@inner,再自动执行inner,将test传给形参func,返回warpper.执行test的时候实际上就是执行的wapper
  14. def test(x,y,z):
  15. print(x,y,z)
  16. return "结果"

  17. res = test(1,2,3) # 拿到warpper的return值
  18. print(res)

有参wraps@语法糖方式调用

  (对于@语法糖调用形式来说):inner层的参数只应该有一个func形参来接收被装饰的函数对象,wapper层就只该有*args**kwargs,为的是和传入的func(函数对象)做参数对接,参数不能多传递。所以就在外面套上一个outer层函数,通过闭包传参即可,目的是做功能拓展。

扩展:wraps装饰器修饰伪装


  如果不使用wraps装饰器对warpper函数进行装饰,那么其实偷梁换柱是有瑕疵的。我们看...

  1. def test(x,y,z):
  2. print(x,y,z)
  3. return "结果"

  4. def outer(func):
  5. def warpper(*args,**kwargs):
  6. res = func(*args,**kwargs)
  7. return res # 将func的返回值返回出去
  8. return warpper

  9. test = outer(test) # test变量名执行warpper内存地址。
  10. help(test)

  11. # ==== 执行结果 ==== Ps:可以看到test的help信息还是warpper,这个偷梁换柱不彻底。

  12. """
  13. Help on function warpper in module __main__:

  14. warpper(*args, **kwargs)
  15. """

  在使用wraps装饰器对warpper函数进行装饰后,那么就是真正意义上的移花接木偷天换日。继续看...

  1. from functools import wraps

  2. def test(x,y,z):
  3. print(x,y,z)
  4. return "结果"

  5. def outer(func):
  6. @wraps(func) # 传入被装饰对象。完成移花接木
  7. def warpper(*args,**kwargs):
  8. res = func(*args,**kwargs)
  9. return res # 将func的返回值返回出去
  10. return warpper

  11. test = outer(test) # test变量名执行warpper内存地址。
  12. help(test)

  13. # ==== 执行结果 ==== Ps:移花接木成功

  14. """
  15. Help on function test in module __main__:

  16. test(x, y, z)
  17. """

Python三大器之装饰器的更多相关文章

  1. python基础(补充):python三大器之装饰器

    函数作为返回值 高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回. 我们来实现一个可变参数的求和.通常情况下,求和的函数是这样定义的: def calc_sum(*args): i = ...

  2. python三大器之装饰器的练习

    装饰器 加载顺序从下至上 执行顺序从上至下 ''' 多层装饰器 ''' def deco1(func): #func=deco2 def wrapper1(*args, **kwargs): '''t ...

  3. python笔记 - day4-之装饰器

                 python笔记 - day4-之装饰器 需求: 给f1~f100增加个log: def outer(): #定义增加的log print("log") ...

  4. Python进阶(六)----装饰器

    Python进阶(六)----装饰器 一丶开放封闭原则 开放原则: ​ 增加一些额外的新功能 封闭原则: ​ 不改变源码.以及调用方式 二丶初识装饰器 装饰器: ​ 也可称装饰器函数,诠释开放封闭原则 ...

  5. python高级之装饰器

    python高级之装饰器 本节内容 高阶函数 嵌套函数及闭包 装饰器 装饰器带参数 装饰器的嵌套 functools.wraps模块 递归函数被装饰 1.高阶函数 高阶函数的定义: 满足下面两个条件之 ...

  6. [python基础]关于装饰器

    在面试的时候,被问到装饰器,在用的最多的时候就@classmethod ,@staticmethod,开口胡乱回答想这和C#的static public 关键字是不是一样的,等面试回来一看,哇,原来是 ...

  7. Python深入05 装饰器

    作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 装饰器(decorator)是一种高级Python语法.装饰器可以对一个函数.方法 ...

  8. Day04 - Python 迭代器、装饰器、软件开发规范

    1. 列表生成式 实现对列表中每个数值都加一 第一种,使用for循环,取列表中的值,值加一后,添加到一空列表中,并将新列表赋值给原列表 >>> a = [0, 1, 2, 3, 4, ...

  9. Noah的学习笔记之Python篇:装饰器

    Noah的学习笔记之Python篇: 1.装饰器 2.函数“可变长参数” 3.命令行解析 注:本文全原创,作者:Noah Zhang  (http://www.cnblogs.com/noahzn/) ...

随机推荐

  1. 项目打包成手机app 通过什么打包?

    项目打包成手机app  通过什么打包? 1.HbuildX注册邮箱账号 2.新建-app,然后将自动生成的除manifest.json之外的所有文件删除,然后将vue项目build之后生成的dist文 ...

  2. Java 第十一届 蓝桥杯 省模拟赛十六进制转换成十进制

    问题描述 请问十六进制数1949对应的十进制数是多少?请特别注意给定的是十六进制,求的是十进制. 答案提交 这是一道结果填空的题,你只需要算出结果后提交即可.本题的结果为一个整数,在提交答案时只填写这 ...

  3. Java实现 LeetCode 367 有效的完全平方数

    367. 有效的完全平方数 给定一个正整数 num,编写一个函数,如果 num 是一个完全平方数,则返回 True,否则返回 False. 说明:不要使用任何内置的库函数,如 sqrt. 示例 1: ...

  4. Java实现 LeetCode 263 丑数

    263. 丑数 编写一个程序判断给定的数是否为丑数. 丑数就是只包含质因数 2, 3, 5 的正整数. 示例 1: 输入: 6 输出: true 解释: 6 = 2 × 3 示例 2: 输入: 8 输 ...

  5. Java实现 蓝桥杯VIP 算法提高 字符串跳步

    问题描述 给定一个字符串,你需要从第start位开始每隔step位输出字符串对应位置上的字符. 输入格式 第一行一个只包含小写字母的字符串. 第二行两个非负整数start和step,意义见上. 输出格 ...

  6. Java实现 蓝桥杯VIP 算法提高 产生数

    算法提高 产生数 时间限制:1.0s 内存限制:256.0MB 问题描述 给出一个整数 n(n<10^30) 和 k 个变换规则(k<=15). 规则: 一位数可变换成另一个一位数: 规则 ...

  7. java实现第六届蓝桥杯牌型整数

    牌型整数 题目描述 小明被劫持到X赌城,被迫与其他3人玩牌. 一副扑克牌(去掉大小王牌,共52张),均匀发给4个人,每个人13张. 这时,小明脑子里突然冒出一个问题: 如果不考虑花色,只考虑点数,也不 ...

  8. 性能测试中TPS上不去的原因

    TPS(Transaction Per Second):每秒事务数,指服务器在单位时间内(秒)可以处理的事务数量,一般以request/second为单位. 压测中为什么TPS上不去的原因: .网络带 ...

  9. cocos2dx Android 使用ant 批量打包

    参考文章: 例子:http://www.2cto.com/kf/201305/208139.html http://blog.csdn.net/ljb_blog/article/details/127 ...

  10. cacti 流量断图

    问题描述 Cacti监控系统新增了一台设备,后来查询流量的时候发现流量不太对,客户跑的流量远不止8M, 下边就是记录一下问题解决的过程了. 解决过程   看到 rrdtool info 2331.rr ...