一、列表生成式
列表生成式是Python内置的非常简单却强大的可以用来创建list的生成式。
比如:
list = list(range(1,10))
print(list)
打印出:[1, 2, 3, 4, 5, 6, 7, 8, 9]。
假如要生成一个列表:[1*1,2*2,3*3,4*4...,9*9]该怎样做呢?
这里推荐两种方法:
方法一,用for循环:
list = []
for x in range(1,10):
list.append(x * x)
print(list)
方法二,用列表生成式:
list = [x * x for x in range(1,10)]
print(list)
写列表生成式时,把要生成的元素x * x
放到前面,后面跟for
循环,就可以把list创建出来,十分简洁,多写几次,很快就可以熟悉这种语法。
还可以进一步加一些条件判断语句,比如筛选出奇数的平方:
list = [x * x for x in range(1,10) if x % 2 == 1]
print(list)
把一个列表中的单词全部变成小写:
list = ["Hello","Love","LMN","HZC"]
print([x.lower() for x in list])
列表生成式的两层循环打印出全排列:
print([m + n for m in 'ABC' for n in 'XYZ'])
二、生成器-yield
写这部分的原因在于最近接触scrapy框架时,发现在parse()函数中为了Request next_url,而要使用yield函数。如果我们def的函数内部有yield,那么这个函数就变成了一个生成器(函数内部不需要return,由yield来返回结果)。
什么是生成器
从前面我们知道,通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。比如创建一个几百万数据的列表,需要占用很大的存储空间。如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。
要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]
改成()
,就创建了一个generator:
generator = (x * x for x in range(1,10))
print(generator)
打印出结果为:<generator object <genexpr> at 0x000001947870EE08>
创建list和generator
的区别在于最外层的[]
和()
,前者是一个list,而后者则是一个generator。
要想获取该生成器的所有数据元素,应该用next()函数:
generator = (x * x for x in range(1,4))
print(next(generator))
print(next(generator))
print(next(generator))
打印出结果为:1 4 9
generator保存的是算法,每次调用next(generator)
,就计算出生成器下一个元素的值,直到计算到最后一个元素,没有更多的元素时,会抛出StopIteration
的错误。
当然,这种方法输出显得太麻烦了,有一种更简单的方法,用for循环可以一次性打印出结果:
generator = (x * x for x in range(1,4))
for n in generator:
print(n)
注:生成器在每次通过算法推算出相应的值后会丢弃,所以对于生成器中的数据,只能输出一次,假如在上述代码后输出next(generator)会报错。
yield关键字
yield是一个类似return的关键字,迭代一次遇到yield时就返回yield后面的值。重点是:下一次迭代时,从上一次迭代遇到的yield后面的代码(下一行)开始执行。简单来说,yield就是返回一个值,并且记住这个返回的位置,下次迭代就从这个位置后(下一行)开始继续执行。
先看一段代码:
def generator():
yield 1
print("ok1")
yield 2
print("ok2")
yield 3
print("ok3")
g = generator()
for x in g:
print(x)
我们定义了一个函数generator,并且可以看到这确实是函数类型。但与一般的函数不同的是,该函数体内使用了关键字yield,这使得该函数成为了一个生成器函数。其中第8行代码表示:调用生成器函数,会返回一个生成器g,然后通过for循环,打印出下列结果:
1
ok1
2
ok2
3
ok3
值得注意的是,生成器函数和一般函数的执行流程不一样。函数是顺序执行,遇到return
语句或者最后一行函数语句就返回,而generator函数,在每次调用next()
的时候执行,遇到yield
语句返回,再次执行时从上次返回的yield
语句处继续执行。
三、迭代器
我们知道,可以用作for循环的数据类型主要有以下几种,一类是集合数据类型,比如:list、tuple、dic、str等,另一类是生成器和带yield关键字的生成器函数。这些对象统称为可迭代对象:Iterable,
可以使用isinstance()
判断一个对象是否是Iterable
对象:
import collections
print(isinstance([],collections.Iterable))
print(isinstance({},collections.Iterable))
print(isinstance((x * x for x in range(1,10)),collections.Iterable))
结果打印出:True True True
其中生成器不仅可以用于for循环,也可以被next()
函数调用并不断返回下一个值,这样的对象称为迭代器:Iterator
。
所有的生成器都是Iterator
对象,但list
、dict
、str等
虽然是Iterable
,却不是Iterator
。把list
、dict
、str
等Iterable
变成Iterator
可以使用iter()
函数:
print(isinstance(iter("abc"), collections.Iterator)) #输出True
Python中的Iterator
对象表示的是一个数据流,Iterator对象可以被next()
函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration
错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()
函数推算出下一个数据。所以Iterator
的计算是惰性的,只有在需要返回下一个数据时它才会计算。
Iterator
甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。