6.1. 迭代器

迭代器在Python语言中的应用较为广泛,迭代的意思类似于循环,每一次重复的过程被称为一次迭代的过程,而每一次迭代的结果会被用来作为下一次迭代的初始值。

提供迭代方法的容器称为迭代器,当我们循环序列(如列表、元组、字符串、集合和字典)的时候,实际上是由迭代器完成的。

6.1.1. 1.认识迭代器

假设现有列表[1,2,3,4],若想把列表的每个元素依次输出,则可以使用for语句执行循环输出,示例代码如下:

list1 = [1, 2, 3, 4]
for i in list1:
    print(i)

在Python中,一切皆为对象,列表list1是一个对象,并且它能使用for语句循环输出每个元素,说明它是一个可迭代对象。

可迭代对象并不是指某种具体的数据类型,可以理解为它是可以使用for循环输出的对象,比如列表list是可迭代对象,字典dict是可迭代对象,集合set也是可迭代对象,等等。

判断一个对象是否为可迭代对象

  • 主要看该对象在定义过程中是否定义了方法__iter__(),如果该对象定义了方法__iter__(),它就是一个可迭代对象。

迭代器有两个核心方法:iter()和next()。

  • iter()方法用于创建迭代器对象;

  • next()用于遍历对象的元素。在遍历字符串、列表或元组对象时经常会用到迭代器,例如:

list1 = [1, 2, 3, 4]
print("list1的对象类型为:", type(list1))

l = iter(list1)
print("iter(list1)的对象类型为:", type(l))
print(next(l))
print(next(l))
print(next(l))
print(next(l))
"""
list1的对象类型为: <class 'list'>
iter(list1)的对象类型为: <class 'list_iterator'>
1
2
3
4
"""

除了使用next()输出之外还可以使用for和while输出每个元素

list1 = [1, 2, 3, 4]
print("list1的对象类型为:", type(list1))
l = iter(list1)
print("iter(list1)的对象类型为:", type(l))
for i in l:
    print(i)

l = iter(list1)
while 1:
    try:
        print(next(l))
    except:
        break

6.1.2. 2.迭代器与可迭代对象的区别

  • 可迭代对象不一定是迭代器,但迭代器一定是可迭代对象;

  • 对可迭代对象使用iter()会返回迭代器,迭代器则会返回其自身;

  • 每个迭代器的被迭代过程是一次性的,可迭代对象则不一定;

  • 可迭代对象只需要实现__iter__方法,而迭代器要额外实现__next__方法。

6.1.3. 3.自定义迭代器

只要在类中定义__iter__()和__next__(),那么该类可以视为迭代器类。有了自定义的迭代器类,还要定义一个可迭代的类,在可迭代的类的__iter__()方法里面使用自定义的迭代器类实现迭代过程,详细代码如下:

#!/usr/bin/env python
# -*- coding:utf8 -*-
# auther: 18793
# Date:2021/11/3 11:34
# filename: list_for_iter.py


class MyListIterator:
    """定义迭代器类,它是MyList可迭代对象的迭代器类"""

    def __init__(self, data):
        self.data = data
        self.now = 0

    def __iter__(self):
        """返回该对象的迭代器类的实例,因为它自己就是迭代器,所以返回self"""
        return self

    def __next__(self):
        """迭代器类必须定义的方法"""
        while self.now < self.data:
            self.now += 1
            # 返回当前迭代值
            return self.now - 1
        raise StopIteration  # 超出范围抛出异常


class MyList:
    def __init__(self, num):
        self.num = num

    def __iter__(self):
        return MyListIterator(self.num)


my_list = MyList(5)
print(type(my_list))
my_list_iter = iter(my_list)
print(type(my_list_iter))

for i in my_list_iter:
    print(i)

"""
<class '__main__.MyList'>
<class '__main__.MyListIterator'>
0
1
2
3
4
"""

使用dir()函数查看属性 __getattribute__或者 __next__,说明是个可迭代的对象

__iter__() 方法返回对象本身,是for遇见使用迭代器的要求

__next__() 方法返回容器中下一个元素或数据,当容器中数据用尽时,引发StopIteration异常

3.1 代码示例1

#!/usr/bin/env python
# -*- coding:utf8 -*-
# auther; 18793
# Date:2019/6/15 14:03
# filename: 实现一个迭代器.py
"""
for循环遍历列表、元祖和字典,属于一个迭代器
"""
'''
如果开发者要实现迭代器,只需要实现如下两个方法即可

__iter__(self):该方法返回一个迭代器(iterator),迭代器必须包含一个__next__()方法,该方法返回迭代器的下一个元素

__reversed__(self):该方法主要为内建的reversed()反转函数提供支持,程序调用reversed()函数时,其实就是在使用
__reversed__此方法
'''


# 实现一个斐波拉契数列 f(n+2)=f(n+1)+f(n)

class Fibs:
    def __init__(self, len):
        self.first = 0
        self.sec = 1
        self.__len = len

    # 定义迭代器所需的__next__方法
    def __next__(self):
        # 如果__len__属性为0,结束迭代
        if self.__len == 0:
            raise StopIteration
        # 完成数列计算
        self.first, self.sec = self.sec, self.first + self.sec
        self.__len -= 1
        return self.first

    # 定义__iter__方法,该方法返回迭代器
    def __iter__(self):
        return self


# 创建Fibs对象
fibs = Fibs(10)
# print(next(fibs))
# print(fibs.__next__())
# print(fibs.__next__())

for i in fibs:
    print(i, end=" ")

输出信息

1 1 2 3 5 8 13 21 34 55
# 将列表、元祖转换为迭代器
my_iter = iter(["千千厥歌", "hu", 'jianli', "python", "java"])
#依次获取迭代器的下一个元素
# print(my_iter.__next__())
# print(my_iter.__next__())
# print(my_iter.__next__())
# print(my_iter.__next__())

for i in my_iter:
    print(i)

提示:

迭代器每次迭代只会取出当前迭代的数据存储在内存进行读取,上一次迭代的数据会在内存中销毁,并且其他数据不会加载到内存中。

当数据量太大的时候,这样就能节省内存的开销,提高程序的运行速度,它在大文件的读取、大数据处理和网站大量数据爬取的情况下具有明显的优势。

3.2 代码示例2

class Fibs:
    def __init__(self, n=10):
        self.a = 0
        self.b = 1
        self.n = n      #定义初始化参数n

    def __iter__(self):
        return self

    def __next__(self):
        self.a,self.b = self.b, self.a + self.b     #a=b b=a+b
        if self.a > self.n:         #退出条件
            raise StopIteration
        return self.a,self.b

hu = Fibs(100)
for i in hu:
    print(i)

3.3 代码示例3

#自定义迭代器
class MyIterator:
    def __init__(self,x=2,xmax=100):
        '''
        定义构造方法,初始化属性
        '''
        self.__mul,self.__x = x,x
        self.__xmax = xmax

    def __iter__(self):
        """
        :return:定义迭代器协议方法,返回类本身
        """
        return self

    def __next__(self):
        if self.__x and self.__x != 1:
            self.__mul *= self.__x
            if self.__mul <= self.__xmax:
                return self.__mul
            else:
                raise StopIteration
        else:
            raise StopIteration

if __name__ == '__main__':
    myiter = MyIterator()
    for i in myiter:
        print("迭代器的数据元素为{}".format(i))

3.4 代码示例4

#!/usr/bin/env python
#-*- coding:utf8 -*-】
class Counter:
    '''
    定义用于计数的类
    '''
    def __init__(self,x=0):
        #定义构造函数,初始化实例属性x
        self.x = x

counter = Counter()  #实例化类

def used_iter():
    #修改计数类中实例属性的值
    counter.x +=2
    return counter.x

for i in iter(used_iter,8):         #8为哨兵,迭代到8立刻停止
    print("本次遍历的数值:{}".format(i))

6.1.4. 4.使用迭代器读取文件

4.1 读取小文件

def count_digits(fname):
    """计算文件里包含多少个数字字符"""
    count = 0
    with open(fname) as file:
        for line in file:
            for s in line:
                if s.isdigit():
                    count += 1
    return count

4.2 读取大文件

# 方式1
def count_digits_v2(fname):
    """计算文件里包含多少个数字字符,每次读取 8kb"""
    count = 0
    block_size = 1024 * 8
    with open(fname) as file:
        while True:
            chunk = file.read(block_size)
            # 当文件没有更多内容时,read 调用将会返回空字符串 ''
            if not chunk:
                break
            for s in chunk:
                if s.isdigit():
                    count += 1
    return count


# 方式2
from functools import partial
def count_digits_v3(fname):
    count = 0
    block_size = 1024 * 8
    with open(fname) as fp:
        # 使用 functools.partial 构造一个新的无需参数的函数
        _read = partial(fp.read, block_size)

        # 利用 iter() 构造一个不断调用 _read 的迭代器
        for chunk in iter(_read, ''):
            for s in chunk:
                if s.isdigit():
                    count += 1
    return count

4.3 读取数字内容的生成器函数

from functools import partial
def read_file_digits(fp, block_size=1024 * 8):
    """生成器函数:分块读取文件内容,返回其中的数字字符"""
    _read = partial(fp.read, block_size)
    for chunk in iter(_read, ''):
        for s in chunk:
            if s.isdigit():
                yield s


def count_digits_v4(fname):
    """计算文件里包含多少个数字字符,每次读取 8kb"""
    count = 0
    with open(fname) as file:
        for num in read_file_digits(file):
            count += 1
    return count


def count_even_groups(fname):
    """分别统计文件里每个偶数字符出现的个数"""
    counter = defaultdict(int)
    with open(fname) as file:
        for num in read_file_digits(file):
            if int(num) % 2 == 0:
                counter[int(num)] += 1
    return counter