python 生成器 迭代器

阅读目录

  • 一 递归和迭代
  • 二 什么是迭代器协议
  • 三 python中强大的for循环机制
  • 四 为何要有for循环
  • 五 生成器初探
  • 六 生成器函数
  • 七 生成器表达式和列表解析
  • 八 生成器总结

一 递归和迭代

这里简单理解一下迭代器和生成器的概念,迭代器可以理解为:一直在干一件事情,要包含iter()方法,获取值的时候,要调用next()方法;而生成器则是对一个可迭代对象进行操作,要调用yeild()方法;两者的区别是,迭代器是用户发出请求,没调用一次返回一个结果;而生成器则是,一次调用,将结果一次性返回数据,Python中有两个典型的例子,xrange 和range ,其中xrange执行原理就是个迭代器,而range则可以理解成一个生成器。

二 什么是迭代器协议

1.迭代器协议是指:对象必须提供一个next方法,执行该方法要么返回迭代中的下一项,要么就引起一个StopIteration异常,以终止迭代 (只能往后走不能往前退)

2.可迭代对象:实现了迭代器协议的对象(如何实现:对象内部定义一个__iter__()方法)

3.协议是一种约定,可迭代对象实现了迭代器协议,python的内部工具(如for循环,sum,min,max函数等)使用迭代器协议访问对象。

三 python中强大的for循环机制

for循环的本质:循环所有对象,全都是使用迭代器协议。

正本清源:

很多人会想,for循环的本质就是遵循迭代器协议去访问对象,那么for循环的对象肯定都是迭代器了啊,没错,那既然这样,for循环可以遍历(字符串,列表,元组,字典,集合,文件对象),那这些类型的数据肯定都是可迭代对象啊?但是,我他妈的为什么定义一个列表l=[1,2,3,4]没有l.next()方法,打脸么。

 

(字符串,列表,元组,字典,集合,文件对象)这些都不是可迭代对象,只不过在for循环式,调用了他们内部的__iter__方法,把他们变成了可迭代对象

然后for循环调用可迭代对象的__next__方法去取值,而且for循环会捕捉StopIteration异常,以终止迭代

复制代码
 1 l=['a','b','c']
 2 #一:下标访问方式
 3 print(l[0])
 4 print(l[1])
 5 print(l[2])
 6 # print(l[3])#超出边界报错:IndexError
 7 
 8 #二:遵循迭代器协议访问方式
 9 diedai_l=l.__iter__()
10 print(diedai_l.__next__())
11 print(diedai_l.__next__())
12 print(diedai_l.__next__())
13 # print(diedai_l.__next__())#超出边界报错:StopIteration
14 
15 #三:for循环访问方式
16 #for循环l本质就是遵循迭代器协议的访问方式,先调用diedai_l=l.__iter__()方法,或者直接diedai_l=iter(l),然后依次执行diedai_l.next(),直到for循环捕捉到StopIteration终止循环
  #for循环所有对象的本质都是一样的原理
17 18 for i in l:#diedai_l=l.__iter__() 19 print(i) #i=diedai_l.next() 20 21 #四:用while去模拟for循环做的事情 22 diedai_l=l.__iter__() 23 while True: 24 try: 25 print(diedai_l.__next__()) 26 except StopIteration: 27 print('迭代完毕了,循环终止了') 28 break
复制代码
 

四 为何要有for循环

基于上面讲的列表的三种访问方式,聪明的你立马看除了端倪,于是你不知死活大声喊道,你这不逗我玩呢么,有了下标的访问方式,我可以这样遍历一个列表啊

复制代码
l=[1,2,3]

index=0
while index < len(l):
    print(l[index])
    index+=1

#要毛线for循环,要毛线for循环,要毛线for循环
复制代码

 

没错,序列类型字符串,列表,元组都有下标,你用上述的方式访问,perfect!但是你可曾想过非序列类型像字典,集合,文件对象的感受,所以嘛,年轻人,for循环就是基于迭代器协议提供了一个统一的可以遍历所有对象的方法,即在遍历之前,先调用对象的__iter__方法将其转换成一个迭代器,然后使用迭代器协议去实现循环访问,这样所有的对象就都可以通过for循环来遍历了,而且你看到的效果也确实如此,这就是无所不能的for循环,觉悟吧,年轻人

 

五 生成器初探

什么是生成器?

可以理解为一种数据类型,这种数据类型自动实现了迭代器协议(其他的数据类型需要调用自己内置的__iter__方法),所以生成器就是可迭代对象

 

生成器分类及在python中的表现形式:(Python有两种不同的方式提供生成器)

1.生成器函数:常规函数定义,但是,使用yield语句而不是return语句返回结果。yield语句一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次重它离开的地方继续执行

