本文的文字及图片来源于网络,仅供学习、交流使用,不具有任何商业用途,如有问题请及时联系我们以作处理

最近阅读《流畅的python》看见其用函数写装饰器部分写的很好,想写一些自己的读书笔记。众所周知,装饰器是python学习过程中的一道门槛,初学者学习时往往是知其然,不知其所以然,这样的结果是导致一段时间后会遗忘掉该部分内容,只好再次去学习,拉高了学习成本。

想学好python的装饰器,需要明白一下几点;

1:闭包

1)函数嵌套

2)内部函数使用外部函数的变量

3)外部函数的返回值为内部函数

​ 接下来看看《流畅的python》中的例子,我稍微修改了一下:

  1. >>> def make_averager(series=[]):
  2. ... def averager(new_value):
  3. ... series.append(new_value)
  4. ... total = sum(series)
  5. ... return total/len(series)
  6. ... return averager
  7. ...
  8. >>> avg = make_averager()
  9. >>> avg
  10. <function make_averager.<locals>.averager at 0x10b82cb00>
  11. >>> avg(10)
  12. 10.0
  13. >>> avg(11)
  14. 10.5
  15. >>> avg(12)
  16. 11.0

​ 函数 make_averager 实现了一个 计算当前所有数字的平均值的功能,不断的添加一个值,然后计算当前的平均值。

​ avg这个对象内存地址指向了make_averager这个函数的内部函数中,而且avg通过不断的添加值进行平均值计算,按理说在这个内部函数没有存储new_value的空间,而且在make_averager对avg赋值后,函数返回后series这个变量也应该消失了,但是avg却依然可以进行计算。

​ 这就是闭包,内部函数averager使用外面的自由变量,也就是属于make_averager的局部变量series

  1. >>> avg.__code__.co_varnames
  2. ('new_value', 'total')
  3. >>> avg.__code__.co_freevars
  4. ('series',)

​ 可以发现avg的自由变量是make_averager的局部变量,就是说闭包里的内部函数可以使用外部函数的变量,即我们上面提到的第二点:“内部函数使用外部函数的变量”, 注:自由变量只能read,并不能write,不然会提示本地变量并没有赋值的错误,我们举的例子没遇到这个问题,因为我们没有给 series 赋值,我们只是调 用 series.append,并把它传给 sum 和 len。也就是说,我们利用了 列表是可变的对象这一事实 。下图是书中提供的闭包范围图:

2:装饰器的实现

所谓装饰器,就是在不改变基础函数的功能上再次给它封装一层,达到我们想要的目的,接下来我举个简单的例子:

​ deco_demo.py

  1. 1 def col(func):
  2. 2 def inner(*args, **kwargs):
  3. 3 print(func.__name__)
  4. 4 print(locals())
  5. 5 print(inner.__code__.co_varnames)
  6. 6 print(inner.__code__.co_freevars)
  7. 7 return func(*args, **kwargs)
  8. 8 return inner
  9. 9
  10. 10
  11. 11 @col
  12. 12 def new_add(x):
  13. 13 return x+2
  14. 14
  15. 15
  16. 16 def new_add_1(x):
  17. 17 return x+3
  18. 18
  19. 19
  20. 20 print(new_add(3))
  21. 21
  22. 22 new_add_1 = col(new_add_1)
  23. 23 print(new_add_1(3))

下方是它的返回结果:

  1. new_add
  2. {'args': (3,), 'kwargs': {}, 'func': <function new_add at 0x10d32aa70>, 'inner': <function col.<locals>.inner at 0x10d32acb0>}
  3. ('args', 'kwargs')
  4. ('func', 'inner')
  5. 5
  6. new_add_1
  7. {'args': (3,), 'kwargs': {}, 'func': <function new_add_1 at 0x10d32add0>, 'inner': <function col.<locals>.inner at 0x10d32a8c0>}
  8. ('args', 'kwargs')
  9. ('func', 'inner')
  10. 6

1-8:是定义的一个简单装饰器,

3:打印当被装饰函数的名字

4:打印inner这个内部函数中的所有变量

5:打印当前inner的局部变量;

6:则打印自由变量;

11-13:修饰了一个简单函数

16,22,23:@这个语法糖,背后实现的过程;

​ 也就是说 col(new_add) 返回的是当前的内部函数的内存地址,而这个调用这个内部函数时会使用自由变量func即col的局部变量,进而达到装饰器的目的;

有参数的装饰器实现

