我是靠谱客的博主 知性黑裤,最近开发中收集的这篇文章主要介绍Python菜鸟起飞day7_迭代器与生成器(5.2未想通)一、概述二、容器(container)三、可迭代(Iterable)对象四、生成器五、几个实例六、参考文章,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

一、概述

在学习python的数据结构的时候,容器(container)、可迭代对象(iterable)、迭代器(iterator)、生成器(generator)、列表/集合/字典推导式(list,set,dict comprehension)众多概念参杂在一起,容易让人晕头转向


二、容器(container)

容器是一种将多个元素组织在一起的数据结构,容器中的元素可以逐个地迭代获取,可以用in和not in

来判断元素是否在容器中存在。这类数据结构大都把所有的元素存储在内存中,python常见的容器有:

  • list ; deque;......
  • set ; frozensets ; ......
  • dict ; defaultdict ; OrderedDict ; Counter ; ......
  • tuple ; namedtuple; ......
  • str

所谓容器,就是字面上的意思--容器,在python这种弱类型的语言中,可以往容器中放任何东西。基本上所有有元素的数据类型(字符串除外)都能包含其他类型的对象;从技术角度来说,当一个对象可以用来询问某个元素是否存在于这个对象中的时候,这个对象就是一个容器。尽管绝大多数容器都提供了某种方式来获取其中的每一个元素,但这并不是容器本身提供的能力,而是 可迭代对象 赋予了容器这种能力,当然并不是所有的容器都是可迭代的


三、可迭代(Iterable)对象

可迭代对象并不是指某种具体的数据类型,它是指存储了元素的一个容器对象,且容器中的元素可以通过__iter__( )方法或__getitem__( )方法访问。如果给定一个list或者tuple,我们可以通过for等方式来遍历这个list或者tuple,这种遍历被称为迭代(Iteration)。

3.1、迭代器与可迭代对象

上面提到,很多容器都是可迭代对象,此外还有很多的对象同样也是可迭代对象,例如打开状态下的files,sockets等。只要能够返回一个迭代器的对象都可以称之为可迭代对象,如下示例:

这里的x就是一个可迭代的对象,可迭代对象并不是指某种具体的数据类型,它是指存储了元素的一个容器对象。y、z是两个迭代器对象。可迭代对象实现了__iter__()和__next__()(python2中是next()),分别对应内置函数iter()和next()

 所有的Iterable可迭代对象均可以通过内置函数iter()来转变为迭代器Iterator。可以这么理解,__iter__( )方法是让对象可以用for ... in循环遍历时找到数据对象的位置,__next__( )方法是让对象可以通过next(实例名)访问下一个元素。除了通过内置函数next调用可以判断是否为迭代器外,还可以通过collection中的Iterator类型判断。如:   isinstance('', Iterator)可以判断字符串类型是否迭代器。注意: list、dict、str虽然是Iterable,却不是Iterator。如果实现了__iter__()则是一个可迭代对象,实现了__iter__(),和__next__() (python2中实现 next() )方法的对象则即是可迭代对象也是迭代器。

from _collections_abc import Iterator, Iterable
if __name__ == '__main__':
    print(isinstance([1,2,3], Iterator))
    print(isinstance([1,2,3], Iterable))

'''
输出结果:
False
True
'''

3.2、迭代器

在迭代器的内部持有一个状态,该状态用于记录代签迭代所在的位置,以方便下次迭代的时候获取正确的元素。其中迭代器有具体的迭代器类型,例如:list_iterator,set_iterator。能在你不断调用内置函数 next() 的时候不断返回容器中的下一个值,直到没有数据的时候抛出StopIteration错误。

3.2.1、创建迭代器(斐波那切数列示例)

class Fab(object):
    
    def __init__(self,m):
        self.max = m
        self.n,self.a,self.b = 0 , 0 , 1 
    
    def __iter__(self):
        return self
    
    def __next__(self):
        if self.n < self.max:
            r = self.a
            self.a,self.b = self.b,self.a+self.b
            self.n += 1
            return r
        raise StopIteration()

if __name__ == '__main__':
    for i in it:
        print(i,end=' ')

'''
输出结果:
0 1 1 2 3 5 8 13 21 

亲测:
当注释掉__iter__()方法之后,Fab(9)既不可迭代也不是一个迭代器,即
print(Fab(9),Iterable)
print(Fab(9),Iterator)
输出全是False

注释掉__next__()则是可迭代的对象,即
print(Fab(9),Iterable)输出True
print(Fab(9),Iterator)输出False
'''

Fabs 类通过 next() 不断返回数列的下一个数,内存占用始终为常数,每次调用 next() 方法的时候做两件事:

  • 1、为下一次调用 next() 方法修改状态
  • 2、为当前这次调用生成返回结果
  • 迭代器就像一个懒加载的工厂,等到有人需要的时候才给它生成值返回,没调用的时候就处于休眠状态等待下一次调用。

3.2.2、for i in (iterator)的内部实现

for首先会调用类中__iter__()方法获取迭代器对象,类似于手动调用内置函数it = iter(...),然后利用这个迭代器对象调用__next__()方法,不断获取下一个值,直到结束。类似示例:

    for i in it:
        print(i,end=' ')

