闭包(closure)

闭包就是在一个函数定义的内部再定义一个函数,并且这个函数用到了外边函数的变量,那么将这个函数以及用到的一些变量称之为闭包,如:

  1. def line(a, b):
  2. def cal(c):
  3. return a + b + c
  4. return cal

定义了一个line函数,在line内部又定义了一个函数cal,内部函数cal中使用到了外部函数line的变量(a,b)并且line函数返回cal函数,调用例子如下:

  1. print('------line1---------')
  2. line1 = line(1, 2)
  3. print('line1指向:', line1)
  4. print(line1(3))
  5. print(line1(4))
  6. print('--------line2---------')
  7. line2 = line(2, 3)
  8. print('line2指向:', line2)
  9. print(line2(10))
  10. print(line2(20))

运行结果为:

1、可以看出每次调用的line函数,都会返回一个cal方法,且每个cal方法都有一个独立的内存空间,即line1和line2指向的空间地址不一样

2、可以多次调用line1或者line2,且多次调用同一个line1或者line2时,共用了上面的a和b变量

3、line1和line2调用时相互独立,不会互相影响

为什么使用闭包

为什么要使用闭包,它和普通的函数和类又有什么区别,这里举一个例子来说明,将上面的line例子替换成一个一元一次方程方程来理解:y=kx+b,这个方程中k和b值的不同,对应在坐标中的就是不同斜率和不同于y轴相交的线,简单理解就是不同形状的直线。那么现在的需求是通过x的值不同,计算出y的值,比如,当k=1,b=1时,y = x+1,定义成函数即为

  1. def line(x):
  2. return x + 1
  3. print(line(1))
  4. print(line(2))
  5. print(line(3))
  6. # 结果为
  7. 2
  8. 3
  9. 4

而上面函数中,如果我想随便换一个k和b的值,就可以改进为

  1. def line(k, x, b):
  2. return k * x + b
  3. print(line(1, 2, 1))
  4. print(line(1, 2, 2))
  5. print(line(1, 2, 3))
  6. # 结果为
  7. 3
  8. 4
  9. 5

上面两个函数,都各有优缺点:

对于第一个函数:

  优点是:对于同一种形状的线(k和b不变),每次只需要传入x的值就可以了

  缺点是:如果想用其他形状的线,即随意设置k和b,每次都要改函数中的代码

对于第二个函数:

  优点是:可以随意设置k、b、x的值

  缺点是:如果想多次求同一形状的线(k和b不变),那么需要多次重复写相同的k和b参数,如 line(1, 2, 1)、line(1, 2, 2)、line(1, 2, 3),重复写了参数k=1和b=2

可以发现其实他们的优缺点其实是互补的,那么能不能有一种方式可以同时具有上面两种函数的优点呢?

即既可以随意设置k和b值,且对于同一种k和b的搭配,只需设置一次k和b的值就可以保存起来,然后每次调用时只需要传入x即可。这种需求可以通过类来实现,如:

  1. class Line(object):
  2. def __init__(self, k, b):
  3. self.k = k
  4. self.b = b
  5.  
  6. def get_y(self, x):
  7. return self.k * x + self.b
  8.  
  9. print('--------line1--------')
  10. line1 = Line(1, 2)
  11. print(line1.get_y(1))
  12. print(line1.get_y(2))
  13. print(line1.get_y(3))
  14. print('--------line2--------')
  15. line2 = Line(10, 20)
  16. print(line2.get_y(1))
  17. print(line2.get_y(2))
  18. print(line2.get_y(3))
  19.  
  20. # 结果为:
  21. --------line1--------
  22. 3
  23. 4
  24. 5
  25. --------line2--------
  26. 30
  27. 40
  28. 50

这样我们需要不同形状(k和b)的线的时候,就可以创建一次不同的line对象(即不需要重复设置k和b),使用line对象获取y值时(调用get_y),也只需要传入x即可。

通过类确实可以实现需求,但是其实为了一个简单的方法而去创建一个类,有点杀鸡用宰牛刀的意思,因为每次创建Line对象都会调用很多用不到的魔法方法以及继承父类的一些东西等,这样会占用一些不必要的空间或者资源,性价比太低了,而创建函数所需占用的空间比创建类要小得多,那么我们这里就可以使用闭包来实现了。如:

  1. def line(k, b):
  2. def get_y(x):
  3. return k * x + b
  4. return get_y
  5.  
  6. print('--------line1--------')
  7. line1 = line(1, 2)
  8. print(line1(1))
  9. print(line1(2))
  10. print(line1(3))
  11. print('--------line2--------')
  12. line2 = line(10, 20)
  13. print(line2(1))
  14. print(line2(2))
  15. print(line2(3))
  16.  
  17. # 结果为:
  18. --------line1--------
  19. 3
  20. 4
  21. 5
  22. --------line2--------
  23. 30
  24. 40
  25. 50

