# 第九章:迭代器和生成器

# 9.1 python中的迭代协议

什么是迭代协议?

  • Iterable
  • Iterator

迭代器是什么?

迭代器是访问集合内元素的一种方式,一般用来遍历数据。 迭代器和以下标访问方式不一样,迭代器是不能返回的,迭代器提供了一种惰性访问数据的方式


    from collections.abc import Iterable, Iterator
    a = [1, 2]
    print(isinstance(a, Iterable))
    print(isinstance(a, Iterator))

# 9.2 什么是迭代器和可迭代对象

实现 iter 时,必须返回 Iterator 对象


    from collections.abc import Iterator


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

        def __next__(self):
            # 真正返回迭代值的逻辑
            # 迭代器不支持切片,不会接收索引值,只能一步一步走
            # 遍历大文件
            try:
                word = self.employee[self.index]
            except IndexError:
                raise StopIteration
            self.index += 1
            return word


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

        # def __iter__(self):
        #     return 1        # TypeError: iter() returned non-iterator of type 'int'

        # def __iter__(self):
        #     return self     # TypeError: iter() returned non-iterator of type 'Company'

        # 使用内置方法 iter
        # def __iter__(self):
        #     return iter(self.employee)  # <iterator object at 0x000001F512B907C8>

        # 使用自定义 MyIterator ******
        def __iter__(self):
            return MyIterator(self.employee)    # <__main__.MyIterator object at 0x0000013462EF0848>

        def __getitem__(self, index):
            return self.employee[index]


    if __name__ == '__main__':
        company = Company(['linda', 'alex', 'catherine'])
        my_iterator = iter(company)
        print(my_iterator)
        # for 循环首先查找 __iter__;如果没有自动生成一个__iter__,里面遍历__getitem__
        # for item in company:
        #     print(item)

        while True:
            try:
                print(next(my_iterator))
            except StopIteration:
                break

        """
        迭代器设计模式,不要在Company中实现 __next__ 方法,而要单独实现MyIterator实现,Company中__iter__调用MyIterator就行
        """
  • Comanpy实例化的对象为可迭代对象,可用for循环遍历数据,内部实现了__iter__方法,该方法返回迭代器
  • MyIterator实现化对象为迭代器,可用next()获取数值,内部实现了__iter__和__next__方法

# 9.3 生成器函数的使用

生成器函数,函数里包含 yield 关键字

  • yield
  • 不再是普通的函数

    def gen_func():
        yield 1
        yield 2
        yield 3

    # 惰性求值,延迟求值提供了可能性
    # 斐波拉契函数 0 1 1 2 3 5 8 ...
    def fib(index):
        if index <= 2:
            return 1
        else:
            return fib(index-1) + fib(index-2)

    def func():
        return 1

    if __name__ == '__main__':
        # 返回为生成器对象,python编译字节码的时候产生
        gen = gen_func()

        # 生成器对象也是实现了迭代协议的,可以for循环
        for value in gen:
            print(value)

        ret = func()
  • 执行生成器函数得到生成器对象,可for循环取值
  • 生成器函数可以多次返回值,流程的变化

    # 获取对应位置的值
    def fib(index):
        if index <= 2:
            return 1
        else:
            return fib(index-1) + fib(index-2)


    # 获取整个过程
    def fib2(index):
        ret_list = []
        n, a, b = 0, 0, 1
        while n < index:
            ret_list.append(b)
            a, b = b, a + b
            n += 1
        return ret_list


    # yield
    def gen_fib(index):
        n, a, b = 0, 0, 1
        while n < index:
            yield b
            a, b = b, a + b
            n += 1


    print(fib(10))
    print(fib2(10))
    for value in gen_fib(10):
        print(value)

斐波拉契 1 1 2 3 5 8 ...

  • 根据位置获取对应值
  • 根据位置获取所有值

# 9.4 python是如何实现生成器的

  • 什么场景下运用生成器
  • 生成器内部实现原理
  • 生成器函数与普通函数区别

# python中函数工作原理

python.exe会用一个叫做PyEval_EvalFrameEx(c函数)去执行foo函数 首先会创建一个栈帧(stack_frame),一个上下文


    import inspect
    frame = None

    def foo():
    bar()

    def bar():
    global frame
    frame = inspect.currentframe()

    # 查看函数实现原理
    # 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

python中一切皆对象,栈帧对象中运行foo函数字节码对象 当foo调用子函数bar,又会创建一个栈帧对象,在此栈帧对象中运行bar函数字节码对象

所有的栈帧都是分配再堆内存上(不会自动释放),这就对定了栈帧可以独立于调用者存在;不用于静态语言的调用,静态语言是栈的形式,调用完就自动释放

# python中生成器函数工作原理


    def gen_func():
        address = 'China'
        yield 1
        name = 'linda'
        yield 2
        age = 20
        return 'done'


    gen = gen_func()

    import dis
    print(dis.dis(gen))

    print(gen.gi_frame.f_lasti)
    print(gen.gi_frame.f_locals)

    print('\nfirst value: %s' % next(gen))
    print(gen.gi_frame.f_lasti)
    print(gen.gi_frame.f_locals)

    print('\nsecond value: %s' % next(gen))
    print(gen.gi_frame.f_lasti)
    print(gen.gi_frame.f_locals)

  • 控制整个生成器函数暂定和继续前进 gen.gi_frame.f_lasti
  • 整个生成器函数作用域逐渐变化 gen.gi_frame.f_locals

# 9.5 生成器在UserList中的应用

  • from collections import UserList

    class MyList:
        def __init__(self):
            self.data = []

        def __getitem__(self, index):
            return self.data[index]

        def __setitem__(self, index, value):
            self.data[index] = value

        def insert(self, index, item):
            self.data.insert(index, item)


    ll = MyList()
    ll.insert(0, 1)
    ll.insert(0, 2)
    ll.insert(0, 3)
    print(ll.data)
  • from collections import UserDict

    class MyDict:
        def __init__(self):
            self.data = {}

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

        def __setitem__(self, key, value):
            self.data[key] = value

        def update(self, **kw):
            for key, value in kw.items():
                self[key] = value

    dd = MyDict()
    print(dd.data)

    dd.update(key1='value1', key2='value2')
    print(dd['key1'])
    print(dd.data)

具体源码分析参考模块 collections

# 9.6 生成器如何读取大文件

场景:500G 文件 特殊只有一行,特殊分割符号 {|}


    def my_readline(f, newline):
        buf = ''
        while True:
            while newline in buf:
                pos = buf.index(newline)
                yield buf[:pos]
                buf = buf[pos + len(newline):]
            chunk = f.read(4096 * 10)
            if not chunk:
                yield buf
                break
            buf += chunk

    with open('input') as f:
        for line in my_readline(f, '{|}'):
            print(line)
上次更新: 8/26/2022, 2:06:10 PM