lee-romantic 's Blog
Everything is OK!
Toggle navigation
lee-romantic 's Blog
主页
About Me
归档
标签
python中的迭代器和生成器
2018-12-03 16:17:10
384
0
0
lee-romantic
**一.迭代器:** https://www.jianshu.com/p/1b0686bc166d python里面有很多的以`__`开始和结尾的函数,利用它们可以完成很多复杂的逻辑代码,而且提高了代码的简洁性,本文主要总结了迭代器用到的魔术方法,并且主要以代码例子进行解释。 `__iter__` 和 `__next__` 其实这里需要引入一个概念,叫迭代器,常见的就是我们在使用for语句的时候,python内部其实是把for后面的对象上使用了内建函数iter,比如: ``` a = [1, 2, 3] for i in a: do_something() ``` 其实在python内部进行了类似如下的转换: ``` a = [1, 2, 3] for i in iter(a): do_something() ``` 那么iter返回的是什么呢,就是一个迭代对象,它主要映射到了类里面的`__iter__`函数,此函数返回的是一个实现了`__next__`的对象。注意理解这句话,比如: ``` class B(object): def __next__(self): raise StopIteration class A(object): def __iter__(self): return B() ``` 我们可以看见,`A`这个类实现了一个`__iter__`函数,返回的是`B()`的实例对象,其中`B`里面实现了`__next__`这个函数。 下面引入几个概念: **`Iterable:`** 有迭代能力的对象,一个类,实现了`__iter__`,那么就认为它有迭代能力,通常此函数必须返回一个实现了`__next__`的对象,如果自己实现了,你可以返回`self`,当然这个返回值不是必须的; **`Iterator:`** 迭代器(当然也是`Iterable`),同时实现了`__iter__`和`__next__`的对象,缺少任何一个都不算是`Iterator`,比如上面例子中,`A()`可以是一个`Iterable`,但是`A()`和`B()`都不能算是和`Iterator`,因为A只实现了`__iter__`,而B只实现了`__next__()`。 我们可以使用collections里面的类型来进行验证: ``` class B(object): def __next__(self): raise StopIteration class A(object): def __iter__(self): return B() from collections.abc import * a = A() b = B() print(isinstance(a, Iterable)) print(isinstance(a, Iterator)) print(isinstance(b, Iterable)) print(isinstance(b, Iterator)) ``` 结果是: ``` True False False False ``` 让我们稍微对B这个类做一点修改: ``` class B(object): def __next__(self): raise StopIteration def __iter__(self): return None class A(object): def __iter__(self): return B() from collections.abc import * a = A() b = B() print(isinstance(a, Iterable)) print(isinstance(a, Iterator)) print(isinstance(b, Iterable)) print(isinstance(b, Iterator)) ``` 结果是: ``` True False True True ``` **真正的迭代器** 上面只是做了几个演示,这里具体说明一下: 当调用iter函数的时候,生成了一个`迭代对象`,要求`__iter__`必须返回一个实现了`__next__`的对象,我们就可以通过next函数访问这个对象的下一个元素了,并且在你不想继续有迭代的情况下抛出一个`StopIteration`的异常(for语句会捕获这个异常,并且自动结束for),下面实现了一个自己的类似range函数的功能。 ``` class MyRange(object): def __init__(self, end): self.start = 0 self.end = end def __iter__(self): return self def __next__(self): if self.start < self.end: ret = self.start self.start += 1 return ret else: raise StopIteration from collections.abc import * a = MyRange(5) print(isinstance(a, Iterable)) print(isinstance(a, Iterator)) for i in a: print(i) ``` 结果是: ``` True True 0 1 2 3 4 ``` 接下来我们使用next函数模拟一次: ``` class MyRange(object): def __init__(self, end): self.start = 0 self.end = end def __iter__(self): return self def __next__(self): if self.start < self.end: ret = self.start self.start += 1 return ret else: raise StopIteration a = MyRange(5) print(next(a)) print(next(a)) print(next(a)) print(next(a)) print(next(a)) print(next(a)) # 其实到这里已经完成了,我们在运行一次查看异常 ``` 可以看见一个很明显的好处是,每次产生的数据,是产生一个用一个,什么意思呢,比如我要遍历[0, 1, 2, 3.....]一直到10亿,如果使用列表的方式,那么是会全部载入内存的,但是如果使用迭代器,可以看见,当用到了(也就是在调用了next)才会产生对应的数字,这样就可以节约内存了,这是一种懒惰的加载方式。 **总结** 可以使用`collection.abs`里面的`Iterator`和`Iterable`配合isinstance函数来判断一个对象是否是可迭代的,是否是迭代器对象 iter实际是映射到了`__iter__`函数 只要实现了`__iter__`的对象就是`可迭代对象(Iterable)`,正常情况下,应该返回一个实现了`__next__`的对象(虽然这个要求不强制),如果自己实现了`__next__`,当然也可以返回自己 同时实现了`__iter__`和`__next__`的是`迭代器(Iterator)`,当然也是一个可迭代对象了,其中`__next__`应该在迭代完成后,抛出一个`StopIteration异常` for语句会自动处理这个StopIteration异常以便结束for循环 作者:Liburro 链接:https://www.jianshu.com/p/1b0686bc166d 來源:简书 简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。 **二.生成器:** https://www.jianshu.com/p/5ee724a8c366 生成器是一个可以快速创建迭代器的工具,结合上文python魔术方法-迭代器进行讲解。 官方文档:https://docs.python.org/3/reference/expressions.html#yieldexpr **语法** 上面文章提到了通过`__iter__`和`__next__`创建迭代器的情况,其实python提供了一个很简单的表达式来创建一个迭代器,它就是`yield表达式`。 使用这个表达式,我们可以像编写一般的函数一样来编写,只不过在需要返回数据的时候使用yield表达式即可。 比如: ``` def MyRange(end): start = 0 while start < end: yield start start += 1 from collections.abc import * a = MyRange(2) print(isinstance(a, Iterator)) print(isinstance(a, Iterable)) for i in a: print(i) ``` 结果是: ``` True True 0 1 ``` 可以看见,函数MyRange里面使用了yield语句,结果变成了`Iterator`,我们在使用for语句的时候,执行步骤如下: ``` start = 0, while 0 < 2,遇到了yield语句,返回start的值0,然后print这值; 第二次for的时候,执行yield后面的语句,start = 1,while 1<2,遇到了yield语句,返回start的值1,然后print; 第三次for的时候,执行yield后面的语句,start = 2,while 2<2不成立,此时函数结束运行,会抛出StopIterator的异常(这个是自动抛出的,不需要显示的调用raise语句,可以使用下面的next方法来查看这个异常)。 ``` ``` def MyRange(end): start = 0 while start < end: yield start start += 1 from collections.abc import * a = MyRange(2) print(isinstance(a, Iterator)) print(isinstance(a, Iterable)) print(next(a)) print(next(a)) print(next(a)) # 这里在上一步已经结束,会抛出异常 ``` `send和throw函数` 我们知道,yield表达式只能在生成器函数中使用(`Yield expressions and statements are only used when defining a generator function, and are only used in the body of the generator function. )`,当我们调用这个生成器函数的时候,它返回的是一个迭代器叫做生成器。 send函数是生成器对象的一个方法,接收一个参数,这个参数是yield表达式的返回值,send函数返回生成器的下一个值或者抛出StopIteration异常(当已经没有下一个值得时候)。 注意上面说的yield表达式的返回值,我们以前使用的都是yield start这种格式,其实yield是有返回值的,默认情况下都是None,我们修改一下上面的MyRange ``` def MyRange(end): start = 0 while start < end: x = yield start # 这里增加了获取返回值 print(x) # 打印出来 start += 1 m = MyRange(5) print(next(m)) print(next(m)) 结果是: 0 None 1 ``` yield执行顺序上面已经说明了,这里打印了一个None,就是yield的返回值,那么说了这么多,就是为了说明send函数的参数,会作为yield的返回值的。 ``` def MyRange(end): start = 0 while start < end: x = yield start print(x) start += 1 m = MyRange(5) print(next(m)) print(m.send(10)) ``` 打印结果是: ``` 0 10 1 ``` 我们在来看看throw这个函数,它是让yield产生一个异常,接收三个参数,异常类型,异常值,trackback对象,其中后面两个可选,同send一样,会返回下一个值。 如果这个异常不在生成器函数里面进行处理,会抛出到调用者 ``` def MyRange(end): start = 0 while start < end: try: x = yield start except Exception as e: print(e) start += 1 m = MyRange(5) print(next(m)) print(m.throw(Exception, 'Some Exception')) ``` 结果是: ``` 0 Some Exception 1 ``` 由于我们在MyRange里面使用try捕获了异常,所以这个异常没有继续抛出来,我们的程序可以正常执行,如果不进行捕获,那么我们的程序是不能继续正常执行的。 yield from 格式类似yield from <expression>,其中expression部分可选,就是使用expression部分作为一个子迭代器。 ```` def g(x): yield from range(x, 0, -1) yield from range(x) list(g(5)) # [5, 4, 3, 2, 1, 0, 1, 2, 3, 4] ``` 生成器表达式 类似于列表的推导式,只是把[]替换为了(),生成器表达式结构更经凑,但是功能不及yield,比如: ``` a = (_ for _ in range(2)) ``` 上例中,a就是一个生成器。 作者:Liburro 链接:https://www.jianshu.com/p/5ee724a8c366 來源:简书 简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
上一篇:
#define __T(x) L ## x 的意义
下一篇:
numpy中的choice()函数
0
赞
384 人读过
新浪微博
微信
腾讯微博
QQ空间
人人网
提交评论
立即登录
, 发表评论.
没有帐号?
立即注册
0
条评论
More...
文档导航
没有帐号? 立即注册