2.生成器表达式:类似于列表推导,但是,生成器返回按需产生结果的一个对象,而不是一次构建一个结果列表

 

为何使用生成器之生成器的优点

Python使用生成器对延迟操作提供了支持。所谓延迟操作,是指在需要的时候才产生结果,而不是立即产生结果。这也是生成器的主要好处。

 

生成器小结:

1.是可迭代对象

2.实现了延迟计算,省内存啊

3.生成器本质和其他的数据类型一样,都是实现了迭代器协议,只不过生成器附加了一个延迟计算省内存的好处,其余的可迭代对象可没有这点好处,记住喽!!!

六 生成器函数

def lay_eggs(num):
    egg_list=[]
    for egg in range(num):
        egg_list.append('蛋%s' %egg)
    return egg_list

yikuangdan=lay_eggs(10) #我们拿到的是蛋
print(yikuangdan)


def lay_eggs(num):
    for egg in range(num):
        res='蛋%s' %egg
        yield res
        print('下完一个蛋')

laomuji=lay_eggs(10)#我们拿到的是一只母鸡
print(laomuji)
print(laomuji.__next__())
print(laomuji.__next__())
print(laomuji.__next__())
egg_l=list(laomuji)
print(egg_l)
#演示只能往后不能往前
#演示蛋下完了,母鸡就死了

七 生成器表达式和列表解析

#三元表达式
name='jesson'
name='tim'
res='SB' if name == 'jesson' else 'tim'
print(res)
# jesson 决定给tim 买一些鸡蛋(一个一个买)
egg_list=['鸡蛋%s' %i for i in range(10)] #列表解析

#tim瞅着jesson买的一筐鸡蛋,捂住了鼻子,说了句:哥,你还是给我只母鸡吧,我自己回家下

laomuji=('鸡蛋%s' %i for i in range(10))#生成器表达式
print(laomuji)
print(next(laomuji)) #next本质就是调用__next__
print(laomuji.__next__())
print(next(laomuji))

复制代码
#老男孩由于峰哥的强势加盟很快走上了上市之路,alex思来想去决定下几个鸡蛋来报答峰哥

egg_list=['鸡蛋%s' %i for i in range(10)] #列表解析

#峰哥瞅着alex下的一筐鸡蛋,捂住了鼻子,说了句:哥,你还是给我只母鸡吧,我自己回家下

laomuji=('鸡蛋%s' %i for i in range(10))#生成器表达式
print(laomuji)
print(next(laomuji)) #next本质就是调用__next__
print(laomuji.__next__())
print(next(laomuji))
复制代码

总结:

1.把列表解析的[]换成()得到的就是生成器表达式

2.列表解析与生成器表达式都是一种便利的编程方式,只不过生成器表达式更节省内存

3.Python不但使用迭代器协议,让for循环变得更加通用。大部分内置函数,也是使用迭代器协议访问对象的。例如, sum函数是Python的内置函数,该函数使用迭代器协议访问对象,而生成器实现了迭代器协议,所以,我们可以直接这样计算一系列值的和:

1 sum(x ** 2 for x in xrange(4))

而不用多此一举的先构造一个列表:

1 sum([x ** 2 for x in xrange(4)]) 

八 生成器总结

综上已经对生成器有了一定的认识,下面我们以生成器函数为例进行总结

  • 语法上和函数类似:生成器函数和常规函数几乎是一样的。它们都是使用def语句进行定义,差别在于,生成器使用yield语句返回一个值,而常规函数使用return语句返回一个值
  • 自动实现迭代器协议:对于生成器,Python会自动实现迭代器协议,以便应用到迭代背景中(如for循环,sum函数)。由于生成器自动实现了迭代器协议,所以,我们可以调用它的next方法,并且,在没有值可以返回的时候,生成器自动产生StopIteration异常
  • 状态挂起:生成器使用yield语句返回一个值。yield语句挂起该生成器函数的状态,保留足够的信息,以便之后从它离开的地方继续执行

优点一:生成器的好处是延迟计算,一次返回一个结果。也就是说,它不会一次生成所有的结果,这对于大数据量处理,将会非常有用。

1 #列表解析
2 sum([i for i in range(100000000)])#内存占用大,机器容易卡死
3 
4 #生成器表达式
5 sum(i for i in range(100000000))#几乎不占内存

优点二:生成器还能有效提高代码可读性

 #求一段文字中,每个单词出现的位置
 2 def index_words(text):
 3     result = []
 4     if text:
 5         result.append(0)
 6     for index, letter in enumerate(text, 1):
 7         if letter == ' ':
 8             result.append(index)
 9     return result