可以理解为,当调用line(1, 2)时,就会创建一块内存空间,用来存储传入的参数 k=1,b=2 以及 get_y的定义代码,然后将变量名line1指向该空间地址,当调用line1(1)时,就会调用里面的get_y方法,并使用到之前传入的k和b的值。当调用新的line(10, 20)时,又会创建一块新的内存空间存储k=10,b=20 以及 get_y的定义代码。所以每次调用闭包的外层方法时,就像是面向对象中创建了一个类的实例,并开辟了一块独立的内存空间,里面存着实例属性(外层函数的参数,如k和b)和实例方法(内层函数)。

通过nonlocal修改外部函数的局部变量

闭包中,内部函数能够直接使用外部函数的变量和参数,但是不能直接修改,如下面例子:

  1. def line(a):
  2. num = 0
  3. print('初始,num:%d, id:%d' % (num, id(num)))
  4. print('初始,a:%d, id:%d' % (a, id(a)))
  5.  
  6. def get_y():
  7. # nonlocal num, a
  8. num = 1
  9. a = 200
  10. print()
  11. print('get_y内,num:%d, id:%d' % (num, id(num)))
  12. print('get_y内,a:%d, id:%d' % (a, id(a)))
  13. get_y()
  14. print()
  15. print('外,num:%d, id:%d' % (num, id(num)))
  16. print('外,a:%d, id:%d' % (a, id(a)))
  17. return get_y
  18.  
  19. line1 = line(100)

运行结果为:

  1. 初始,num:0, id:140724620236416
  2. 初始,a:100, id:140724620239616
  3.  
  4. get_y内,num:1, id:140724620236448
  5. get_y内,a:200, id:140724620242816
  6.  
  7. 外,num:0, id:140724620236416
  8. 外,a:100, id:140724620239616

1、初始局部变量指向的是....6416和....9616对应的地址,该地址上存着0和100两个值

2、在内部函数get_y中企图修改上面的局部变量对应的值,即创建了两个新地址....6448和....2816,存着1和200,想让上面的局部变量不再指向原来的地址,而是指向新创建的地址

3、但结果发现原局部变量还是指向的原地址,即变量对应的值不变,说明并没有改变原来的值

想要修改外部局部变量时,需要在修改前通过nonlocal声明,如:

  1. def line(a):
  2. num = 0
  3. print('初始,num:%d, id:%d' % (num, id(num)))
  4. print('初始,a:%d, id:%d' % (a, id(a)))
  5.  
  6. def get_y():
  7. nonlocal num, a
  8. num = 1
  9. a = 200
  10. print()
  11. print('get_y内,num:%d, id:%d' % (num, id(num)))
  12. print('get_y内,a:%d, id:%d' % (a, id(a)))
  13. get_y()
  14. print()
  15. print('外,num:%d, id:%d' % (num, id(num)))
  16. print('外,a:%d, id:%d' % (a, id(a)))
  17. return get_y
  18.  
  19. line1 = line(100)

运行结果为:

  1. 初始,num:0, id:140724620236416
  2. 初始,a:100, id:140724620239616
  3.  
  4. get_y内,num:1, id:140724620236448
  5. get_y内,a:200, id:140724620242816
  6.  
  7. 外,num:1, id:140724620236448
  8. 外,a:200, id:140724620242816

发现最后外部的局部变量成功被修改了,即外部的局部变量num和a的指向变成了新的地址....6448和...2816