​ 既然无参数的装饰器即@col ,通过内部函数的方式装饰基础函数,那么我们调用有参数的装饰器 则可以再原本的基础即函数col再封装一层函数,使其达到可以通过装饰器传参数的目的

  1. 1 from functools import wraps
  2. 2
  3. 3
  4. 4 def col(string="hello world"):
  5. 5 def decorate(func):
  6. 6 @wraps(func)
  7. 7 def inner(*args, **kwargs):
  8. 8 print(string)
  9. 9 return func(*args, **kwargs)
  10. 10 return inner
  11. 11 return decorate
  12. 12
  13. 13
  14. 14 @col()
  15. 15 def new_add(x):
  16. 16 return x+2
  17. 17
  18. 18
  19. 19 @col("hello python")
  20. 20 def new_add_1(x):
  21. 21 return x+3
  22. 22
  23. 23
  24. 24 def new_add_2(x):
  25. 25 return x+4
  26. 26
  27. 27
  28. 28 print(new_add(1))
  29. 29 print(new_add_1(1))
  30. 30
  31. 31
  32. 32 new_add_2 = col("hello china")(new_add_2)
  33. 33 print(new_add_2(1))

导入wrap是为了修复这个装饰器的名称, new_add.__name__ 调用时指向被装饰的函数,而不是内部函数,有兴趣的小伙伴可以去了解一下;

4-11:实现了一个带参数的装饰器,最外层返回的是我们真正的装饰器;

32-33:则是@这个装饰器语法糖背后的实现过程

可以发现new_add与new_add_1这两个函数的装饰器是两个不同值,而我们的装饰器也返回了不同的对应情况

  1. hello world
  2. 3
  3. hello python
  4. 4
  5. hello china
  6. 5

间而言之:装饰器就是在我们需要添加功能的函数上进而封装一层,而python的语法糖@背后,帮助我们省略掉了这些赋值的过程;

3:装饰器何时调用

关于装饰器何时运行,我们分两种情况讨论,一种是当作脚本运行时,另一种是当作模块被导入时;

  1. 1 registry = []
  2. 2
  3. 3
  4. 4 def register(func):
  5. 5 print(f"running register {func}")
  6. 6 registry.append(func)
  7. 7 return func
  8. 8
  9. 9
  10. 10 @register
  11. 11 def f1():
  12. 12 print('running f1()')
  13. 13
  14. 14
  15. 15 @register
  16. 16 def f2():
  17. 17 print('running f2()')
  18. 18
  19. 19
  20. 20 def f3():
  21. 21 print('running f3()')
  22. 22
  23. 23
  24. 24 def main():
  25. 25 print('running main()')
  26. 26 print('regisry ->', registry)
  27. 27 f1()
  28. 28 f2()
  29. 29 f3()
  30. 30
  31. 31
  32. 32 if __name__ == '__main__':
  33. 33 main()

当作独立脚本运行时:

  1. running register <function f1 at 0x103f9dcb0>
  2. running register <function f2 at 0x103f9ddd0>
  3. running main()
  4. regisry -> [<function f1 at 0x103f9dcb0>, <function f2 at 0x103f9ddd0>]
  5. running f1()
  6. running f2()
  7. running f3()

被当作模块导入时:

  1. >>> import registration
  2. running register <function f1 at 0x1005a2710>
  3. running register <function f2 at 0x1005a2b90>
  4. >>> registration.registry
  5. [<function f1 at 0x1005a2710>, <function f2 at 0x1005a2b90>]

该段代码的装饰器主要功能是:记录了被装饰函数的个数,通常是web框架以这种方式把函数注册到中央注册器的某处。

总结:可以发现装饰器无论是作为模块被导入,还是单独的脚本运行,它都是优先执行的;

4:装饰器的常用模块

之前介绍的function.wraps不用说了,接下来介绍两种神奇的装饰器;

1:singledispatch

何为singledispatch ?

就是在不改变函数本身的功能上复用该函数,达到重复使用函数名的目的,有点类似多态的感觉;可以把整体方案拆分成多个模块,甚至可以为你无法修改的类提供专门的函数。使用@singledispatch 装饰的普通函数会变成泛函数(generic function);根据第一个参数的类型,以不同方式执行相同操作的一组函数

  1. 1 from functools import singledispatch
  2. 2
  3. 3
  4. 4 @singledispatch
  5. 5 def hello(obj):
  6. 6 print(obj)
  7. 7
  8. 8
  9. 9 @hello.register(str)
  10. 10 def _(text):
  11. 11 print("hello world "+text)
  12. 12
  13. 13
  14. 14 @hello.register(int)
  15. 15 def _(n):
  16. 16 print(n)
  17. 17
  18. 18
  19. 19 hello({"what": "say"})
  20. 20 print('*'*30)
  21. 21 hello('dengxuan')
  22. 22 print('*'*30)
  23. 23 hello(123)
  1. {'what': 'say'}
  2. ******************************
  3. hello world dengxuan
  4. ******************************
  5. 123

