python---基础知识回顾(七)迭代器和生成器

前戏:迭代器和生成器

迭代:数据结构

若是给定一个list或tuple,咱们能够经过for循环来遍历这个list或tuple,这种遍历咱们称为迭代(Iteration)。
Python的for循环不只能够用在list或tuple上,还能够做用在其余可迭代对象上(用isinstance判断)

能够直接做用于for循环的对象统称为可迭代对象ide

能够直接做用于for循环的数据类型有如下几种:
一类是集合数据类型,如list、tuple、dict、set、str等;(称为容器<容器是一种把多个元素组织在一块儿的数据结构>,不少容器都是可迭代的) 一类是generator,包括生成器和带yield的generator function。 这些能够直接做用于for循环的对象统称为可迭代对象:Iterable。

(一)迭代器

一个实现了__iter__方法的对象是可迭代的,一个实现了__next__方法的对象则是迭代器

对于序列和字典的可迭代,是由于在该对象中实现了上面的两个方法函数

__iter__方法会返回一个迭代器,而所谓的迭代器就是具备__next__方法的对象。在调用__next__方法时,迭代器会返回他的下一个值。如果next方法被调用牡丹石迭代器中没有值能够返回,就会引起一个StopIteration异常spa

迭代器的优势:须要数据就去获取,而不是一次获取所有数据code

 

相对于咱们一次性取出数据,放在列表等类型中,若数据量过大,那么列表会占据大量的内存。并且对于这些数据,咱们如果只使用一次就释放的话,那么放在列表中实在是太过浪费内存。
更好的方法就是使用迭代器。迭代器只会取出当前须要的数据方法内存中。

例如Django中的queryset惰性机制中就有说起迭代器的好处(在处理大量的数据时)对象

栗子:不使用列表的案例,由于若是使用列表,那么列表长度将会是无穷大。占据空间将会是巨大的。blog

斐波那契数列:内存

class Fibs:
    def __init__(self):
        self.a = 0
        self.b = 1

    def __next__(self):
        self.a,self.b = self.b,self.a+self.b
        return self.a

    def __iter__(self):
        return self

f = Fibs()

for i in f:
    if i > 1000:
        print(i)    #1597
        break

补充:内建函数iter能够从可迭代的对象中获取迭代器ci

>>> a = [1,2,3,]
>>> b = iter(a)
>>> type(b)
<class 'list_iterator'>
>>> next(b)
1
>>> next(b)
2
>>> next(b)
3
>>> next(b)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

能够知道迭代器是一次性消耗品(只会向前获取,不会向后获取),当耗尽时就会触发StopIteration异常
如果想保留一份数据,能够用deepcopy

从迭代器中获取序列:作用域

class Fibs:
    def __init__(self):
        self.a = 0
        self.b = 1

    def __next__(self):
        self.a,self.b = self.b,self.a+self.b
        if self.a > 1597:
            raise StopIteration return self.a

    def __iter__(self):
        return self

f = Fibs()

ls = list(f)
print(ls) #[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597]

使用list构造方法显示的将迭代器转换为列表

class list(object):
        def __init__(self, seq=()): # known special case of list.__init__
        """
        list() -> new empty list
        list(iterable) -> new list initialized from iterable's items
     (如果迭代器,那么新的列表则是迭代器的全部成员,结束是以StopIteration为标志,如果上面没有触发,那么会一直去扩展列表)
# (copied from class doc) """ pass

 结束

 


应该还记得列表推导式(生成式)<顺道回忆下lambda表达式>

>>> [x for x in range(100) if x % 7 == 0]
[0, 7, 14, 21, 28, 35, 42, 49, 56, 63, 70, 77, 84, 91, 98]

经过列表推导式,咱们能够直接生成一个列表,一样的,这个列表的内存也是受到限制的,当咱们使用列表推导式,一次生成一个超大数量的列表,会占据大量内存,然而,若咱们只是访问了前面几个,那么后面的空间占用几乎是无用的。

for i in [x for x in range(100) if x % 7 == 0]:
    if i < 50:
        print(i)
    else:
        break

在上面案例中,咱们只是想去获取知足条件的数据的一部分。可是在进行循环时,并不会马上进行,而是须要将列表生成式所有执行后,才容许去进行循环。而咱们所须要的数据仅仅是列表中的前一部分,可是列表推导式一次性将数据所有生成。占据大量无用的空间。那么咱们是否能够作到像迭代器那样,须要的时候再去获取。从而避免数据冗余

(二)生成器

生成器都是迭代器。生成器是一种用普通函数语法定义的迭代器

建立一个生成器方法有多种:

其中第一种与列表推导式十分类似,只是须要将中括号[]变为小括号()

>>> b = [x for x in range(100) if x % 7 == 0]
>>> type(b)
<class 'list'>
[0, 7, 14, 21, 28, 35, 42, 49, 56, 63, 70, 77, 84, 91, 98]

