08-迭代器和生成器

一、迭代器和生成器

  迭代是Python最强大的功能之一,是访问集合元素的一种方式。迭代器是一个可以记住遍历的位置的对象。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。迭代器有两个基本的方法:iter() 和 next()。

  在 Python 中,使用了 yield 的函数被称为生成器(generator)。跟普通函数不同的是,生成器是一个返回迭代器的函数只能用于迭代操作,更简单点理解生成器就是一个迭代器。在调用生成器运行的过程中,每次遇到 yield函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next() 方法时从当前位置继续运行。调用一个生成器函数,返回的是一个迭代器对象。

1.1、python中的迭代协议

  什么是迭代协议?  迭代协议:一个是 __iter__(也就是可迭代类型),另一个是Iterator(迭代器),迭代器是什么? 迭代器是访问集合内元素的一种方式, 一般用来遍历数据。迭代器和以下标的访问(list[0])方式不一样, 迭代器是不能返回的, 迭代器提供了一种惰性数据的方式。

#list[item]访问的原理是__getitem__
#[] list , __iter__
#Iterable继承的是抽象基类(metaclass=ABCMeta)(__iter__)
#Iterator(迭代器)这个是继承了Iterable,实现的魔法函数有__iter__,__next__
from collections.abc import Iterable, Iterator
a = [1,2]  #list是实现__iter__是可迭代类型,可迭代类型与迭代器(__iter__,__next__)是不一样的
print(isinstance(a, Iterable))#True
print(isinstance(a, Iterator))#False

1.2、什么是迭代器和可迭代对象

from collections.abc import Iterator

class Company(object):
    def __init__(self, employee_list):
        self.employee = employee_list

    def __iter__(self):
        return MyIterator(self.employee) #返回的是迭代器,这样自定义迭代器复用性更强

    # def __getitem__(self, item): #能切片,迭代器是不能切片的
    #     return self.employee[item]


class MyIterator(Iterator):
    def __init__(self, employee_list):
        self.iter_list = employee_list
        self.index = 0 #用迭代器维护这个变量

    def __next__(self):
        #真正返回迭代值的逻辑
        try:
            res= self.iter_list[self.index]
        except IndexError:
            raise StopIteration
        self.index += 1
        return res

if __name__ == "__main__":
    company = Company(["li", "lily", "john"])
    my_itor = iter(company)
    # while True:           #for循环的实现原理
    #     try:
    #         print (next(my_itor))
    #     except StopIteration:
    #         pass

    # 当调用迭代器的时候会这样调用next(my_itor)
    for item in company: #当调用for循环的时候,它会尝试去调用iter(company),首先找的是__iter__,
        print(item)       # 如果没有的话,它就会找__getitem__,

1.3、生成器函数的使用

  生成器函数,函数里只要有yield关键字。

def gen_func():#提交的值都能for循环打印出来
    yield 1
    yield 2
    yield 3

def fib(index):  #1\求斐波拉契只能看见最终结果,看不到过程
    if index <= 2:
        return 1
    else:
        return fib(index-1) + fib(index-2)

def fib2(index): #2\改动之后,将每次产生的值添加到列表中,就可以看到过程中产生的值
    re_list = []
    n,a,b = 0,0,1
    while n<index:
        re_list.append(b)
        a,b = b, a+b
        n += 1
    return re_list

def gen_fib(index):#3\用生成器改动,将每次结果yield出来,
    n,a,b = 0,0,1
    while n<index:
        yield b
        a,b = b, a+b
        n += 1

# 斐波拉契 0 1 1 2 3 5 8
#惰性求值, 延迟求值提供了可能

def func():
    return 1

if __name__ == "__main__":
    #生成器对象, python编译字节码的时候就产生了,
    gen = gen_func()
    for value in gen:
        print (value,end=' ') #1 2 3
    # re = func()
    #将yield提交的值遍历出来
    for data in gen_fib(10):
        print(data,end=' ') #1 1 2 3 5 8 13 21 34 55 

1.4、python是如何实现生成器的

#1.python中函数的工作原理

import inspect
frame = None
def foo():
    bar()
def bar():
    global frame
    frame = inspect.currentframe()

#python.exe会用一个叫做 PyEval_EvalFramEx(c函数)去执行foo函数, 首先会创建一个栈帧(stack frame)
"""
python一切皆对象,栈帧对象,编译成字节码对象
当foo调用子函数 bar, 又会创建一个栈帧
所有的栈帧都是分配在堆内存(栈帧一直在堆内存之中)上,这就决定了栈帧可以独立于调用者存在
"""
# import dis
# print(dis.dis(foo)) #查看运行的字节码过程

foo()
print(frame.f_code.co_name) #bar
caller_frame = frame.f_back
print(caller_frame.f_code.co_name) #foo  #虽然这个函数已经运行完成,我们照样可以去堆内存中找到它的栈帧


def gen_func():
    yield 1
    name = "lishuntao"
    yield 2
    age = 18
    return "HUT"

import dis
gen = gen_func() #返回生成器对象
print (dis.dis(gen))
#人为控制它的执行流程
print(gen.gi_frame.f_lasti) #-1      f_lasti:可以知道进行到哪一个步骤了
print(gen.gi_frame.f_locals) #{}
next(gen)  #进行下一步流程
print(gen.gi_frame.f_lasti)#2
print(gen.gi_frame.f_locals)#{}
next(gen)
print(gen.gi_frame.f_lasti)#12
print(gen.gi_frame.f_locals)#{"name":"lishuntao"}

 

 

1.5、生成器在UserList中的应用

class Company:
    def __getitem__(self, item):
        pass
    def __iter__(self):
        pass
company = Company()
iter(company)  #调用iter的时候,会去对象中寻找__iter__,如果没有的话就去寻找__getitem__

#生成器在list中的应用,如果我们要继承list的话,就继承UserList,因为list是C语言写出来的,UserList是利用python写出来的。
from collections import UserList #UserList中继承的序列Sequence中的__iter__里面就运用生成器实现的数据迭代

1.6、生成器如何读取大文件

当在工作中,应用生成器提取大文件(一行大文件)。

#大文件, 特殊的一行
def myreadlines(f, newline):
  buf = ""     #设置空的缓存区
  while True:
    #第一次进入buf为空,直接跳到下面读取文件
    while newline in buf:
      pos = buf.index(newline)
      yield buf[:pos]  #提交第一行
      buf = buf[pos + len(newline):] #将提交出去的舍弃掉
    chunk = f.read(4096)

    if not chunk:
      #说明已经读到了文件结尾,然后将buf提交出去
      yield buf
      break
    buf += chunk

with open("input.txt") as f:
    for line in myreadlines(f, "{|}"):
        print (line)
"""
Prior to beginning tutoring sessions
, I ask new students to fill
 out a brief self-assessment
 where they rate their
 understanding of various Python concepts. Some topics ("control flow with if/else" or "defining and using functions") are understood by a majority of students before ever beginning tutoring. There are a handful of topics, however, that almost all
 students report having no knowledge or very limited understanding of. Of these
"""
posted @ 2019-12-11 15:41  一知.半解  阅读(...)  评论(...编辑  收藏