详解python中的生成器、迭代器、闭包、装饰器


Posted in Python onAugust 22, 2019

迭代是访问集合元素的一种方式。迭代器是一个可以记住遍历的位置的对象。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。

1|1可迭代对象

以直接作用于 for 循环的数据类型有以下几种:

  • 一类是集合数据类型,如 list 、 tuple 、 dict 、 set 、 str 等;
  • 一类是 generator ,包括生成器和带 yield 的generator function。

这些可以直接作用于 for 循环的对象统称为可迭代对象: Iterable 。

1|2判断是否可以迭代

可以使用 isinstance() 判断一个对象是否是 Iterable 对象:

from collections import Iterable
isinstance([],Iterable)
# True
isinstance({},Iterable)
# True
isinstance(123,Iterable)
# False
isinstance((x for x in range(10)),Iterable)
# True

1|3什么是迭代器

可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。

可以使用 isinstance() 判断一个对象是否是 Iterator 对象:

from collections import Iterator
isinstance([],Iterator)
False
isinstance({},Iterator)
False
isinstance((x for x in range(10)),Iterator) # 
True

生成器都是迭代器。

1|4iter()函数

虽然list 、 tuple 、 dict 、 set 、 str 等是可迭代对象,但他们不是迭代器。可以通过iter()函数把可迭代对象编程迭代器。

isinstance(iter([]),Iterator)
# True
isinstance(iter({}),Iterator)
# True
isinstance(iter("asdf"),Iterator)
# True

1|5总结:

  • 凡是可作用于 for 循环的对象都是 Iterable 类型。
  • 凡是可作用于 next() 函数的对象都是 Iterator 类型。
  • 集合数据类型如 list 、 dict 、 str 等是 Iterable 但不是 Iterator ,不过可以通过 iter() 函数获得一个 Iterator 对象。

2|0生成器

2|1什么是生成器

我们可以通过列表生成式来创建一个列表,但是收到内存的限制,列表的容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。

1|1修改列表推导式创建生成器的方法

最简单的方法是把列表生成式中的 [ ] 改成 ( ) 就好了。

a = [x for x in range(10)]
print(a) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
b = (x for x in range(10))
print(b) # <generator object <genexpr> at 0x03387DB0>

如何遍历生成器

我们发现生成器不是能直接打印出来的,我们可以通过next()函数来获得生成器的下一个返回值。

生成器保存的是算法,每次调用 next(G) ,就计算出 G 的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出 StopIteration 的异常。

**使用next() 或者__next __():**
print(next(b))
# 0
print(next(b))
# 1
print(next(b))
# 2
print(next(b))
# 3
print(next(b))
# 4
print(next(b))
# 5
print(b.__next__())
# 6
print(b.__next__())
# 7
print(b.__next__())
# 8
print(b.__next__())
# 9
print(b.__next__())
# Traceback (most recent call last):
# File "<input>", line 2, in <module>
# StopIteration

那么有什么简单的方法呢?因为生成器是可迭代对象,也可以使用for循环来遍历它,并且不需要关心 StopIteration 异常。

b = (x for x in range(10))
for x in b:
 print(x)
# 0
# 1
# 2
# 3
#...

1|2函数中使用yield创建生成器的方法

如果如果生成器推算的算法比较复杂,用类似列表生成式的 for 循环无法实现的时候,还可以用函数来实现。把你要返回的值前面加yield 即可。

使用函数实现上面代码:

def fn():
 for x in range(3):
  yield x
# 遍历函数实现的生成器
f = fn()
print(next(f))
# 0
print(next(f))
# 1
print(next(f))
# 2
print(next(f))
# Traceback (most recent call last):
# File "<input>", line 1, in <module>
# StopIteration

使用生成器实现斐波拉契数列:

def fib(count):
 n = 0
 a,b = 0,1
 while n < count:
  yield b
  a,b = b,a+b
  n += 1
 return "done"
f = fib(5)
print(next(f))
# 1
print(next(f))
# 1
print(next(f))
# 2
print(next(f))
# 3
print(next(f))
# 5
print(next(f))
# Traceback (most recent call last):
# File "<input>", line 1, in <module>
# StopIteration: done

yield执行流程

  • 当执行next(f)时,函数开始执行到yield,yield 右边的变量x作为next()的返回值被返回,此时函数保存当前的运行状态,并暂停执行。
  • 再次调用next(f)时,函数从上次暂停的位置开始继续执行,再次遇到yield时重复上面的操作
  • 直到生成器遍历结束

我们在循环过程中不断调用 yield ,就会不断中断。当然要给循环设置一个条件来退出循环,不然就会产生一个无限数列出来。同样的,把函数改成generator后,我们基本上从来不会用 next() 来获取下一个返回值,而是直接使用 for 循环来迭代:

