迭代:
如果给定一个list或tuple,我们可以通过for循环来遍历这个list或tuple,这种遍历我们称为迭代(Iteration)。
Python的for循环不仅可以用在list或tuple上,还可以作用在其他可迭代对象上(用isinstance判断)
可以直接作用于for循环的对象统称为可迭代对象
可以直接作用于for循环的数据类型有以下几种:
一类是集合数据类型,如list、tuple、dict、set、str等;(称为容器<容器是一种把多个元素组织在一起的数据结构>,很多容器都是可迭代的)
一类是generator,包括生成器和带yield的generator function。
这些可以直接作用于for循环的对象统称为可迭代对象:Iterable。
(一)迭代器
一个实现了__iter__方法的对象是可迭代的,一个实现了__next__方法的对象则是迭代器
对于序列和字典的可迭代,是因为在该对象中实现了上面的两个方法
__iter__方法会返回一个迭代器,而所谓的迭代器就是具有__next__方法的对象。在调用__next__方法时,迭代器会返回他的下一个值。若是next方法被调用牡丹石迭代器中没有值可以返回,就会引发一个StopIteration异常
迭代器的优点:需要数据就去获取,而不是一次获取全部数据
相对于我们一次性取出数据,放在列表等类型中,若数据量过大,那么列表会占据大量的内存。而且对于这些数据,我们若是只使用一次就释放的话,那么放在列表中实在是太过浪费内存。
更好的方法就是使用迭代器。迭代器只会取出当前需要的数据方法内存中。
例如Django中的queryset惰性机制中就有提及迭代器的好处(在处理大量的数据时)
栗子:不使用列表的案例,因为如果使用列表,那么列表长度将会是无穷大。占据空间将会是巨大的。
斐波那契数列:
class Fibs: def __init__(self): self.a = 0 self.b = 1 def __next__(self): self.a,self.b = self.b,self.a+self.b return self.a def __iter__(self): return self f = Fibs() for i in f: if i > 1000: print(i) #1597 break
补充:内建函数iter可以从可迭代的对象中获取迭代器
>>> a = [1,2,3,] >>> b = iter(a) >>> type(b) <class 'list_iterator'> >>> next(b) 1 >>> next(b) 2 >>> next(b) 3 >>> next(b) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
可以知道迭代器是一次性消耗品(只会向前获取,不会向后获取),当耗尽时就会触发StopIteration异常
若是想保留一份数据,可以用deepcopy
从迭代器中获取序列:
class Fibs: def __init__(self): self.a = 0 self.b = 1 def __next__(self): self.a,self.b = self.b,self.a+self.b if self.a > 1597: raise StopIteration return self.a def __iter__(self): return self f = Fibs() ls = list(f) print(ls) #[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597]
使用list构造方法显示的将迭代器转换为列表
class list(object): def __init__(self, seq=()): # known special case of list.__init__ """ list() -> new empty list list(iterable) -> new list initialized from iterable's items
(若是迭代器,那么新的列表则是迭代器的所有成员,结束是以StopIteration为标志,若是上面没有触发,那么会一直去扩展列表) # (copied from class doc) """ pass
结束
应该还记得列表推导式(生成式)<顺道回忆下lambda表达式>
>>> [x for x in range(100) if x % 7 == 0] [0, 7, 14, 21, 28, 35, 42, 49, 56, 63, 70, 77, 84, 91, 98]
通过列表推导式,我们可以直接生成一个列表,同样的,这个列表的内存也是受到限制的,当我们使用列表推导式,一次生成一个超大数量的列表,会占据大量内存,然而,若我们只是访问了前面几个,那么后面的空间占用几乎是无用的。
for i in [x for x in range(100) if x % 7 == 0]: if i < 50: print(i) else: break
在上面案例中,我们只是想去获取满足条件的数据的一部分。但是在进行循环时,并不会立刻进行,而是需要将列表生成式全部执行后,才允许去进行循环。而我们所需要的数据仅仅是列表中的前一部分,但是列表推导式一次性将数据全部生成。占据大量无用的空间。那么我们是否可以做到像迭代器那样,需要的时候再去获取。从而避免数据冗余
(二)生成器
生成器都是迭代器。生成器是一种用普通函数语法定义的迭代器
创建一个生成器方法有多种:
其中第一种与列表推导式十分相似,只是需要将中括号[]变为小括号()
>>> b = [x for x in range(100) if x % 7 == 0] >>> type(b) <class 'list'> [0, 7, 14, 21, 28, 35, 42, 49, 56, 63, 70, 77, 84, 91, 98] >>> b = (x for x in range(100) if x % 7 == 0) >>> type(b) <class 'generator'> >>> next(b) 0 >>> next(b) 7 >>> next(b) 14 >>> for i in b: ... print(i) 当数据全部取出后也会触发StopIteration错误
另外一种是:任何包括yield语句的函数都可以称为生成器。
这里同样以斐波那契数列为例:
def fibs(max): n,a,b = 0,0,1 while n < max: yield a #yield语句 n += 1 a, b = b, a+b f = fibs(8) for i in f: print(i) #0 1 1 2 3 5 8 13
生成器和普通函数的行为有很大的区别。
- 不像return返回一次结果就结束函数,而是可以返回多次结果。从什么for循环可以看出,这一个函数返回了不止一次结果
- 每产生一个值(即在yield语句中返回的值),函数就会被冻结,不在执行:即函数停在那点等待被重新唤醒。被重新唤醒后就从之前通知的那点开始执行
def 函数: ... yield 1 执行第一次后返回值1后冻结,不在执行,等待第二次 ... #执行第二次时会向下继续执行,直到下一次yield ... ... yield 2 #第二次冻结(这个过程包括了执行上面的逻辑语句) ... ... ... yield 3
案例:
>>> def flatten(nested): ... for sublist in nested: ... for ele in sublist: ... yield ele >>> for num in flatten(nested): ... print(num) ... 1 2 3 4 5 >>>
也可以同上面迭代器一样使用list显示转换为列表。
>>> list(flatten(nested)) [1, 2, 3, 4, 5] >>>
但是这样会立刻实例化列表,丧失了迭代的优势。
def nrange(num): temp = -1 while True: temp = temp + 1 if temp >= num: return else: yield temp for i in nrange(10): print(i)