容器
容器就是一个用来存储多个元素的数据结构,常见的容器包括【列表】、【元组】、【字典】、【集合】、【字符串】
容器有两个特点:
- 容器中的元素可通过迭代获取
2. 所有容器中的元素被存储在内存中。
可迭代对象
实现了__iter__方法的对象就叫做可迭代对象。
直观理解就是能用for循环进行迭代的对象就是可迭代对象。
如:字符串,列表,元祖,字典,集合等等,都是可迭代对象。
迭代器:
迭代器创建方法1:
iter():将可迭代对象转为迭代器
import sys
mylist1 = [1, 3, 4, 6, 7, 8, 'osaiduhgfoiuydsfhguy', 'adsuygfydshgf', '654856465', 88, 90, 344]
print('==========mylist1=======')
print('type:',type(mylist1))
print(len(mylist1))
print('size:',sys.getsizeof(mylist1)) # 输出list占用内存空间
myiter1 = iter(mylist1) # 将list转为迭代器
print('==========mylist1=======')
print('type:',type(myiter1))
print('size:',sys.getsizeof(myiter1)) # 输出list占用内存空间
# 从迭代器取值
print('next取值---: ')
print(next(myiter1))
print(next(myiter1))
print('for 取值---:')
for i in myiter1:
print(i)
》》》》》
==========mylist1=======
type: <class 'list'>
12
size: 152
==========mylist1=======
type: <class 'list_iterator'>
size: 48
next取值---:
1
3
for 取值---:
4
6
7
8
osaiduhgfoiuydsfhguy
adsuygfydshgf
654856465
88
90
344
迭代器创建方法2:
任何实现了__iter__() 和 __next__()方法的对象都是迭代器。
__iter__() 返回迭代器自身,
__next__() 决定迭代规则;返回容器中的下一个值,如果容器中没有更多元素了,则抛出StopIteration异常。而且,迭代器不会一次性吧所有元素都加载到内存,而是需要的时候才返回结果。
调用next()方法的时候返回容器中的下一个值,
迭代器每次调用next()方法的时候做两件事:
- 为下一次调用next()方法修改状态。
- 生成当前调用的返回结果。
class Myiterator():
# 迭代器求斐波那契数列
def __init__(self,max):
self.prev = 0
self.curr = 1
self.max = max
def __iter__(self):
return self
def __next__(self):
if self.max == 0:
raise StopAsyncIteration
value = self.curr
self.curr += self.prev
self.prev = value
self.max = self.max-1
return value
mm = Myiterator(20)
for i in mm:
print(i)
为什么list、dict、str等数据类型不是Iterator?
这是因为Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。
缺点
在上面的介绍中也提到了迭代器的缺点,集中说一下:
- 取值不够灵活。next方法只能往后取值,不能往前。取值不如按照索引的方式灵活,不能取指定的某一个值
- 无法预测迭代器的长度。迭代器通过next()方法取值,并不能提前知道要迭代出的个数
- 用完一次就失效
误区
迭代器的优势和缺点已经说的清晰了,现在讨论一个普遍对迭代器的一个误区:迭代器是不能节省内存的
给这句话加一个前提:这里的迭代器是指普通的迭代器,而非生成器,因为生成器也是一种特殊的迭代器。
生成器:
使用了 yield 的函数被称为生成器(generator)
生成器是一种特殊的迭代器。特殊在我们可以通过send()方法像生成器中传入数据,而迭代器只能将数据输出。
生成器一定是迭代器(反之不成立)
其主要的特点有:
1、拥有yield关键字的函数就是生成器函数,生成器拥有迭代器的迭代传出数据的功能,但用关键字yield来替代迭代器中的__next__()方法来实现。
2、生成器可以传入数据(使用send())进行计算(不同于迭代器),并根据变量内容计算结果后返回。
3、生成器不会一次把所有的元素加载到内存,而是调用的时候才生成返回结果(这点相同于迭代器)
4、可以通过for循环进行迭代(因为生成器是迭代器)
总结:生成器是一种特殊的迭代器,其具有传入数据的功能。
从生成器取值方法:
1. for i in myiter #形式迭代取值 ---- 同迭代器
2. next(myiter) #迭代器next函数取值 ---- 同迭代器
3. myiter.send(x)
#生成器send函数发送参数,同时取到值(生成器函数中未对接收参数进行任何处理时,也能使用send进行传参)
注:不能在第一次取值时使用send(x);第一次取值使用next() 或者send(None)或for中迭代取值
否则会出现报错:
TypeError: can't send non-None value to a just-started generator-python
python两种方式提供生成器:
一.生成器函数:
- 使用yield语句而不是return语句返回结果
- yield语句一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次重新在挂起的地方继续执行。函数中可以有多个yield,每个yield都会挂起一次
3.在for 循环中迭代,或使用next(iterobject,defalt)函数
1.1不带参数
def fib(max):
n, a = 0, 0
while n < max:
print('in while start')
yield a #每次返回值则停在此处等待下次取值再往下执行
a = a+1
n = n + 1
print('in while end')
dd = fib(10)
i = 0
while i<10:
print(next(dd))
print('完成一次取值')
i = i +1
>>>>
in while start
0
完成一次取值
in while end
in while start
1
完成一次取值
in while end
in while start
2
完成一次取值
in while end
in while start
3
完成一次取值
in while end
in while start
4
完成一次取值
2.对传入参数进行处理
Send()
实际上next()和send()在一定意义上作用是相似的,区别是send()可以传递yield表达式的值进去,而next()不能传递特定的值,只能传递None进去。因此,我们可以看做c.next() 和 c.send(None) 作用是一样的。
1:def test():
2: i = 1
3: while i < 5:
4: temp = yield i**2 #python程序碰到 “=” 从右往左执行
5: print("in iter:",temp)
6: i += 1
7:aa = test()
8:for j in range(4):
9: if j == 0:
10: print(aa.send(None))
11: else:
12: print(aa.send(j))
13: print('==========')
》》》》
1 #第一次执行到第4行等号右侧
==========
in iter: 1 #第二次执行 从第4行等号左侧接收send参数 往下执行
4
==========
in iter: 2
9
==========
in iter: 3
16
==========
解析:
1.第一次执行只执行到yield右侧 并返回yield右侧的值
2.send() 把参数传递给temp,并让生成器执行一次到yield右侧,接收返回值
二.生成器表达式:类似于列表推导式,但是,生成器返回按需要产生结果的一个队想,而不是一次构建整个结果列表
列表推导式: squares = [x**2 for x in range(5)] #squres = [0,1,4,9,16]
换成圆括号--生成器: squares = (x**2 for x in range(5))
next(squares) # 0
next(squares) # 1
使用方法
启动:生成器的启动需要用next(xx),或xx.send(None)
优点:Python使用生成器对延迟操作提供了支持.所谓延迟操作,是指在需要的时候才产生结果,而不是立即产生结果.
- 节省内存:当需要创建一个包含一百万元素的列表时,使用列表定义是不现实的,列表元素可以用算法推算出来后,节省大量内存空间,适合使用生成器。
使用场景
- 数据的数据规模巨大,内存消耗严重
- 数列有规律,但是依靠列表推导式描述不出来
- 协程。生成器和协程有着千丝万缕的联系
迭代器与生成器区别
- 生成器属于迭代器,但是生成器可以传参数(send()),迭代器不行
- 迭代器是一个对象,需要定义迭代器类来实例化。
- 生成器是一个对象,用带yield的函数返回
参考: