Python系列(十一)生成器和迭代器

Posted by YaPi on December 22, 2018

迭代器和生成器

迭代器和迭代协议

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

使用’[ ]’访问元素默认是实现 __getitem__方法 使对象变成可迭代对象默认是实现 __iter__方法

在__iter__方法找不到的时候,会去找__getitem__方法

from collections.abc import Iterable, Iterator

# 自定义Iterator需要实现两个方法
# Iterable 中的__iter__方法
# Iterator 中的__next__方法


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

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

    # for 循环 和 c[1]下标取值方式需要实现此方法
    # def __getitem__(self, item):
    #     return self.e_list[item]


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

    def __next__(self):
        try:
            w = self.e_list[self.index]
        except IndexError:
            raise StopIteration
        self.index += 1
        return w


if __name__ == '__main__':
    c = Company(['1', '2', '3'])
    # 转换成为迭代器
    # 迭代器只能被遍历一次
    # 无论是用next()方法,或for循环都算
    my_iterator = iter(c)

    # for i in my_iterator:
    #     print(i)
    #
    # print('pass')
    while True:
        try:
            print(next(my_iterator))
        except StopIteration:
            break

生成器

简单实例

# 生成器函数,函数里之遥有yield关键字
def fib(index):
    if index <= 2:
        return 1
    else:
        return fib(index - 1) + fib(index - 2)


# 显示整个过程
def fib2(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


# 使用yield改造
def fib3(index):
    n, a, b = 0, 0, 1
    while n < index:
        yield b
        a, b = b, a + b
        n += 1


if __name__ == '__main__':
    # 直接打印
    print(fib(10))
    # 显示过程
    for i in fib2(10):
        print(i, end=', ')
        print()
    # 直接显示过程中,若index较大,就会相当消耗内存
    for i in fib3(10):
        print(i, end=', ')
原理

生成器函数的栈帧对象于普通函数栈帧对象不同,它多了一层封装。

每次调用时,会保存调用的是变量及执行位置,调用过后生成新的栈帧对象。所以 每次调用都能够知道下一次是什么值

import inspect
frame = None


def foo():
    bar()


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


# python.exe会用一个叫做PyEval_EvalFramEx(c函数)去执行foo函数,首先会创建一个栈帧

"""
当foo调用自函数bar,又会创建一个栈帧
所有的栈帧都是分配在堆内存上,这就决定了栈帧可以
独立于调用者存在
"""

# 查看函数字节码
# import dis
# print(dis.dis(foo))

foo()
# 所在函数名称
print(frame.f_code.co_name)
caller_frame = frame.f_back
# 调用当前函数的函数名称
print(caller_frame.f_code.co_name)


def gen_func():
    yield 1
    name = "ergou"
    yield 2
    age = 29
    yield 2
    return "abc"


# 生成器对象
# 每次调用都会重新生成一个栈帧对象
gen = gen_func()
# 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)
next(gen)
print(gen.gi_frame.f_lasti)
print(gen.gi_frame.f_locals)

应用场景

python 内部实现的str、list、dict是用c语言写的,我们要自定义类实现这些类的功能时 不能直接继承,因为c语言实现内部有很多优化,并且有一些方法不能够自定义。为了解决这种 情况,python内部用python实现了相关的类。可继承这些类实现相关的方法。

from collections import UserList
from collections import UserDict
from collections import UserString
# class MutableSequence(Sequence) 中实现
def __iter__(self):
    i = 0
    try:
        while True:
            v = self[i]
            yield v
            i += 1
    except IndexError:
        return
读取大文件
def my_read_lines(f, newline):
    buf = ""
    while True:
        while newline in buf:
            # 找到包含换行分隔符的位置
            pos = buf.index(newline)
            # 将分隔符之前的数据截取保存
            yield buf[:pos]
            # 设置buf为截取之后的数据
            buf = buf[pos + len(newline):]
        # 读取指定大小数据
        chunk = f.read(4)
        if not chunk:
            # 文件结尾
            yield buf
            break
        # 将读取到的数据与旧数据进行拼接
        buf += chunk


with open("input.txt") as f:
    # 指定分隔符
    for line in my_read_lines(f, "{|}"):
        print(line)