>>> b = (x for x in range(100) if x % 7 == 0)
>>> type(b)
<class 'generator'>
>>> next(b)
0
>>> next(b)
7
>>> next(b)
14
>>> for i in b:
... print(i)

当数据所有取出后也会触发StopIteration错误

 

另一种是:任何包括yield语句的函数均可以称为生成器。

这里一样以斐波那契数列为例:

def fibs(max):
    n,a,b = 0,0,1
    while n < max:
        yield a  #yield语句
        n += 1
        a, b = b, a+b


f = fibs(8)

for i in f:
    print(i)    #0 1 1 2 3 5 8 13

生成器和普通函数的行为有很大的区别。

  1. 不像return返回一次结果就结束函数,而是能够返回屡次结果。从什么for循环能够看出,这一个函数返回了不止一次结果
  2. 每产生一个值(即在yield语句中返回的值),函数就会被冻结,不在执行:即函数停在那点等待被从新唤醒。被从新唤醒后就从以前通知的那点开始执行
def 函数:
    ...
    yield  1    执行第一次后返回值1后冻结,不在执行,等待第二次
    ...    #执行第二次时会向下继续执行,直到下一次yield
    ...
    ...
    yield  2    #第二次冻结(这个过程包括了执行上面的逻辑语句)
    ...
    ...
    ...
    yield  3

案例:

>>> def flatten(nested):
...     for sublist in nested:
...             for ele in sublist:
...                     yield ele

>>> for num in flatten(nested):
...     print(num)
...
1
2
3
4
5
>>>

也能够同上面迭代器同样使用list显示转换为列表。

>>> list(flatten(nested))
[1, 2, 3, 4, 5]
>>>

可是这样会马上实例化列表,丧失了迭代的优点。

def nrange(num):
    temp = -1
    while True:
        temp = temp + 1
        if temp >= num:
            return
        else:
            yield temp

for i  in nrange(10):
    print(i)
含有return的自定义nrange生成器

通用生成器:

生成器是一个包含yield关键字的函数。当他被调用的时候,在函数体中的代码不会执行,而是会返回一个迭代器。每次请求一个值,就会执行生成器中的代码,直到遇到一个yield或者return语句。(yield意味着生成一个值,并冻结执行,等待下一次执行。return意味着生成器要通知执行)

生成器由两部分组成:生成器的函数和生成器的迭代器。生成器的函数使用def语句定义的,包含yield的。生成器的迭代器是这个函数的返回部分。合在一块儿就是生成器。

生成器方法:

生成器中新特征:能够为生成器提供值,而不是只像上面那样生成器为外面返回值。生成器内外能够进行交流

外部做用域访问生成器的send方法,能够向生成器内部传递消息(任意对象),此时yield再也不是一个返回值语句,而是一个表达式

send方法和yield语句的执行区别:

yield

def rep2(val):
yield val print("aaa") val += 10 print("bbb") yield val r = rep2(33) print(next(r))  #能够看出,执行yield返回值后,就冻结在该条语句,再也不向下执行,只有当下一个next出现,才会继续执行
def rep(val):new = (yield val)  #接收send发送过来的数据
        if new:
            print(new)
        new = (yield val)
        if new:
            print(new)

f = rep(33)
v = next(f)

f.send("dsad")  #会打印出来dasd  能够看出,当send发送数据后,在接收数据后,会继续向下执行,直到下一个yield表达式出现

 

send方法使用:

注意:在使用send方法时,只有当生成器挂起之后才有意义(也就是说:在yield函数第一次执行以后)

def rep(val):
        new = (yield val)
        if new:
            print(new)
        new = (yield val)
        if new:
            print(new)

开始执行:

若没有将生成器挂起:

TypeError: can't send non-None value to a just-started generator

因此,咱们在使用时须要先挂起生成器。挂起方法有两种:

第一种:

r = rep(33)
v = next(r)  #这里正常执行next获取yield返回,后面就能够正常使用send
print(v)

r.send("dsad")
r.send("dsadds")

第二种(由刚刚的TypeError能够知道不能send一个非None值,在第一次时,因此咱们能够直接在第一次时send(None)):

r.send(None)
r.send("dsad")
r.send("dsadds")

两种方法,强烈推荐第二种

缘由:使用send方法时,须要注意两点

1.须要先将生成器挂起,此时才有意义

2.send方法第一次使用时,也是须要进行一次next()方法执行,或者send(None)执行

第2条件是在咱们使用send前有其余yield语句返回时,能够了解到

def rep(val):
        yield val
        new = (yield val)
        if new:
            print(new)
        new = (yield val)
        if new:
            print(new)

r = rep(33)
v = next(r)  #挂起生成器
print(v)
v = next(r)  #激活send方法
print(v)
r.send("dsad")
r.send("dsadds")
---------------- 正常输出 #
33 #33 #dsad #dsadds
如果只是挂起了生成器,没有激活send方法,那么默认第一个send方法会拿去激活
r = rep(33) v = next(r) print(v) # v = next(r)  #没有去激活send方法 # print(v) r.send("dsad")  #第一个send方法会被用到去激活send r.send("dsadds")    #这个才是正常的信息传入
----------

 #33
 #dsadds