python-闭包和装饰器-01-闭包(closure)的更多相关文章

  1. python函数下篇装饰器和闭包,外加作用域

    装饰器和闭包的基础概念 装饰器是一种设计模式能实现代码重用,经常用于查日志,性能测试,事务处理等,抽离函数大量不必的功能. 装饰器:1.装饰器本身是一个函数,用于装饰其它函数:2.功能:增强被装饰函数 ...

  2. python学习-42 装饰器 --- 函数闭包1

    函数闭包举例: def father(name): print('hello world') def son(): print('儿子说:我的爸爸是%s' % name) def grandfson( ...

  3. python进阶(三)~~~装饰器和闭包

    一.闭包 满足条件: 1. 函数内嵌套一个函数: 2.外层函数的返回值是内层函数的函数名: 3.内层嵌套函数对外部作用域有一个非全局变量的引用: def func(): print("=== ...

  4. python学习-43 装饰器 -- 函数闭包2

    函数闭包为函数加上认证功能 1.登陆账号 user_dic ={'username':None,'login':False} def auth_func(func): def wrapper(*arg ...

  5. python中的闭包和装饰器

    重新学习完了函数,是时候将其中的一些重点重新捋一捋了,本次总结的东西只有闭包和装饰器 1.闭包 闭包是python函数中的一个比较重要功能,一般闭包都是用在装饰器上,一般学完闭包就会去学习装饰器,这俩 ...

  6. Python 简明教程 --- 22,Python 闭包与装饰器

    微信公众号:码农充电站pro 个人主页:https://codeshellme.github.io 当你选择了一种语言,意味着你还选择了一组技术.一个社区. 目录 本节我们来介绍闭包与装饰器. 闭包与 ...

  7. Python的高级特性7:闭包和装饰器

    本节跟第三节关系密切,最好放在一起来看:python的高级特性3:神奇的__call__与返回函数 一.闭包:闭包不好解释,只能先看下面这个例子: In [23]: def outer(part1): ...

  8. python闭包与装饰器

    转自小马哥: 闭包和装饰器充分体现了Python语法糖的优雅感觉. 在本文中,我们的实验要完成两个工作,一个是加法,一个是累计调用加法的次数,最普通的Python程序可以这么写: def valida ...

  9. python闭包和装饰器

    本文目录: 1. 闭包的解析和用法 2. 函数式装饰器 3. 类装饰器 一.闭包 闭包是一种函数,从形式上来说是函数内部定义(嵌套)函数,实现函数的扩展.在开发过程中,考虑到兼容性和耦合度问题,如果想 ...

  10. 21.python中的闭包和装饰器

    python中的闭包从表现形式上定义(解释)为:如果在一个内部函数里,对在外部作用域(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure). 以下说明主要针对 python ...

随机推荐

  1. 当我们创建HashMap时,底层到底做了什么?

    jdk1.7中的底层实现过程(底层基于数组+链表) 在我们new HashMap()时,底层创建了默认长度为16的一维数组Entry[ ] table.当我们调用map.put(key1,value1 ...

  2. 如何运行Spring Boot项目

    背景 帮别人指导一个Spring Boot项目,它在本地把项目push到git服务器上,然后在部署的服务器上把代码pull下来(我猜应该是这个流程) 然后他问我这项目怎么运行? 我当时就懵了,因为我平 ...

  3. msf stagers开发不完全指北(二)

    采用 Golang 开发stagers 上一篇文章 msf stagers开发不完全指北(一)中我们谈到如何采用 c 进行 msf 的 stagers 开发,这篇文章我们探讨一下如何使用 Golang ...

  4. cp5200的一般步骤

    cp5200的一般步骤: 1.创建数据对象 hObj = CP5200_CommData_Create(nCommType, id, GetIDCode()); 2.生成所需要的数据,如 :生成设置亮 ...

  5. java.math.BigDecimal转换double double转换java.math.BigDecimal

    有方法 java.math.BigDecimal.doubleValue() BigDecimal a = new BigDecimal(1000);return a.doubleValue(); p ...

  6. 《UNIX环境高级编程》(APUE) 笔记第五章 - 标准I/O库

    5 - 标准I/O库 Github 地址 1. 标准 I/O 库作用 缓冲区分配 以优化的块长度执行 I/O 等 使用户不必担心如何选择使用正确的块长度 标准 I/O 最终都要调用第三章中的 I/O ...

  7. 一.1搭建跨平台的统一python开发环境

    搭建跨平台的统一python开发环境: 使用开发环境的好处: 可不用在服务器上直接修改源代码---写的代码首先得入版本库(放git或giitlab中),在本地写代码提交到git中.然后在服务器上git ...

  8. python用类的方式创建线程---自创建类

    用类的方式创建线程---自创建类 import threadingimport time class MyThread(threading.Thread):#自建MyThread类继承threadin ...

  9. SELinux已经允许,为什么日志显示的仍然是denied?

    从日志可以看到,SELinux的Mode已经修改位了permissive = 1,也就是允许模式,但它前面的日志仍然显示的是“denied".本来我还以为是自己哪里没弄好导致的这个问题,但访 ...

  10. 关于数据文件的文件头1-P2

    文章目录 1 疑问点 2 问题模拟 2.1 dump 0,1块 2.2 查看trc文件 2.3 如何查看 1 疑问点 这里引用p2处的一段话: 事实上,每个文件的前128个块,都是文件头,被Oracl ...