迭代器

Posted Python3技术笔记

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了迭代器相关的知识,希望对你有一定的参考价值。

迭代器

引入

如果开发中有以下需求,如何解决?

class StuSystem(object):
    """
    学生管理系统
    """
    def __init__(self):
        self.stus = []

    def add(self):
        """
        添加一个新的学生
        :return:
        """
        name = input("请输入新学生的姓名:")
        tel = input("请输入新学生的手机号:")
        address = input("请输入新学生的住址:")

        new_stu = dict()
        new_stu["name"] = name
        new_stu["tel"] = tel
        new_stu["address"] = address

        self.stus.append(new_stu)


# 创建管理系统对象
stu_sys = StuSystem()

# 添加3个学生信息到系统中
stu_sys.add()
stu_sys.add()
stu_sys.add()

# 问题1:怎样才能实现用for循环遍历系统中所有的学生信息呢?下面的方式能实现吗?
for temp in stu_sys:
    print(temp)

# 问题2:如果需要一个列表,这个列表 样子例如 [\'name\': \'张三\', \'id\': 10086, \'name\': \'李四\': \'id\': 10087]
# stu_list = [ ...列表推导式...]
# 这个列表推导式该怎样写才能实现呢?

在实际开发工作中,经常需要快速的将对象转化问其他的不同的数据类型,此时如果能快速的遍历出自定义的对象,这样大大减少代码的冗余,而且可读性会更优美

问题是,怎样实现呢?

今天我们要学习的知识只有1个,那就是迭代器

什么是迭代

迭代是访问序列类型元素的一种方式

nums = [11, 22, 33]

# 可以通过for循环将nums列表中的每个数据依次获取
for num in nums:
    print(num)


name = "teacher"

for temp in name:
    print(temp)

我们已经知道可以对listtuplestr等类型的数据使用for...in...的循环语法从其中依次拿到数据进行使用,我们把这样的过程称为遍历,也叫迭代

可迭代对象

是否所有的数据类型都可以放到for...in...的语句中,然后让for...in...每次从中取出一条数据供我们使用呢?

weight = 160

for item in weight:
    print(item)  # 报错

通过运行可以发现,并不是所有的类型都可以通过for...in...的方式进行遍历

我们可以通俗的认为:只要是可以通过for...in…的形式进行遍历的,那么这个数据类型就是可以迭代的

例如,下面的是可以迭代的数据类型

  • 列表
  • 元组
  • 字典
  • 字符串

而下面的则不是可以迭代的数据类型

  • 整型
  • 浮点型

那是否可以通过某种方式能够测量出一个数据类型到底是否是可以迭代呢?

In [50]: from collections.abc import Iterable

In [51]: isinstance([], Iterable)
Out[51]: True

In [52]: isinstance(, Iterable)
Out[52]: True