for x in fib(5):
 print(x)
# 1
# 1
# 2
# 3
# 5

但是用for循环调用generator时,发现拿不到generator的return语句的返回值。如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIteration的value中:

f = fib(5)
while True:
 try:
  print(next(f))
 except StopIteration as e:
  print("生成器返回值:%s"%e.value)
  break
# 1
# 1
# 2
# 3
# 5
# 生成器返回值:done

1|3send方法

def gen():
 i = 0
 while i<3:
  temp = yield i
  print(temp)
  i+=1
g = gen()
print(g.__next__())
# 0
print(g.send(None))
# None
# 1
print(next(g))
# None
# 2
print(g.send("哈哈")
# 哈哈
# Traceback (most recent call last):
# File "<input>", line 1, in <module>
# StopIteration

上面代码可以看出next()、next ()、send(None)是等价的并没有什么区别。

  • send()其实是比他们更高级的,在之前的代码中yield i是没有返回值的即输出为None。
  • 如果修改send()的形参,那么yield i 的返回值就是括号中的形参,在上面的代码中g.send("哈哈")相当于temp = "哈哈",并且g.send("哈哈")的返回值就是变量i。
  • 使用send时要注意,第一次调用生成器对象时,send()不能传参数否则会报错,第一次必须是send(None),或者第一次调
  • 用next()、next ()也可以。

3|0闭包

3|1什么是闭包

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

def test(number):
 def test_in(number_in):
  print("test_in函数 的number_in=%s"%number_in)
  return number_in+number
 # 返回test_in函数的引用
 return test_in
ret = test(20)
print(ret(100)) # 相当于直接调用test_in函数,并给它传值100
# test_in函数 的number_in=100
# 120
print(ret(200))
# test_in函数 的number_in=200
# 220

3|2闭包的一个例子

在数学中,一次函数:y=kx+b,在一条确定的直线中,它的k、b是不变的。求y时,根据确定的k、b、x来求出。

def line_conf(k, b):
 def line(x):
  return k*x + b
 return line

line1 = line_conf(1, 1)
line2 = line_conf(4, 5)
print(line1(5))
print(line2(5))
# 6
# 25

如果没有闭包,我们需要每次创建直线函数的时候同时说明a,b,x。这样,我们就需要更多的参数传递,也减少了代码的可移植性。

4|0装饰器
4|1什么是装饰器

装饰器就是对一个函数进行装饰,给这个函数增加额外的功能。

def logging(func):
 def wrap():
  print("正在打印日志!")
  func()
 return wrap

@logging # 该装饰器为函数增加了打印日志的额外功能,并且之前函数内部代码不会改变。

def login():
 print("张三正在登陆。")
login()
# 正在打印日志!
# 张三正在登陆。
4|2两个装饰器
def makeBold(fn1):
 def wrapped():
  print("----1----")
  return "<b>"+fn1()+"</b>"
 return wrapped

def makeItalic(fn2):
 def wrapped():
  print("----2----")
  return "<i>"+fn2()+"</i>"
 return wrapped

@makeBold
@makeItalic
def f1():
 print("----3----")
 return "hello world"

ret = f1() # 此时f1并不是f1函数,它是makeBold装饰器返回的wrapped函数的引用。
print(ret)
"""
输出结果:
----1----
----2----
----3----
<b><i>hello world</i></b>
"""

调用流程:

  1. 把函数f1的引用传入装饰器makeItalic中的变量fn2,此时fn2指向f1函数。
  2. 把装饰器makeItalic中wrapped函数的引用传入装饰器makeBold的变量fn1,此时fn1指向装饰器makeItalic中的wrapped函数。
  3. ret = f1()表是执行f1所指向的函数,并返回给ret。

4|3装饰器带参数

一般情况下装饰器内部函数的参数都是不定长参数,保证通用性,确保装饰任何函数时都不会出错。

def logging(func):
 def wrap(*args,**kwargs):
  print("正在打印日志!")
  func(*args,**kwargs)
 return wrap

@logging # 该装饰器为函数增加了打印日志的额外功能,并且之前函数内部代码不会改变。

def login(name,dic):
 print("%s正在登陆。"%name)
 print(dic)
login("李四",{"sex":"男"})
# 正在打印日志!
# 李四正在登陆。
# {'sex': '男'}
4|4类装饰器
class Test(object):
 def __init__(self, func):
  print("---初始化---")
  print("func name is %s"%func.__name__)
  self.__func = func
 def __call__(self):
  print("---装饰器中的功能---")
  self.__func()
@Test
def test():
 print("----test---")
test()
"""

输出结果:

---初始化---
func name is test
---装饰器中的功能---
----test---
"""

说明:

  • 当用Test来装作装饰器对test函数进行装饰的时候,首先会创建Test的实例对象并且会把test这个函数名当做参数传递到__init__方法中即在__init__方法中的func变量指向了test函数体。
  • test函数相当于指向了用Test创建出来的实例对象。
  • 当在使用test()进行调用时,就相当于让这个对象(),因此会调用这个对象的__call__方法。
  • 为了能够在__call__方法中调用原来test指向的函数体,所以在__init __方法中就需要一个实例属性来保存这个函数体的引用所以才有了self.__func = func这句代码,从而在调用__call __方法中能够调用到test之前的函数体。

总结

以上所述是小编给大家介绍的python中的生成器、迭代器、闭包、装饰器,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

Python 相关文章推荐
Python使用代理抓取网站图片(多线程)
Mar 14 Python
Python django使用多进程连接mysql错误的解决方法
Oct 08 Python
python pyheatmap包绘制热力图
Nov 09 Python
python3人脸识别的两种方法
Apr 25 Python
python实现机器人卡牌
Oct 06 Python
利用Python绘制有趣的万圣节南瓜怪效果
Oct 31 Python
让你的Python代码实现类型提示功能
Nov 19 Python
Python3的unicode编码转换成中文的问题及解决方案
Dec 10 Python
Python如何基于rsa模块实现非对称加密与解密
Jan 03 Python
学会python自动收发邮件 代替你问候女友
May 20 Python
用pushplus+python监控亚马逊到货动态推送微信
Jan 29 Python
python程序的组织结构详解
Dec 06 Python
python支付宝支付示例详解
Aug 22 #Python
关于python3中setup.py小概念解析
Aug 22 #Python
python3 requests库文件上传与下载实现详解
Aug 22 #Python
python3使用print打印带颜色的字符串代码实例
Aug 22 #Python
Python检查 云备份进程是否正常运行代码实例
Aug 22 #Python
浅谈Python 递归算法指归
Aug 22 #Python
python求加权平均值的实例(附纯python写法)
Aug 22 #Python
You might like
改写ThinkPHP的U方法使其路由下分页正常
2014/07/02 PHP
php常量详细解析
2015/10/27 PHP
PHP新建类问题分析及解决思路
2015/11/19 PHP
PHP的Yii框架中移除组件所绑定的行为的方法
2016/03/18 PHP
PHP基于堆栈实现的高级计算器功能示例
2017/09/15 PHP
PHP按一定比例压缩图片的方法
2018/10/12 PHP
Javascript 去除数组的重复元素
2010/05/04 Javascript
jquery限制输入字数,并提示剩余字数实现代码
2012/12/24 Javascript
获取内联和链接中的样式(js代码)
2013/04/11 Javascript
JavaScript实现LI列表数据绑定的方法
2015/08/04 Javascript
javascript实现tab切换的四种方法
2015/11/05 Javascript
JavaScript从0开始构思表情插件
2016/07/26 Javascript
jquery 校验中国身份证号码实例详解
2017/04/11 jQuery
详解angular ui-grid之过滤器设置
2017/06/07 Javascript
vue中使用sessionStorage记住密码功能
2018/07/24 Javascript
JQuery实现ajax请求的示例和注意事项
2018/12/10 jQuery
JS实现判断移动端PC端功能
2020/02/21 Javascript
js+canvas实现转盘效果(两个版本)
2020/09/13 Javascript
[07:49]2014DOTA2国际邀请赛 Newbee夺冠后采访xiao8坦言奖金会上交
2014/07/23 DOTA
python安装教程
2018/02/28 Python
Python使用matplotlib绘制多个图形单独显示的方法示例
2018/03/14 Python
Python 实现在文件中的每一行添加一个逗号
2018/04/29 Python
pytorch点乘与叉乘示例讲解
2019/12/27 Python
Python 线性回归分析以及评价指标详解
2020/04/02 Python
Python垃圾回收机制三种实现方法
2020/04/27 Python
公认8个效率最高的爬虫框架
2020/07/28 Python
HTML5 与 XHTML2
2008/10/17 HTML / CSS
来自Ocado的宠物商店:Fetch
2018/07/10 全球购物
测绘工程本科生求职信
2013/10/10 职场文书
最受欢迎的自我评价
2013/12/22 职场文书
护理专科毕业生自荐书范文
2014/02/19 职场文书
小学阳光体育活动总结
2014/07/05 职场文书
2014年创先争优工作总结
2014/12/11 职场文书
导游词之新疆尼雅遗址
2019/10/16 职场文书
5行Python代码实现一键批量扣图
2021/06/29 Python
【海涛DOTA】D-cup邀请赛NV.cn vs DT.Love
2022/04/01 DOTA