迭代器和生成器

一. python的迭代协议

  1.什么是迭代器:

    迭代器是访问集合内数据的一种方式,一般是用来遍历数据的    

  2.迭代器和以下标访问的方式不一样,迭代器是不能返回的,且迭代器提供了一种惰性访问数据的方式

  3.python中可迭代的对象基本都是实现了__iter__魔法函数的,迭代协议实质上就是满足__iter__魔法函数

可迭代类型

迭代器

from collections.abc import Iterable,Iterator
a=[1,2]
#list是可迭代的,但不是一个迭代器(list中实现了__iter__,但是没有实现__next__)
print(isinstance(a,Iterable))
print(isinstance(a,Iterator))
'''
输出:
True
False
'''

二. 什么是迭代器和可迭代对象

  1.可迭代和迭代器的区别:

from collections.abc import Iterable,Iterator
a=[1,2]
#迭代器,内置函数iter()会首先检查是否有__iter__,如果没有就会调用__getitem__首先创建一个迭代器
iter_rator=iter(a)
print(isinstance(a,Iterable))
print(isinstance(a,Iterator))
print(isinstance(iter_rator,Iterable))
print(isinstance(iter_rator,Iterator))
'''
输出:
True
False
True
True
'''

  2.__iter__和__getitem__:

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

    def __iter__(self):
        return 1

    def __getitem__(self, item):
        return self.employee_list[item]


if __name__ == '__main__':
    com = Comany(['LYQ1', 'LYQ2', 'LYQ3'])
    #内部实质也是iter,首先调用__iter__(如果有),否则调用__getitem
    for i in com:
        print(i)

 __iter__和__getitem__都实现,首先调用__iter__

 只实现__getitem__

__iter__和__getitem__都不实现

  3.自己实现迭代器:

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

    def __iter__(self):
        return MyIterator(self.employee_list)


class MyIterator(Iterator):
    def __init__(self, emp_list):
        self.iter_list = emp_list
        self.index = 0

    def __next__(self):
        # 真正返回迭代值的逻辑,迭代器不支持切片,但__getitem__支持且可以取任意位置的数
        try:
            word = self.iter_list[self.index]
        except IndexError:
            raise StopIteration
        self.index += 1
        return word


if __name__ == '__main__':
    com = Comany(['LYQ1', 'LYQ2', 'LYQ3'])
    my_itor = iter(com)
    # for循环内部其实也是调用next放法
    while True:
        try:
            # my_itor为一个迭代器,实际在__next__中实现
            print(next(my_itor))
        except StopIteration:
            pass

三. 生成器函数使用

  1.什么是生成器函数:函数里只要有yeild关键字:

    生成器函数返回的是一个生成器对象,是在python编译字节码的时候就产生了;

    return只能有一个返回值,而yield可以返回多个,生成器对象实现了迭代协议; 

    惰性求值,延迟求值提供了可能。

def gen_fun():
    #首先把值返回给调用方
    #然后又调用__next__调用下一个(实现了生成器协议)
    yield 'a'
    yield 'b'
def fun():
    return 1
#生成器函数返回的是一个生成器对象,实在python编译字节码的时候就产生了
#return只能有一个返回值,而yield可以返回多个
c1 = gen_fun()
c2 = fun()
print(c1)
for i in c1:
    print(i)
print(c2)
'''
<generator object gen_fun at 0x000001AF4399D048>
a
b
1
'''

  2.例:

    使用return:

#斐波拉契过程

def fib(index):
    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
#若果index太大,十分消耗内存,这时就可以用到yield
print(fib(10))
'''
输出:
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
'''

    使用生成器:

#使用生成器的斐波拉契过程

def gen_fib(index):
    n,a,b=0,0,1
    while n<index:
#将值yield,不会消耗内存    
        yield b
        a,b=b,a+b
        n+=1
for i in gen_fib(10):
    print(i)

