11.1.可迭代对象(iterable): 内部实现了__iter__方法
可以被迭代满足要求的就叫做可迭代协议。
iterable:可迭代的------对应的标志
迭代概念:一个一个取值,就像for循环一样取值
字符串,列表,元组,集合,字典都是可迭代的
11.2迭代器协议(iterator): 内部实现了__iter__,__next__方法
可以被next()函数调用并且不断返回下一个值的对象称为迭代器
所有的iterable()均可以通过内置函数iter()转变成iterator
iterator继承自iterable,iterator包含iter()和next()方法,而iterable仅仅包含iter()
迭代器的优点:如果用了迭代器, 节约内存,方便操作
print(dir([1, 2].__iter__())) # 查看列表迭代器的所有方法print(dir([1, 2])) # 查看列表的所有方法print(set(dir([1, 2].__iter__()))-set(dir([1, 2]))) # 对上面两者求差集,去重---{ '__setstate__', '__length_hint__', '__next__'}
iter_l = [1, 2, 3, 4, 5, 6].__iter__() # 后缀必须有这个print(iter_l.__length_hint__()) # 获取迭代器中元素的长度---6print(iter_l.__setstate__(4)) # 根据索引指定从哪里开始迭代---Noneprint(iter_l.__next__())print(iter_l.__next__())print(iter_l.__next__()) # 一个一个的取值print(next(iter_l)) # next(iter_l)和iter_l.__next__()方法一样,推荐用next(iter_l)这个---1---2---3---4
采用for循环模拟可迭代机制:
li = [1,5,78,1,534,34,51,3,15,6]lis = li.__iter__()while True: try: print(next(lis)) except: #采用try过滤掉报错 break
11.3可迭代和迭代器的相同点:都可以用for循环
11.4可迭代和迭代器的不同点:就是迭代器内部多实现了一个__next__方法
11.5判断迭代器和可迭代的方法:
第一种:判断内部是不是实现了__next__方法
'__iter__' in dir(str) # 如果__iter__在这个方法里面,就是可迭代的。s = 'abc'print('__iter__' in str(s))---False
第二种:
Iterable 判断是不是可迭代对象
Iterator 判断是不是迭代器
用法:
from collections import Iterablefrom collections import Iterators = 'abc' # 随便给一个字符串print(isinstance(s, Iterable)) # isinstance 判断类型的print(isinstance(s, Iterator))---True---False
11.6生成器(Generator )(难点):
生成器就是一种迭代器,它拥有next方法而且行为与迭代器完全相同,生成器是一种普通函数语法定义的迭代器。任何包含yield语句的函数成为生成器。
我们定义一个生成器就是定义一个普通函数,但是函数里面有yield关键字,调用生成器函数时会返回一个生成器。
生成器的好处,就是一下子不会在内存中生成太多的数据
yield 生成器的运行机制:
当你问生成器要一个数时,生成器会执行,直至出现 yield 语句,生成器把yield 的参数给你,之后生成器就不会往下继续运行。 当你问他要下一个数时,他会从上次的状态开始运行,直至出现yield语句,把参数给你,之后停下。如此反复直至退出函数。
yield的使用:
在python中,当你定义一个函数,使用了yield关键字时,这个函数就是一个生成器,它的执行会和其他普通的函数有很多不同,函数返回的是一个对象,而不是你平常所用return语句那样,能得到结果值。 如果想取得值,那得调用next()函数
生成器的本质:就是一个迭代器
def h(): print 'To be brave' yield 5h() # 调用h()之后,print语句并没有执行,因为它有yield表达式
我们通过next()语句让它执行。next()语句将恢复Generator执行,并直到下一个yield表达式处。比如:
def h(): print 'Wen Chuan' yield 5 print 'Fighting!'c = h()print(next(c)) # c. 每当调用一次迭代器的next函数,生成器函数运行到yield之处, # 返回yield后面的值且在这个地方暂停,所有的状态都会被保持住,直到下次next函数被调用,或者碰到异常循环退出。--- Wen Chuan---5 # 当我们再次调用next(c)时,会继续执行,直到找到下一个yield表达式,否则报错def h(): print 'Wen Chuan' yield 5 print 'Fighting!'c = h()c.__next__() # 这里直接调用了next,所以没有打印yield后面的值,和上面对比一下---Wen Chuan
这里多取值会直接报错:
def func(): print('aaaaaaaaaaa') a = 1 yield a print('bbbbbb') yield 12 # 返回第二个值ret = func() # 得拿到一个生成器print(ret) # 返回的是一个地址print(next(ret)) # 取第一个值,'aaaaaa', 1print(next(ret)) # 取第二个值, 'bbb', 12print(next(ret)) # 取第三个值,会报错,因为没有yield第三个值 #注意,这里如果直接连续print(next(func())),一直只会打印第一个yield的内容,注意这里的区别。 这和上面ret是两个不同的生成器。
例:创建斐波那契数列:
def fib(max): (a, b) = (1, 1) while a < max: yield a # generators return an iterator that returns a stream of values. (a, b) = (b, a+b)for n in fib(15): print(n)程序运行到yield这行时,就不会继续往下执行。而是返回一个包含当前函数所有参数的状态的iterator对象。 目的就是为了第二次被调用时,能够访问到函数所有的参数值都是第一次访问时的值,而不是重新赋值。程序第一次调用时:def fib(max): (a, b) = (1, 1) while a < max: yield a # 这时a,b值分别为1,1,当然,程序也在执行到这时,返回 (a, b) = (b, a+b)程序第二次调用时: 从前面可知,第一次调用时,a,b=1,1,那么,我们第二次调用时(其实就是调用第一次返回的iterator对象的next()方法),程序跳到yield语句处,执行a,b = b, a+b语句,此时值变为:(a,b = 1, (1+1)) => (a,b = 1, 2)程序继续while循环,当然,再一次碰到了yield a 语句,也是像第一次那样,保存函数所有参数的状态,返回一个包含这些参数状态的iterator对象。等待第三次的调用....
# 通过类的方法来实现斐波那契数列class Fib: def __init__(self, max): self.max = max def __iter__(self): self.a = 0 self.b = 1 return self def next(self): fib = self.a if fib > self.max: raise StopIteration self.a, self.b = self.b, self.a + self.b return fib
通过yield生成全排列:
def perm(items, n = None): if n is None: n = len(items) for i in range(len(items)): v = items[i:i+1] if n==1: yield v else: rest = items[:i] + items[i+1:] for p in perm(rest, n-1): yield v + p def comb(items, n = None): if n is None: n = len(items) else: for i in range(len(items)): v = items[i:i+1] if 1 == n: yield v else: rest = items[i+1:] for c in comb(rest, n-1): yield v + c
11.6.1 列表推导式 和生成器表达式
1.把列表解析的[]换成()得到的就是生成器表达式
2.列表解析与生成器表达式都是一种便利的编程方式,只不过生成器表达式更节省内存
3.Python不但使用迭代器协议,让for循环变得更加通用。大部分内置函数(sum,sorted,math等),也是使用迭代器协议访问对象的。例如, sum函数是Python的内置函数,该函数使用迭代器协议访问对象,而生成器实现了迭代器协议,所以,我们可以直接这样计算一系列值的和:
#列表解析sum([i for i in range(100000000)])#内存占用大,机器容易卡死 不要运行,千万不要运行 #生成器表达式sum(i for i in range(100000000))#几乎不占内存 # 使用sorted函数 >>>sorted(x**2 for x in range(4)) [0,1,4,9]
列表推导式链接------
# 注意:对于生成器
g = (i for i in range(2))print(list(g))print(list(g))---[0, 1]---[]
这里为什么第二次再次打印为空了呢?它其实只能被调用一次,第一次调用完第二次调用的时候就没有了。生成器,生成器并不会二次生成,一次使用完就不会再有第二次了
见这么个面试题:
def demo(): for i in range(4): yield ig=demo()g1=(i for i in g)g2=(i for i in g1)print(list(g1))print(list(g2))
11.6.2 yield from(两个例子结果相同,比较一下两者区别)
def func(): yield from 'AB' yield from [1, 2, 3]g = func()print(list(g))②def func(): for i in 'AB': yield i for i in [1, 2, 3]: yield ig = func()print(list(g))