因此咱们最好使用send(None)表示去激活send方法,不易混淆

def rep(val):
        yield val
        new = (yield val)
        if new:
            print(new)
        new = (yield val)
        if new:
            print(new)



r = rep(33)

v = next(r)    #挂起生成器         也能够用send(None)去挂起生成器,可是仍是不要这样作,两个套用容易混淆
print(v)

r.send(None)    #激活send方法(在首次使用send时使用)

r.send("dsad")
r.send("dsadds")
--------------

 #33
 #dsad
 #dsadds

另外补充下send方法会获取到yield表达式中的返回值

def rep(val):
        yield val
        new = (yield val)
        if new:
            print(new)
        new = (yield val)
        if new:
            print(new)



r = rep(33)
v = next(r)
print(v)

v = r.send(None)
print(v,1)
v = r.send("dsad")
print(v,2)
v = r.send("dsadds")  #在最后一个send方法时,没有返回值
print(v,3)
-----------------------
33
33 1
dsad
33 2
dsadds

send方法会依次获取yield表达式的返回值,因此在第三个send方法使用时,并无yield与之对应,因此没有值。

具体缘由暂不讨论。

注意区分yield语句和yield表达式

生成器的定义是:包含yield语句的函数是生成器

 

再进一步讨论:send方法

 1 def rep(val):
 2         yield val  #这里已经含有yield语句,此函数是生成器,咱们在下面执行的next()只是正常执行这条语句
 3         print("t1")
 4         new = (yield val)
 5         print("t2")
 6         if new:
 7             print("t3")
 8             print(new)
 9         new = (yield val)
10         if new:
11             print(new)
12 r = rep(33)

 

(1):正常执行yield语句(行2),返回值,而且冻结到行2,不在向下执行

v = next(r)
print(v)
-------
#33

那么如何执行到下面的yield表达式

(2)这时就须要一条语句,去联系yield语句和yield表达式

v = next(r)
print(v)

v = r.send(None)  
print(v,1)
---------------------
33

t1 33 1
由上面的两个结果能够看出send(None)执行的语句是
yield val
print("t1")
new = (yield val)
这两条描红语句,由上面的yield语句,执行到下面yield表达式中的返回值语句中,因此send(None)的返回值就是这里的yield返回的值

(3)下面的send方法执行一致

v = next(r)
print(v)

v = r.send(None)
print(v,1)
v = r.send("dsad")
print(v,2)

----------------------
33
t1
33 1

由上面(2)的yield表达式开始(原来只是执行到返回值),如今开始赋值(send("dasd"))传递进去,而后执行到下一条yield返回值语句 t2 t3 dsad
------------------------
new = (yield val)  从这里赋值开始,到下面返回值结束
print("t2")
if new:
print("t3")
print(new)
new = (yield val)

 (4)最后一步执行

v = next(r)
print(v)

v = r.send(None)
print(v,1)
v = r.send("dsad")
print(v,2)
v = r.send("dsadds")
print(v,3)

--------------------------
33
t1
33 1
t2
t3
dsad
33 2
dsadds
--------------------------
new = (yield val)
if new:
print(new)
这里语句中不在含有返回值,因此咱们最后没法接收到值

当前面没有yield语句时,执行也是类似的

def rep(val):
        # yield val
        print("t1")      #先执行print('t1')和yield val
        new = (yield val)   #而后执行赋值和向下执行到下一个yield 返回值语句
        print("t2")
        if new:
            print("t3")
            print(new)
        new = (yield val)  #执行赋值,而且将下面语句执行完成*无返回值
        if new:
            print(new)

-----------------------

r = rep(33)

v = r.send(None)
print(v,1)
v = r.send("dsad")
print(v,2)
v = r.send("dsadds")
print(v,3)    #因为无返回值,不会去执行
-----------------------
t1 33 1 t2 t3 dsad 33 2 dsadds

能够看为:

若前面没有yield语句,则须要进行生成器挂起。(使用next或者send(None)),而后再使用send执行,方法类似

如有yield语句,就已是生成器,咱们只须要正常执行他(使用next或者send(None)),而后须要再次使用去激活send方法(链接yield语句和yield表达式中的返回值语句).....

 

案例:生成器实现文件流

#文件输入流
def FileInputStream(filename):
    try:
        f = open(filename,"r")
        for line in f:
            for byte in line:  #按字节获取数据
                yield byte
    except Exception as e:
        print(repr(e))  #正常读取文件无错误
    finally:
        f.close()
        return

#文件输出流 def FileOutputStream(inputStream,filename):
try: f = open(filename,"w") while True: byte = next(inputStream) #如果在调用next方法时,迭代器没有值能够返回,就会引起一个StopIteration错误 f.write(byte) except StopIteration as e: print(repr(e)) #StopIteration() f.close() return FileOutputStream(FileInputStream('f'),"t2")
相关文章
相关标签/搜索