python 列表生成式、生成器及迭代器介绍

一、列表生成式

  列表生成式即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)
View Code

  列表生成式中,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。

  要创建一个生成器,有多种方法。最简单的方法就是,把列表生成式中的[ ],改成( )就可以了。

扫描二维码关注公众号,回复: 9784348 查看本文章
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.

 

猜你喜欢

转载自www.cnblogs.com/feixiangshuoshuo/p/12463228.html