'''
就相当于:
'''

    it = iter(Fab(9))
    while True:
        try:
            print(next(it),end=' ')
        except StopIteration as e:
            pass

四、生成器

生成器其实一种特殊的迭代器,使用关键字yield,使其不再像上面的类一样写 __iter__() 和 __next__() 方法了,生成器也是以一种懒加载的模式生成值。

4.1、yield关键字

4.1.1、看起来像return

菜鸟刚起飞时,可以将yield看做“return”,就是会返回yield后面的值,这里的返回并不是真的将yield后的值当做函数返回值(函数的返回值是一个生成器)直接返回,而是返回到了一个可迭代的容器中,将这个容器返回,同时这个容器也是一个迭代器,使用next()取到这个值,这里的返回并不会使函数体停止,而是使用一个状态记录迭代位置(就像3.2中所描述的那样),等待下一个next()。示例:

示例一:

def foo():
    yield 4
 
print(next(foo()),type(foo()))

'''
输出结果:
4 <class 'generator'>
'''

示例二、

def foo():
    yield 4
    yield 5    
    print('-'*15,end=' ')
    yield 6
    
if __name__ == '__main__':
    g = foo()
    print(next(g),end=' ')
    print(next(g),end=' ')

'''
输出结果:
4 5
'''

def foo():
    yield 4
    yield 5    
    print('-'*15,end=' ')
    yield 6
    
if __name__ == '__main__':
    g = foo()
    print(next(g),end=' ')
    print(next(g),end=' ')
    print(next(g),end=' ')

'''
输出结果:
4 5 --------------- 6 
'''

4.1.2、有yield的函数到底是怎么运行的

'''
以下各个实例中,不同的地方都在
if __name__ == '__main__':中
'''

def foo():
    print('start() foo')
    yield 4
    print('after yield 4')
    yield 5    
    print('after yield 5')
    yield 6
    print('after yield 6')
    
if __name__ == '__main__':
    print('start main')
    g = foo()
    print('after g = foo()')
'''
输出结果:
start main
after g = foo()

也就是说在g=foo()的时候函数体并没有执行
'''


def foo():
    print('start() foo')
    yield 4
    print('after yield 4')
    yield 5    
    print('after yield 5')
    yield 6
    print('after yield 6')
    
if __name__ == '__main__':
    print('start main')
    g = foo()
    print('after g = foo()')
    print(next(g),end=' ')

'''
输出结果:
start main
after g = foo()
start() foo
4 
'''

def foo():
    print('start() foo')
    yield 4
    print('after yield 4')
    yield 5    
    print('after yield 5')
    yield 6
    print('after yield 6')
    
if __name__ == '__main__':
    print('start main')
    g = foo()
    print('after g = foo()')
    print(next(g),end=' ')
    print(next(g),end=' ')
    print(next(g),end=' ')#在这里输出6之后,并不会执行print('after yield 6')
    print(next(g),end=' ') 

'''
输出结果:
start main
after g = foo()
start() foo
4 after yield 4
5 after yield 5
6 after yield 6
Traceback (most recent call last):
  File "D:ProjectsFirstPythonzz疑难杂症new1_yield关键字.py", line 45, in <module>
    print(next(g),end=' ')
StopIteration

当yield后的数被全部取出后,再尝试取数的时候,就会抛出错误!
'''


总结:

①程序开始执行的时候,由于函数中由yield关键字,函数体并不会立即执行,而是返回一个叫做生成器的迭代器(迭代器不一定是生成器,但生成器肯定是迭代器);

②当程序中显式或者隐式地调用next()(直接使用next(),或者调用含有next()函数的函数例如下面提到的send()),生成器函数体才会真正开始执行-----第一次调用next(),将yield关键字后面的对象返回,并停留在第一个yield位置,依次类推;

③当所有yield后的对象全部被取出之后,再调用next(),则会抛出StopIteration错误。

4.2、生成器函数体中有return

当生成器函数体中执行到return时,直接抛出StopIteration错误,结束迭代。示例:

def foo():
    print('start() foo')
    yield 4
    return
    print('after yield 4')
    yield 5    

     
if __name__ == '__main__':
    print('start main')
    g = foo()
    print('after g = foo()')
    print(next(g),end=' ')
    try:
        print(next(g),end=' ')
    except StopIteration as e:
        print('n异常被捕获,结束迭代')

'''
输出结果:
start main
after g = foo()
start() foo
4 
异常被捕获,结束迭代
'''

4.3、send()函数

send(实例)会将实参传递到yield位置,在send()中有一个next(),并将next()取出的对象返回。示例如下:

def foo():
    print('this is in foo()')
    res = yield 4
    yield res
    yield 5
  
      
if __name__ == '__main__':
    print('start main')
    g = foo()
    print('after g = foo()')
    print(next(g))
    print(g.send(56))#在send()中有一个next(),并将next()取出的对象返回
    print(next(g))

'''
输出结果:
start main
after g = foo()
this is in foo()
4
56
5
'''