从该段代码中我们可以发现,当使用singledispatch这个装饰器时,函数hello可以根据不同的参数返回不同的结果。这样的好处就是极大的减少代码中的if/elif/else,并且可以复用函数名称 _ (下横线代表没用),降低了代码的耦合度,达到了多态的效果。

2:lru_cache

根据书上原话:

functools.lru_cache 是非常实用的装饰器,它实现了备忘 (memoization)功能。这是一项优化技术,它把耗时的函数的结果保存 起来,避免传入相同的参数时重复计算。LRU 三个字母是“Least Recently Used”的缩写,表明缓存不会无限制增长,一段时间不用的缓存 条目会被扔掉。

  1. 1 from my_tools.runtime import clock
  2. 2 import functools
  3. 3
  4. 4
  5. 5 @functools.lru_cache()
  6. 6 @clock
  7. 7 def fibonacci(n):
  8. 8 if n < 2:
  9. 9 return n
  10. 10 return fibonacci(n-2)+fibonacci(n-1)
  11. 11
  12. 12
  13. 13 if __name__ == '__main__':
  14. 14 print(fibonacci(6))

第5行:注释funtools.lru_cache()

返回结果:

  1. [0.00000046] fibonacci(0) -> 0
  2. [0.00000053] fibonacci(1) -> 1
  3. [0.00006782] fibonacci(2) -> 1
  4. [0.00000030] fibonacci(1) -> 1
  5. [0.00000035] fibonacci(0) -> 0
  6. [0.00000037] fibonacci(1) -> 1
  7. [0.00001312] fibonacci(2) -> 1
  8. [0.00002514] fibonacci(3) -> 2
  9. [0.00010535] fibonacci(4) -> 3
  10. [0.00000030] fibonacci(1) -> 1
  11. [0.00000030] fibonacci(0) -> 0
  12. [0.00000037] fibonacci(1) -> 1
  13. [0.00001209] fibonacci(2) -> 1
  14. [0.00002376] fibonacci(3) -> 2
  15. [0.00000028] fibonacci(0) -> 0
  16. [0.00000038] fibonacci(1) -> 1
  17. [0.00001210] fibonacci(2) -> 1
  18. [0.00000028] fibonacci(1) -> 1
  19. [0.00000036] fibonacci(0) -> 0
  20. [0.00000034] fibonacci(1) -> 1
  21. [0.00001281] fibonacci(2) -> 1
  22. [0.00002466] fibonacci(3) -> 2
  23. [0.00004897] fibonacci(4) -> 3
  24. [0.00008414] fibonacci(5) -> 5
  25. [0.00020196] fibonacci(6) -> 8
  26. 8

当取消掉第5行注释时;

  1. [0.00000040] fibonacci(0) -> 0
  2. [0.00000049] fibonacci(1) -> 1
  3. [0.00008032] fibonacci(2) -> 1
  4. [0.00000066] fibonacci(3) -> 2
  5. [0.00009398] fibonacci(4) -> 3
  6. [0.00000063] fibonacci(5) -> 5
  7. [0.00010943] fibonacci(6) -> 8
  8. 8

可以发现,lru_cache()这个装饰器,极大的提高了计算性能;

maxsize 参数指定存储多少个调用的结果。缓存满了之后,旧的结果会被扔掉,腾出空间。为了得到最佳性能,maxsize 应该设为 2 的 幂。typed 参数如果设为 True,把不同参数类型得到的结果分开保存,即把通常认为相等的浮点数和整数参数(如 1 和 1.0)区分开。顺 便说一下,因为 lru_cache 使用字典存储结果,而且键根据调用时传 入的定位参数和关键字参数创建,所以被 lru_cache 装饰的函数,它的所有参数都必须是可散列的。

5:多重装饰器

  1. @d1
  2. @d2
  3. def f():
  4. print('hello world')
  5.  
  6. ###########################
  7.  
  8. def f():
  9. print("hello world")
  10.  
  11. f = d1(d2(f))

上下两块代码是等效效果;

想要获取更多Python学习资料可以加
QQ:2955637827私聊
或加Q群630390733
大家一起来学习讨论吧!