四. 生成器的原理

  1.函数工作原理:

    python.exe会用一个叫做PyEval_EvalFrameEx(c语言函数)去执行函数,首先会创建一个栈帧(stack frame)

def foo():
    bar()


def bar():
    pass
#python.exe会用一个叫做PyEval_EvalFrameEx(c语言函数)去执行foo()函数,首先会创建一个栈帧(stack frame)
"""
python一切皆对象,栈帧对象, 字节码对象
当foo调用子函数 bar, 又会创建一个栈帧
所有的栈帧都是分配在堆内存上,这就决定了栈帧可以独立于调用者存在
"""
def foo():
    bar()
def bar():
    pass
#查看字节码,以下为查看foo函数的字节码
import dis
print(dis.dis(foo))
'''
 2            0 LOAD_GLOBAL              0 (bar) ——》加载目标函数
              2 CALL_FUNCTION            0       ——》执行函数
              4 POP_TOP                          ——》从栈顶端pop出
              6 LOAD_CONST               0 (None)——》加载return值,没有就None
              8 RETURN_VALUE                     ——》将值return
'''
import inspect
frame=None
def foo():
    bar()
def bar():
    global frame
    frame=inspect.currentframe()
foo()
#即使函数运行完成,也可以查看函数的栈帧,是放在堆上面的,和静态语言不一样(放在栈上,运行后就销毁了)
print(frame.f_code.co_name)
caller_frame=frame.f_back
print(caller_frame.f_code.co_name)
'''
输出:
bar
foo
'''

   2.生成器工作原理:(生成器对象也是放在堆内存中的,可以独立于调用者存在,任何地方都可以控制它)

函数工作原理

对生成器对象封装,f_lasti: 最近执行代码字节码的位置,以及f_locals:变量,因为有这两个变量,就可以不断循环函数

def gen_fun():
    #生成器函数可以return一个值,早期的python不行
    #编译字节码时,识别了关键词yield,标记函数
    yield 1
    name='LYQ'
    yield 2
    age=20
    return 'HaHa'
import dis
gen=gen_fun()
print(dis.dis(gen))
print(gen.gi_frame.f_lasti)
print(gen.gi_frame.f_locals)
next(gen)
print(gen.gi_frame.f_lasti)
print(gen.gi_frame.f_locals)
next(gen)
print(gen.gi_frame.f_lasti)
print(gen.gi_frame.f_locals)
'''
输出:
  4           0 LOAD_CONST               1 (1)
              2 YIELD_VALUE
              4 POP_TOP

  5           6 LOAD_CONST               2 ('LYQ')
              8 STORE_FAST               0 (name)

  6          10 LOAD_CONST               3 (2)
             12 YIELD_VALUE
             14 POP_TOP

  7          16 LOAD_CONST               4 (20)
             18 STORE_FAST               1 (age)

  8          20 LOAD_CONST               5 ('HaHa')
             22 RETURN_VALUE
None
-1——》还没开始执行
{}
2 ——》执行到第一个yield的位置(yield 1)
{}
12——》执行到第二个yield的位置(yield 2)
{'name': 'LYQ'}——》把局部变量拿出来了
'''

五. 通过UserList来看生成器的应用

  

实现了遍历list,i是局部变量,从0开始,一步一步返回值

六. 生成器实现大文件读取

#读取大文件
def myreadlines(f, newline):
  #buf相当于缓存
  buf = ""
  while True:
    while newline in buf:
      pos = buf.index(newline)
      yield buf[:pos]
      #更新截断buf(以分割符)
      buf = buf[pos + len(newline):]
    #每次读取4096个字符(接着上一次的)
    chunk = f.read(4096)

    if not chunk:
      #说明已经读到了文件结尾
      yield buf
      break
    buf += chunk

with open("input.txt") as f:
    for line in myreadlines(f, "{|}"):
        print (line)
原文地址:https://www.cnblogs.com/lyq-biu/p/10438189.html