In [53]: isinstance(\'abc\', Iterable)
Out[53]: True

In [54]: isinstance(mylist, Iterable)
Out[54]: False

In [55]: isinstance(100, Iterable)
Out[55]: False

只要是通过isinstance来判断出是Iterable类的实例,即isinstance的结果是True那么就表示,这个数据类型是可以迭代的数据类型

迭代器

迭代器是一个可以记住遍历的位置的对象。迭代器对象从第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。

可迭代对象的本质

分析 可迭代对象 进行迭代的过程,发现每迭代一次(即在for...in...中每循环一次)都会返回对象中的下一条数据,一直向后读取数据直到迭代了所有数据后结束。

那么,在这个过程中就应该有一个“人”去记录每次访问到了第几条数据,以便每次迭代都可以返回下一条数据。我们把这个能帮助我们进行数据迭代的“人”称为迭代器(Iterator)

可迭代对象的本质就是可以向我们提供一个这样的中间“人”,即迭代器帮助我们对其进行迭代遍历使用。

listtuple等都是可迭代对象,我们可以通过iter()函数获取这些可迭代对象的迭代器。然后我们可以对获取到的迭代器不断使用next()函数来获取下一条数据。

获取可迭代对象的迭代器

from collections.abc import Iterator

nums = [11, 22, 33, 44]

print(type(nums))

nums_iter = iter(nums)

print(type(nums_iter))

print("nums", isinstance(nums, Iterator))
print("nums_iter", isinstance(nums_iter, Iterator))

获取迭代器的数据

上面提到,通过iter()能够得到一个可迭代对象的 迭代器,可以通过next()函数多次提取迭代器中的数据,下面我们就测试下

测试代码如下:

from collections.abc import Iterator


nums = [11, 22, 33, 44]
nums_iter = iter(nums)

print("nums", isinstance(nums, Iterator))
print("nums_iter", isinstance(nums_iter, Iterator))


num1 = next(nums_iter)
print(num1)

num2 = next(nums_iter)
print(num2)

num3 = next(nums_iter)
print(num3)

num4 = next(nums_iter)
print(num4)

StopIteration 异常

如果将上面的代码,多写一次的next()会怎样呢?看如下测试代码:

from collections.abc import Iterator


nums = [11, 22, 33, 44]
nums_iter = iter(nums)

print("nums", isinstance(nums, Iterator))
print("nums_iter", isinstance(nums_iter, Iterator))


num1 = next(nums_iter)
print(num1)

num2 = next(nums_iter)
print(num2)

num3 = next(nums_iter)
print(num3)

num4 = next(nums_iter)
print(num4)

num5 = next(nums_iter)  # 这里会产生异常
print(num5)

可以看到23行,即第5次调用next()时,产生了异常。why??????

因为列表nums中只有4个数据,也就是说可以调用4次next是完全合理的,但是如果,调用的次数多了肯定是不合理,都没有5个数据,怎么可以能取5次呢!显然是不对的

此时估计想明白了,为什么会产生异常,其实就是一种告知迭代结束的标志而已

添加try...except...即可解决刚刚遇到的问题

try:
    num5 = next(nums_iter)
    print(num5)
except StopIteration as e:
    print(f\'迭代结束: e\')
自定义迭代器

大家是否还记得 在刚开学习今天的知识时,我们引入了一个学生管理系统的问题,该怎样实现呢

我们下面来谈谈

  • __iter__方法
  • __next__方法

__iter__方法

上面提到iter()方法必须是对”可迭代“对象 才能 提取到 ”迭代器“对象,但是怎样保证自定义的对象是”可迭代“对象呢?

答:只要在类中定义__iter__方法,那么这个类创建出来的对象一定是可迭代对象

通俗的说:一个具备了__iter__方法的对象就是一个可以迭代的对象

测试代码一:无__iter__方法

from collections.abc import Iterable


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

    def add(self, item):
        self.container.append(item)

        
mylist = MyList()
mylist.add(11)
mylist.add(22)
mylist.add(33)

print("mylist是否是可以迭代对象", isinstance(mylist, Iterable))

for temp in mylist:
    print(temp)

运行结果:

mylist是否是可以迭代对象 False
Traceback (most recent call last):
  File "/home/ubuntu/Desktop/stu_code/测试代码.py", line 19, in <module>
    for temp in mylist:
TypeError: \'MyList\' object is not iterable

测试代码二:有__iter__方法

from collections.abc import Iterable


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

    def add(self, item):
        self.container.append(item)

    def __iter__(self):
        pass

mylist = MyList()
mylist.add(11)
mylist.add(22)
mylist.add(33)

print("mylist是否是可以迭代对象", isinstance(mylist, Iterable))

for temp in mylist:
    print(temp)

运行结果:

mylist是否是可以迭代对象 True
Traceback (most recent call last):
  File "/home/ubuntu/Desktop/stu_code/测试代码.py", line 21, in <module>
    for temp in mylist:
TypeError: iter() returned non-iterator of type \'NoneType\'

能够看出,一个类,只要有__iter__方法,那么这个类创建出来的对象就是可以迭代对象

其实,当我们调用iter()函数提取一个可迭代对象的 迭代器时,实际上会自动调用这个对象的__iter__方法,并且这个方法返回迭代器

__next__方法

通过上面的分析,我们已经知道,迭代器是用来帮助我们记录每次迭代访问到的位置,当我们对迭代器使用next()函数的时候,迭代器会向我们返回它所记录位置的下一个位置的数据。

实际上,在使用next()函数的时候,调用的就是迭代器对象的__next__方法(Python3中是对象的__next__方法,Python2中是对象的next()方法)。

所以,我们要想构造一个迭代器,就要实现它的__next__方法。

但这还不够,python要求迭代器本身也是可迭代的,所以我们还要为迭代器实现__iter__方法,而__iter__方法要返回一个迭代器,迭代器自身正是一个迭代器,所以迭代器的__iter__方法返回自身即可。

一个实现了__iter__方法和__next__方法的对象,就是迭代器

如何判断一个对象是否是迭代器

可以使用 isinstance() 判断一个对象是否是 Iterator 对象:

In [56]: from collections.abc import Iterator

In [57]: isinstance([], Iterator)
Out[57]: False

In [58]: isinstance(iter([]), Iterator)
Out[58]: True

In [59]: isinstance(iter("abc"), Iterator)
Out[59]: True

自定义迭代器:

from collections.abc import Iterable
from collections.abc import Iterator


class MyList(object):
    """自定义的一个可迭代对象"""
    def __init__(self):
        self.items = []

    def add(self, val):
        self.items.append(val)

    def __iter__(self):
        return MyIterator()


class MyIterator(object):
    """自定义的供上面可迭代对象使用的一个迭代器"""
    def __init__(self):
        pass

    def __next__(self):
        pass

    def __iter__(self):
        pass


mylist = MyList()
mylist_iter = iter(mylist)

print("mylist是否是可以迭代对象", isinstance(mylist, Iterable))
print("mylist是否是迭代器", isinstance(mylist, Iterator))

print("mylist_iter是否是可以迭代对象", isinstance(mylist_iter, Iterable))
print("mylist_iter是否是迭代器", isinstance(mylist_iter, Iterator))

运行结果:

mylist是否是可以迭代对象 True
mylist是否是迭代器 False
mylist_iter是否是可以迭代对象 True
mylist_iter是否是迭代器 True

自定义迭代器案例

class MyList(object):
    """自定义的一个可迭代对象"""
    def __init__(self):
        self.items = []

    def add(self, val):
        self.items.append(val)

    def __iter__(self):
        myiterator = MyIterator(self)
        return myiterator


class MyIterator(object):
    """自定义的供上面可迭代对象使用的一个迭代器"""
    def __init__(self, mylist):
        self.mylist = mylist
        # current用来记录当前访问到的位置
        self.current = 0

    def __next__(self):
        if self.current < len(self.mylist.items):
            item = self.mylist.items[self.current]
            self.current += 1
            return item
        else:
            raise StopIteration

    def __iter__(self):
        return self


if __name__ == \'__main__\':
    mylist = MyList()
    mylist.add(1)
    mylist.add(2)
    mylist.add(3)
    mylist.add(4)
    mylist.add(5)
    for num in mylist:
        print(num)

运行结果:

1
2
3
4
5

可迭代对象通过__iter__方法向我们返回一个迭代器,我们在迭代一个可迭代对象的时候,实际上就是先获取该对象提供的一个迭代器,然后通过这个迭代器来依次获取对象中的每一个数据。

for...in...循环的本质
  1. 先调用iter()函数,它会自动调用可迭代对象中的__iter__方法,此方法返回这个可迭代对象的 迭代器对象
  2. 对获取到的迭代器不断调用next()函数,它会自动调用迭代器中的__next__方法来获取下一个值
  3. 当遇到StopIteration异常后循环结束
并不是只有for循环能接收可迭代对象

除了for循环能接收可迭代对象,listtuple等也能接收。

测试代码如下:

class MyList(object):
    """自定义的一个可迭代对象"""
    def __init__(self):
        self.items = []

    def add(self, val):
        self.items.append(val)

    def __iter__(self):
        myiterator = MyIterator(self)
        return myiterator


class MyIterator(object):
    """自定义的供上面可迭代对象使用的一个迭代器"""
    def __init__(self, mylist):
        self.mylist = mylist
        # current用来记录当前访问到的位置
        self.current = 0

    def __next__(self):
        if self.current < len(self.mylist.items):
            item = self.mylist.items[self.current]
            self.current += 1
            return item
        else:
            raise StopIteration

    def __iter__(self):
        return self


if __name__ == \'__main__\':
    mylist = MyList()
    mylist.add(1)
    mylist.add(2)
    mylist.add(3)
    mylist.add(4)
    mylist.add(5)

    nums = list(mylist)
    print(nums)

运行结果:

[1, 2, 3, 4, 5]
简单总结
  • 凡是可作用于for 循环的对象都是 Iterable 类型
  • 凡是可作用于 next() 函数的对象都是 Iterator 类型
  • 序列数据类型如 listdictstr等是 Iterable 但不是Iterator,不过可以通过 iter() 函数获得一个 Iterator 对象
随堂作业

既然已经学习过了迭代器,那么今天刚开始的知识点也就自然有了答案

  • 实现用for循环遍历学生系统中的所有学生信息
class StuSystem(object):
    """
    学生管理系统
    """
    def __init__(self):
        self.stus = []
        self.current_num = 0

    def add(self):
        """
        添加一个新的学生
        :return:
        """
        name = input("请输入新学生的姓名:")
        tel = input("请输入新学生的手机号:")
        address = input("请输入新学生的住址:")
        new_stu = dict()
        new_stu["name"] = name
        new_stu["tel"] = tel
        new_stu["address"] = address
        self.stus.append(new_stu)

    def __iter__(self):
        return self

    def __next__(self):
        if self.current_num < len(self.stus):
            ret = self.stus[self.current_num]
            self.current_num += 1
            return ret
        else:
            self.current_num = 0
            raise StopIteration


# 创建管理系统对象
stu_sys = StuSystem()

# 添加3个学生信息到系统中
stu_sys.add()
stu_sys.add()
stu_sys.add()

# 问题1:怎样才能实现用for循环遍历系统中所有的学生信息呢?下面的方式能实现吗?
for temp in stu_sys:
    print(temp)

运行结果:

(base) ubuntu@VM-16-5-ubuntu:~/Desktop/stu_code$ /home/ubuntu/miniconda3/bin/python /home/ubuntu/Desktop/stu_code/测试代码.py
请输入新学生的姓名:顾安
请输入新学生的手机号:13711111111
请输入新学生的住址:长沙
请输入新学生的姓名:安娜
请输入新学生的手机号:13711111112
请输入新学生的住址:长沙
请输入新学生的姓名:双双
请输入新学生的手机号:13711111113
请输入新学生的住址:长沙
\'name\': \'顾安\', \'tel\': \'13711111111\', \'address\': \'长沙\'
\'name\': \'安娜\', \'tel\': \'13711111112\', \'address\': \'长沙\'
\'name\': \'双双\', \'tel\': \'13711111113\', \'address\': \'长沙\'
  • 对输入的数据进行格式转换
class StuSystem(object):
    """
    学生管理系统
    """
    def __init__(self):
        self.stus = []
        self.current_num = 0

    def add(self):
        """
        添加一个新的学生
        :return:
        """
        name = input("请输入新学生的姓名:")
        tel = input("请输入新学生的手机号:")
        address = input("请输入新学生的住址:")
        new_stu = dict()
        new_stu["name"] = name
        new_stu["tel"] = tel
        new_stu["address"] = address
        self.stus.append(new_stu)

    def __iter__(self):
        return self

    def __next__(self):
        if self.current_num < len(self.stus):
            ret = self.stus[self.current_num]
            self.current_num += 1
            return ret
        else:
            self.current_num = 0
            raise StopIteration



stu_sys = StuSystem()

stu_sys.add()
stu_sys.add()
stu_sys.add()

stu_list = [x for x in stu_sys]
print(stu_list)

运行结果:

请输入新学生的姓名:顾安
请输入新学生的手机号:13711111111
请输入新学生的住址:长沙
请输入新学生的姓名:安娜
请输入新学生的手机号:13711111112
请输入新学生的住址:南京
请输入新学生的姓名:双双
请输入新学生的手机号:13711111113
请输入新学生的住址:上海
[\'name\': \'顾安\', \'tel\': \'13711111111\', \'address\': \'长沙\', \'name\': \'安娜\', \'tel\': \'13711111112\', \'address\': \'南京\', \'name\': \'双双\', \'tel\': \'13711111113\', \'address\': \'上海\']

以上是关于迭代器的主要内容,如果未能解决你的问题,请参考以下文章

五 迭代器生成器

STL迭代器相关的输出迭代器

课时10:迭代器迭代器失效分析

C++迭代器 iterator

C++迭代器 iterator

25Python之迭代器