Python高级语法-列表生成式与迭代器杂谈

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Apple_hzc/article/details/82497483

一、列表生成式

列表生成式是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对象,但listdictstr等虽然是Iterable,却不是Iterator。把listdictstrIterable变成Iterator可以使用iter()函数:

print(isinstance(iter("abc"), collections.Iterator)) #输出True

Python中的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数推算出下一个数据。所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。

Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。

猜你喜欢

转载自blog.csdn.net/Apple_hzc/article/details/82497483