一、列表生成式
列表生成式即List Comprehensions,是python内置的非常强大的创建列表的方式。
比如有一个要求,列表 a = [0, 1, 2, 3, 4, 5],要求把列表里的每个值增加1,实现方式有以下几种:
# 方式一 通过for循环 a = [0, 1, 2, 3, 4, 5] b = [] for i in a: b.append(i+1) a = b print(a) # 方式二 通过enumerate函数 a = [0, 1, 2, 3, 4, 5] for index, i in enumerate(a): a[index] += 1 print(a) # 方式三 通过lambda函数 a = [0, 1, 2, 3, 4, 5] a = map(lambda x: x+1, a) for i in a: print(i) # 方式四 通过列表生成器 a = [0, 1, 2, 3, 4, 5] a = [i+1 for i in a] print(a)
列表生成式中,for循环后边还可以加上if判断语句:
a = [0, 1, 2, 3, 4, 5] a = [i + 1 for i in a if i < 3] print(a) 结果:[1, 2, 3]
还可以使用两层循环,可以生成全排列:
a = [m + n for m in "ABC" for n in "XYZ"] print(a) 结果: ['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']
二、生成器
通过列表生成式,我们可以创建一个列表。但是,受到内存限制,列表包容量肯定是有限的。
for i in range(1000000): if i == 50: break print(i)
比如在一个100万个元素的列表中,访问其中的前面几个元素,如果采用for循环,则后边绝大多数的元素所占的空间就白白浪费了,因为for i in range(1000000)会先生成100万个元素的列表。但是循环到第50次时,我们就不想继续了,就退出了。那前面90多万的元素就白白提前生成了。
所以,如果列表元素能够按照某种算法推算出来,那我们可否可以在循环的过程中不断推算出后续的元素呢?
像上面的循环,我们可以写一个算法,让其每执行依次就自动加1, 这样就不用创建完整的list,从而节省大量的空间。在Python中,这种边循环边计算后面元素的机制,称为生成器:generator。
要创建一个生成器,有多种方法。最简单的方法就是,把列表生成式中的[ ],改成( )就可以了。
ls = [x * x for x in range(5)] print(ls) g = (x * x for x in range(5)) print(g) 结果: [0, 1, 4, 9, 16] <generator object <genexpr> at 0x0378FDF0>
我们可以看到,将[ ]变为()后,得到的是一个generator 对象,如何拿到结果呢?有两种方法:
方法一:g.__next()__
每执行一次g.__next__()就返回一个计算结果,待所有结果都返回后,如果再次执行g.__next__(),则会报StopIteration的错误。
g = (x * x for x in range(5)) print(g) print(g.__next__()) print(g.__next__()) print(g.__next__()) print(g.__next__()) print(g.__next__()) 结果: <generator object <genexpr> at 0x0332FDB0> 0 1 4 9 16
方法二:通过for 循环获取生成器中的值,这种情况下就不会报错。
g = (x * x for x in range(5)) for i in g: print(i) 结果: 0 1 4 9 16
generator 非常强大,如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数生成器来实现。
比如忠明的斐波拉契数列(Fibonacci),除了第一个和第二个外,任意一个数都可以用前两个数相加得到:
1, 2, 3, 5, 8, 13, 21, 34, 55,.......
实现100以内的斐波那契数的函数代码:
def fibno(max): n = 0 a = 0 b = 1 while n < max: n = a + b a = b b = n print(n) fibno(100)
结果:
1
2
3
5
8
13
21
34
55
89
144
仔细观察,可以看出,fibno函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。也就是说,上面的函数和gennerator仅一步之遥,只需要把print(n)改为 yield n 就可以了。
def fibno(max): n = 0 a = 0 b = 1 while n < max: n = a + b a = b b = n yield n # 程序走到这里,就会暂停下来,返回n到函数外面,知道被next调用时唤醒。 f = fibno(100) # 执行函数得到的是一个生成器对象 print(f) print(f.__next__()) print(f.__next__()) print(f.__next__()) 结果: <generator object fibno at 0x039CFDB0> 1 2 3
如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通的函数,而是一个generator,这里最难理解的就是generator和函数的执行流程不一样,函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而编程gennerator的函数,再每次调用next()的时候执行,遇到yield语句暂停并返回数据到函数外,在此被next()调用时从上次返回的yield语句处继续执行。
我们再循环的过程中不断地调用yield,函数就会不断的终端(暂停),当然要给循环设置一个条件来退出循环,不然就会产生一个无限数列出来,同样的,把函数改成genenrator后,我们基本数从不用next()来获取下一个返回值,而是直接使用for循环来迭代。
三、迭代器
我们知道,可以直接作用于for循环的数据类型有以下几种:
1、一类是集合数据类型,如:list、tuple、dict、set、str等;
2、一类是genenrator,包括生成器和带yield的generator function
我们把这些直接作用于for循环的对象统称为可迭代对象:Iterable,可迭代的意思就是可遍历、可循环。
通过isinstance()来判断一个对象是否是Iterable对象:
from collections import Iterable print(isinstance([], Iterable)) print(isinstance({}, Iterable)) print(isinstance("abc", Iterable)) 结果: True True True
从上面可以看出列表、字典和字符串都是可迭代对象,但他们是不是迭代器呢?我们可以通过以下方式进行判断。
from collections import Iterator print(isinstance([], Iterator)) print(isinstance({}, Iterator)) print(isinstance("abc", Iterator)) 结果: False False False
为什么列表、字典、字符串等数据类型不是Iterator呢?这是因为Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用,并不断返回下一个值,直到没有数据时抛出StopIteration错误。可以把这个数据流看作时一个有序序列,但我们却不能提前直到序列的长度,只能通过next()函数实现按需要计算下一个数据。所以迭代器的计算是惰性的,只有在需要返回下一个数据时它才会计算。
迭代器可以表示一个无限大的数据流,例如全体自然数,而使用list是永远无法做到的。
我们可以将列表通过iter()函数将其变为迭代器:
a = [1, 2, 3, 4, 5] b = iter(a) print(b) 结果:<list_iterator object at 0x0341EF90>
通过iter()函数把列表变成了列表迭代器对象,这样就可以通过next()函数来依次返回下一个值,直到抛出异常,同样也可以通过for循环进行取值,但不能通过下标的方式进行取值。
关于生成器和迭代器的区别:
生成器属于迭代器,它们都可以通过next()和for循环的方式取值。
from collections import Iterator def fibno(max): n = 0 a = 0 b = 1 while n < max: n = a + b a = b b = n yield n f = fibno(100)
print(f) print(isinstance(f, Iterator)) 结果:
<generator object fibno at 0x039517B0>
True # 生成器属于迭代器
f = iter([1, 2, 3, 4, 5]) print(f) print(f.__next__()) print(f.__next__()) print(f.__next__()) print(f.__next__()) print(f.__next__()) print(f.__next__()) 结果: <list_iterator object at 0x02BFEF30> # 迭代器 1 2 3 4 5 StopIteration # 抛异常
f2 = (i+1 for i in range(5))
print(f2)
print(f2.__next__())
print(f2.__next__())
print(f2.__next__())
print(f2.__next__())
print(f2.__next__())
print(f2.__next__())
结果:
<generator object <genexpr> at 0x0394FDB0> # 生成器
1
2
3
4
5
StopIteration # 抛异常
通过上面的代码可以看出,f迭代器和f2生成器都可以通过next()函数不断返回下一个值直到抛出异常。生成器是一种特殊的迭代器,特殊之处在于它是通过函数运算或四则运算生成的一个惰性序列。而迭代器除了包含生成器,还可以通过对一个已知序列用iter()函数得到。
迭代器小结:
1、凡是可以用于for循环额对象都是Iterable
2、凡是可以用作next()函数的对象都是Iterator,表示一个惰性序列
3、generator是一种特殊的Iterator,它是通过过函数运算或四则运算生成的惰性序列
4、列表、字典、字符串等式iterable但不是Iterator,但可以通过iter()函数获得一个Iterator.