'''
如果第一次先调用send()而不调用next(),则会报错
'''
if __name__ == '__main__':
    print('start main')
    g = foo()
    print('after g = foo()')
    print(g.send(56))#在send()中有一个next
    print(next(g))
    print(next(g))

'''
输出结果:
start mainTraceback (most recent call last):

after g = foo()
  File "D:ProjectsFirstPythonzz疑难杂症new1_yield关键字.py", line 18, in <module>
    print(g.send(56))#在send()中有一个next
TypeError: can't send non-None value to a just-started generator
'''

需要注意的是:当在send()前使用next()的时候,next()只会讲yield后的对象返回,并停留在对应yield位置,赋值操作并不会执行。换个角度想,对象还没send(),赋值也不可能执行。

4.3、为什么使用生成器?

4.3.1、列表生成式

使用列表生成式可以快速创建一个列表。[]会返回一个列表,而()会返回一个生成器。

a = [x for x in range(10)]
b = [x*2 for x in range(10) if x%2==0]
c = (x for x in range(10))
d = (x*2 for x in range(10) if x%2==0)

print(type(a),"a=",a)
print(type(b),"b=",b)
print('typec>>',type(c))
print('typed>>',type(d))
for i in c:
    print(i,end = ' ')
print()
for i in d:
    print(i,end = ' ')

'''
输出结果:

<class 'list'> a= [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
<class 'list'> b= [0, 4, 8, 12, 16]
typec>> <class 'generator'>
typed>> <class 'generator'>
0 1 2 3 4 5 6 7 8 9 
0 4 8 12 16 
'''

4.3.2、原因

有4.3.1中的示例可见,range()并不是一个生成器,所以,它产生的所有元素将全部存储在内存中。当元素数据巨大时,占用的内存容量自然很大。使用yield来实现,则会节省一部分空间。例如:

def foo(num,n):
    while num<n:
        num=num+1
        yield num
a  = [x for x in foo(0,5)]
print(a)

'''
输出结果:
[1, 2, 3, 4, 5]
'''

五、几个实例

5.1、读取文件

5.1.1、每次读取固定长度。

def read_file(fpath): 
    BLOCK_SIZE = 1024 
    with open(fpath, 'rb') as f: 
        while True: 
            block = f.read(BLOCK_SIZE) 
            if block: 
                yield block 
            else: 
                return

5.1.2、输出最长行的长度

对于文件句柄,也是一个可迭代对象。在for循环内部使用迭代器来读取。

print(max(len(x.strip()) for x in open('/hello/abc','r')))

5.2、一个caodan的示例

def add(s, x):
    print("in add>>>%d+%d=%d"%(s,x,s+x))
    return s + x
 
def gen():
    for i in range(4):
        print('in gen()>>>',i)
        yield i
 
base = gen()

for n in [1,10]:
    print("n in for >>>",n)
    base = (add(i, n) for i in base)
 
print(list(base))

'''
输出结果:
n in for >>> 1
n in for >>> 10
in gen()>>> 0
in add>>>0+10=10
in add>>>10+10=20
in gen()>>> 1
in add>>>1+10=11
in add>>>11+10=21
in gen()>>> 2
in add>>>2+10=12
in add>>>12+10=22
in gen()>>> 3
in add>>>3+10=13
in add>>>13+10=23
[20, 21, 22, 23]

base = (add(i, n) for i in base)这个东西返回一个生成器
'''

5.3、实现多次迭代

我们知道iterator只能迭代一次,但是iterable对象则没有这个限制,因此我们可以把iterator从数据中分离出来,分别定义一个iterable与iterator如下:

class Data(object): # 只是iterable:可迭代对象而不iterator:迭代器
    def __init__(self, *args):
        self.data = list(args)
 
    def __iter__(self): # 并没有返回自身
        return DataIterator(self)
 
 
class DataIterator(object): # iterator: 迭代器
 
    def __init__(self, data):
        self.data = data.data
        self.ind = 0
 
    def __iter__(self):
        return self
 
    def __next__(self):
        if self.ind == len(self.data):
            raise StopIteration
        else:
            data = self.data[self.ind]
            self.ind += 1
            return data
 
if __name__ == '__main__':
    d = Data(1, 2, 3)
    for x in d:
        print(x,end=' ')
    print('n')
    for x in d:
        print(x,end=' ')

'''
输出结果:
1 2 3 

1 2 3 
'''

六、参考文章

https://www.cnblogs.com/yuanchenqi/articles/5769491.html

https://blog.csdn.net/mieleizhi0522/article/details/82142856/

https://www.jb51.net/article/80740.htm

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

最后

以上就是知性黑裤为你收集整理的Python菜鸟起飞day7_迭代器与生成器(5.2未想通)一、概述二、容器(container)三、可迭代(Iterable)对象四、生成器五、几个实例六、参考文章的全部内容,希望文章能够帮你解决Python菜鸟起飞day7_迭代器与生成器(5.2未想通)一、概述二、容器(container)三、可迭代(Iterable)对象四、生成器五、几个实例六、参考文章所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(60)

评论列表共有 0 条评论

立即
投稿
返回
顶部