10 
11 print(index_words('hello alex da sb'))

复制代码
 1 #求一段文字中,每个单词出现的位置
 2 def index_words(text):
 3     result = []
 4     if text:
 5         result.append(0)
 6     for index, letter in enumerate(text, 1):
 7         if letter == ' ':
 8             result.append(index)
 9     return result
10 
11 print(index_words('hello alex da sb'))
复制代码
#求一段文字中每个单词出现的位置
def index_words(text):
    if text:
        yield 0
    for index, letter in enumerate(text, 1):
        if letter == ' ':
            yield index

g=index_words('hello alex da sb')
print(g)
print(g.__next__())
print(g.__next__())
print(g.__next__())
print(g.__next__())
print(g.__next__())#报错

这里,至少有两个充分的理由说明 ,使用生成器比不使用生成器代码更加清晰:

  1. 使用生成器以后,代码行数更少。大家要记住,如果想把代码写的Pythonic,在保证代码可读性的前提下,代码行数越少越好
  2. 不使用生成器的时候,对于每次结果,我们首先看到的是result.append(index),其次,才是index。也就是说,我们每次看到的是一个列表的append操作,只是append的是我们想要的结果。使用生成器的时候,直接yield index,少了列表append操作的干扰,我们一眼就能够看出,代码是要返回index。

这个例子充分说明了,合理使用生成器,能够有效提高代码可读性。只要大家完全接受了生成器的概念,理解了yield语句和return语句一样,也是返回一个值。那么,就能够理解为什么使用生成器比不使用生成器要好,能够理解使用生成器真的可以让代码变得清晰易懂。

注意事项:生成器只能遍历一次(母鸡一生只能下一定数量的蛋,下完了就死掉了)

复制代码
 1 人口信息.txt文件内容
 2 {'name':'北京','population':10}
 3 {'name':'南京','population':100000}
 4 {'name':'山东','population':10000}
 5 {'name':'山西','population':19999}
 6 
 7 def get_provice_population(filename):
 8     with open(filename) as f:
 9         for line in f:
10             p=eval(line)
11             yield p['population']
12 gen=get_provice_population('人口信息.txt')
13 
14 all_population=sum(gen)
15 for p in gen:
16     print(p/all_population)
17 执行上面这段代码,将不会有任何输出,这是因为,生成器只能遍历一次。在我们执行sum语句的时候,就遍历了我们的生成器,当我们再次遍历我们的生成器的时候,将不会有任何记录。所以,上面的代码不会有任何输出。
18 
19 因此,生成器的唯一注意事项就是:生成器只能遍历一次。
复制代码
#_*_coding:utf-8_*_
__author__ = 'Linhaifeng'

def test():
    for i in range(4):
        yield i

g=test()

g1=(i for i in g)
g2=(i for i in g1)

print(list(g1))
print(list(g2))

复制代码
#_*_coding:utf-8_*_
__author__ = 'Linhaifeng'

def test():
    for i in range(4):
        yield i

g=test()

g1=(i for i in g)
g2=(i for i in g1)

print(list(g1))
print(list(g2))
复制代码
def add(n,i):
    return n+i

def test():
    for i in range(4):
        yield i

g=test()
for n in [1,10]:
    g=(add(n,i) for i in g)

print(list(g))
 

复制代码
def add(n,i):
    return n+i

def test():
    for i in range(4):
        yield i

g=test()
for n in [1,10]:
    g=(add(n,i) for i in g)

print(list(g))
复制代码
import os

def init(func):
    def wrapper(*args,**kwargs):
        g=func(*args,**kwargs)
        next(g)
        return g
    return wrapper

@init
def list_files(target):
    while 1:
        dir_to_search=yield
        for top_dir,dir,files in os.walk(dir_to_search):
            for file in files:
                target.send(os.path.join(top_dir,file))
@init
def opener(target):
    while 1:
        file=yield
        fn=open(file)
        target.send((file,fn))
@init
def cat(target):
    while 1:
        file,fn=yield
        for line in fn:
            target.send((file,line))

@init
def grep(pattern,target):
    while 1:
        file,line=yield
        if pattern in line:
            target.send(file)
@init
def printer():
    while 1:
        file=yield
        if file:
            print(file)

g=list_files(opener(cat(grep('python',printer()))))

g.send('/test1')

复制代码
import os

def init(func):
    def wrapper(*args,**kwargs):
        g=func(*args,**kwargs)
        next(g)
        return g
    return wrapper

@init
def list_files(target):
    while 1:
        dir_to_search=yield
        for top_dir,dir,files in os.walk(dir_to_search):
            for file in files:
                target.send(os.path.join(top_dir,file))