python装饰器学习详解-函数部分的更多相关文章

  1. python装饰器大详解

    1.作用域 在python中,作用域分为两种:全局作用域和局部作用域. 全局作用域是定义在文件级别的变量,函数名.而局部作用域,则是定义函数内部. 关于作用域,我要理解两点:a.在全局不能访问到局部定 ...

  2. python装饰器使用详解

    装饰器 '''装饰器:就是闭包(闭包的一个应用场景) -- 把要被装饰的函数作为外层函数的参数通过闭包操作后返回一个替代版函数 优点: -- 丰富了原有函数的功能 -- 提高了程序的可拓展性''' 开 ...

  3. Python 装饰器学习

    Python装饰器学习(九步入门)   这是在Python学习小组上介绍的内容,现学现卖.多练习是好的学习方式. 第一步:最简单的函数,准备附加额外功能 1 2 3 4 5 6 7 8 # -*- c ...

  4. (转载)Python装饰器学习

    转载出处:http://www.cnblogs.com/rhcad/archive/2011/12/21/2295507.html 这是在Python学习小组上介绍的内容,现学现卖.多练习是好的学习方 ...

  5. Python装饰器学习

    Python装饰器学习(九步入门)   这是在Python学习小组上介绍的内容,现学现卖.多练习是好的学习方式. 第一步:最简单的函数,准备附加额外功能 ? 1 2 3 4 5 6 7 8 # -*- ...

  6. python 函数及变量作用域及装饰器decorator @详解

    一.函数及变量的作用   在python程序中,函数都会创建一个新的作用域,又称为命名空间,当函数遇到变量时,Python就会到该函数的命名空间来寻找变量,因为Python一切都是对象,而在命名空间中 ...

  7. 进阶Python:装饰器 全面详解

    进阶Python:装饰器 前言 前段时间我发了一篇讲解Python调试工具PySnooper的文章,在那篇文章开始一部分我简单的介绍了一下装饰器,文章发出之后有几位同学说"终于了解装饰器的用 ...

  8. Python 装饰器学习心得

    最近打算重新开始记录自己的学习过程,于是就捡起被自己废弃了一年多的博客.这篇学习笔记主要是记录近来看的有关Python装饰器的东西. 0. 什么是装饰器? 本质上来说,装饰器其实就是一个特殊功能的函数 ...

  9. python 装饰器学习(decorator)

    最近看到有个装饰器的例子,没看懂, #!/usr/bin/python class decorator(object): def __init__(self,f): print "initi ...

随机推荐

  1. linux(centos7.x)安装jdk

    一.下载与安装 下载地址:链接:https://pan.baidu.com/s/1g7MF1xqlOxWnLGf2shl3NA   提取码:epae  下载完成后将安装包上传到linxu环境中,并将其 ...

  2. CoProcessFunction实战三部曲之一:基本功能

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  3. Oracle11gR2 sqlplus中可以执行上键查询backspace删除

    1.1 sqlplus中可以执行上键查询backspace删除 1.1.1 上键查询 方法1: 安装源-导入key-安装rpm包-进入配置文件修改参数 rpm -ivh http://download ...

  4. 初探Lerna

    1.简介 首先是关于Monorepo(一篇不错的介绍Monorepo的文章),它是管理项目代码的一种方式,主要手段是通过在一个项目仓库中管理多个模块/仓库包.而Multirepo是传统的仓库管理方法, ...

  5. 痞子衡嵌入式:一个奇怪的Keil MDK下变量链接强制对齐报错问题(--legacyalign)

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家分享的是一个奇怪的Keil MDK下变量链接强制对齐报错问题. 痞子衡最近一直在参与恩智浦SBL项目(就是一个适用LPC和i.MXRT的完整OT ...

  6. jwt介绍

    jwt原理 最简单理解:jwt本质就是, 把用户信息通过加密后生成的一个字符串 JWT的原则是在服务器身份验证之后,将生成一个JSON对象并将其发送回用户 { "UserName" ...

  7. PyQt(Python+Qt)学习随笔:QDockWidget停靠部件的setTitleBarWidget方法

    setTitleBarWidget方法用于给停靠窗口设置个性化的标题栏,调用语法如下: setTitleBarWidget(QWidget widget) 说明: widget参数可以是任意一个QWi ...

  8. Python文件学习遇到的问题

    关于open函数文件打开模式的有意思的一个现象 关于Python中中文文本文件使用二进制方式读取后的解码UnicodeDecodeError问题 Python中str类型的字符串写入二进制文件时报Ty ...

  9. PyQt(Python+Qt)学习随笔:树型部件QTreeWidget中的项编辑方法editTriggers、editItem和openPersistentEditor作用及对比分析

    老猿Python博文目录 专栏:使用PyQt开发图形界面Python应用 老猿Python博客地址 在树型部件QTreeWidget中,有三种方法触发进行项数据的编辑:editTriggers触发编辑 ...

  10. Microsoft工具之Disk2vhd

    Official documents:https://docs.microsoft.com/zh-cn/sysinternals/downloads/disk2vhd 1.Introduction D ...