@init
def opener(target):
    while 1:
        file=yield
        fn=open(file)
        target.send((file,fn))
@init
def cat(target):
    while 1:
        file,fn=yield
        for line in fn:
            target.send((file,line))

@init
def grep(pattern,target):
    while 1:
        file,line=yield
        if pattern in line:
            target.send(file)
@init
def printer():
    while 1:
        file=yield
        if file:
            print(file)

g=list_files(opener(cat(grep('python',printer()))))

g.send('/test1')
复制代码

 

 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Python迭代器,可迭代对象,生成器

迭代器

迭代器(iterator)有时又称游标(cursor)是程式设计的软件设计模式,可在容器物件(container,例如链表或阵列)上遍访的界面,设计人员无需关心容器物件的内存分配的实现细节。

摘自维基百科

也就是说迭代器类似于一个游标,卡到哪里就是哪里,可以通过这个来访问某个可迭代对象的元素;同时,也不是只有Python有这个特性。比如C++的STL中也有这个,如vector<int>::iterator it。下面主要说一下Python中的可迭代对象和迭代器吧。

Python可迭代对象(Iterable)

Python中经常使用for来对某个对象进行遍历,此时被遍历的这个对象就是可迭代对象,像常见的list,tuple都是。如果给一个准确的定义的话,就是只要它定义了可以返回一个迭代器的__iter__方法,或者定义了可以支持下标索引的__getitem__方法(这些双下划线方法表示私有方法),那么它就是一个可迭代对象。

Python迭代器(iterator)

迭代器是通过next()来实现的,每调用一次他就会返回下一个元素,当没有下一个元素的时候返回一个StopIteration异常,所以实际上定义了这个方法的都算是迭代器。可以用通过下面例子来体验一下迭代器:

In [38]: s = 'ab'

In [39]: it = iter(s)

In [40]: it
Out[40]: <iterator at 0x1068e6d50>

In [41]: print it
<iterator object at 0x1068e6d50>

In [42]: it.next()
Out[42]: 'a'

In [43]: it.next()
Out[43]: 'b'

In [44]: it.next()
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-44-54f0920595b2> in <module>()
----> 1 it.next()

StopIteration:

自己实现一个迭代器,如下(参见官网文档):

class Reverse:
    """Iterator for looping over a sequence backwards."""
    def __init__(self, data):
        self.data = data
        self.index = len(data)

    def __iter__(self):
        return self

    def next(self):
        if self.index == 0:
            raise StopIteration
        self.index = self.index - 1
        return self.data[self.index]

rev = Reverse('spam')
for char in rev:
    print char

[output]
m
a
p
s

生成器(Generators)

生成器是构造迭代器的最简单有力的工具,与普通函数不同的只有在返回一个值的时候使用yield来替代return,然后yield会自动构建好next()iter()。是不是很省事。例如:

def reverse(data):
    for index in range(len(data)-1, -1, -1):
        yield data[index]

>>> for char in reverse('golf'):
...     print char
...
f
l
o
g

生成器最佳应用场景是:你不想同一时间将所有计算出来的大量结果集分配到内存当中,特别是结果集里还包含循环。比方说,循环打印1000000个数,我们一般会使用xrange()而不是range(),因为前者返回的是生成器,后者返回的是列表(列表消耗大量空间)。

Help on built-in function range in module __builtin__:

range(...)
    range(stop) -> list of integers
    range(start, stop[, step]) -> list of integers

    Return a list containing an arithmetic progression of integers.
    range(i, j) returns [i, i+1, i+2, ..., j-1]; start (!) defaults to 0.
    When step is given, it specifies the increment (or decrement).
    For example, range(4) returns [0, 1, 2, 3].  The end point is omitted!
    These are exactly the valid indices for a list of 4 elements.

class xrange(object)
 |  xrange(stop) -> xrange object
 |  xrange(start, stop[, step]) -> xrange object
 |
 |  Like range(), but instead of returning a list, returns an object that
 |  generates the numbers in the range on demand.  For looping, this is
 |  slightly faster than range() and more memory efficient.

iter()

将可迭代对象转化为迭代器。

In [113]: s = 'abc'

In [114]: s.next()
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-114-5e5e6532ea26> in <module>()
----> 1 s.next()

AttributeError: 'str' object has no attribute 'next'

In [115]: it = iter(s)

In [116]: it.next()
Out[116]: 'a'

生成器表达式

和列表推导式唯一的区别就是中括号换成了小括号,如下:

In [119]: num = (i for i in range(10))

In [120]: sum(num)
Out[120]: 45

参考

  1. Python官方文档
  2. Python进阶
原文地址:https://www.cnblogs.com/hellojesson/p/5861389.html