当前位置:   article > 正文

Python:可迭代对象与迭代器

Python:可迭代对象与迭代器

相关阅读

Pythonicon-default.png?t=N7T8https://blog.csdn.net/weixin_45791458/category_12403403.html?spm=1001.2014.3001.5482


        根据Python官方文档,可迭代对象(iterable)是“一种能够逐个返回其成员项的对象”。具体来说,这种对象要么定义了一个返回迭代器(iterator)的魔术方法__iter__(),要么定义了魔术方法__getitem__(),并使用从0开始的整数索引。

        Python中的一些内置类型就是可迭代对象,比如列表(list)、元组(tuple)、range对象、字符串(str)、字典(dict)、集合(set),下面的例子检测了这些类型是否拥有__iter__()方法或__getitem__()方法:

  1. List = [1, 2, 3]
  2. Tuple = (1, 2, 3)
  3. Range = range(1, 4)
  4. Str = 'hello, world!'
  5. Dict = {'name': 'Alice', 'age': 13, 'gender': 'Female'}
  6. Set = {1, 2, 3}
  7. # 检测是否包含 __getitem__ 方法
  8. print("List has __getitem__:", hasattr(List, '__getitem__'))
  9. print("Tuple has __getitem__:", hasattr(Tuple, '__getitem__'))
  10. print("Range has __getitem__:", hasattr(Range, '__getitem__'))
  11. print("Str has __getitem__:", hasattr(Str, '__getitem__'))
  12. print("Dict has __getitem__:", hasattr(Dict, '__getitem__'))
  13. print("Set has __getitem__:", hasattr(Set, '__getitem__'))
  14. # 检测是否包含 __iter__ 方法
  15. print("List has __iter__:", hasattr(List, '__iter__'))
  16. print("Tuple has __iter__:", hasattr(Tuple, '__iter__'))
  17. print("Range has __iter__:", hasattr(Range, '__iter__'))
  18. print("Str has __iter__:", hasattr(Str, '__iter__'))
  19. print("Dict has __iter__:", hasattr(Dict, '__iter__'))
  20. print("Set has __iter__:", hasattr(Set, '__iter__'))

        结果显示,它们大多都实现了这两个魔术方法,除了集合没有实现__getitem__()方法,如下所示。

  1. 输出:
  2. List has __getitem__: True
  3. Tuple has __getitem__: True
  4. Range has __getitem__: True
  5. Str has __getitem__: True
  6. Dict has __getitem__: True
  7. Set has __getitem__: False
  8. List has __iter__: True
  9. Tuple has __iter__: True
  10. Range has __iter__: True
  11. Str has __iter__: True
  12. Dict has __iter__: True
  13. Set has __iter__: True

        collections.abc.Iterable是一个基类,可以用它检测一个对象是否拥有__iter__()方法,如下所示。

  1. from collections.abc import Iterable
  2. # 检测是否含有__iter__()
  3. print("List is Iterable:", isinstance(List, Iterable))
  4. print("Tuple is Iterable:", isinstance(Tuple, Iterable))
  5. print("Range is Iterable:", isinstance(Range, Iterable))
  6. print("Str is Iterable:", isinstance(Str, Iterable))
  7. print("Dict is Iterable:", isinstance(Dict, Iterable))
  8. print("Set is Iterable:", isinstance(Set, Iterable))
  9. # 输出:
  10. List is Iterable: True
  11. Tuple is Iterable: True
  12. Range is Iterable: True
  13. Str is Iterable: True
  14. Dict is Iterable: True
  15. Set is Iterable: True

        但这不是一个可靠的检测方式,因为如果一个可迭代对象只拥有__getitem__()方法,则这种方式就会失效。一个可靠的方式是使用Python内置函数iter()。

        可迭代对象(iterable)可以使用内置函数iter()转化为迭代器(iterator),iter()函数首先会尝试调用可迭代对象的__iter__()方法返回一个迭代器(是否是迭代器是根据返回对象是否拥有__next__方法检测的,如返回的不是迭代器则会产生TypeError异常: iter() returned non-iterator of type ***),如果不存在__iter__()方法,则会寻找是否有索引从0开始的__getitem__()方法并尝试创建一个迭代器,如果仍然不存在,则会引发TypeError异常。

        下面展示了一些内置类型使用iter()函数创建的迭代器,直接使用__iter__()方法也可返回迭代器,但它不检测返回的是否是迭代器,因此最好使用iter()函数。

  1. # 检测迭代器的类型
  2. ListIter = iter(List) # 直接使用List.__iter__()方法也可返回迭代器
  3. print(type(ListIter))
  4. TupleIter = iter(Tuple) # 直接使用Tuple.__iter__()方法也可返回迭代器
  5. print(type(TupleIter))
  6. RangeIter = iter(Range) # 直接使用Range.__iter__()方法也可返回迭代器
  7. print(type(RangeIter))
  8. StrIter = iter(Str) # 直接使用Str.__iter__()方法也可返回迭代器
  9. print(type(StrIter))
  10. DictIter = iter(Dict) # 直接使用Dict.__iter__()方法也可返回迭代器
  11. print(type(DictIter))
  12. SetIter = iter(Set) # 直接使用Set.__iter__()方法也可返回迭代器
  13. print(type(SetIter))
  14. # 输出:
  15. <class 'list_iterator'>
  16. <class 'tuple_iterator'>
  17. <class 'range_iterator'>
  18. <class 'str_iterator'>
  19. <class 'dict_keyiterator'>
  20. <class 'set_iterator'>

        迭代器拥有__iter__()方法(用于返回自己,因此迭代器本身也是可迭代对象),和__next__()方法(用于迭代)。

        for循环其实就是迭代的过程,首先会调用iter()函数为可迭代对象创建一个未命名的迭代器,再循环的过程中调用__next__()方法进行迭代,直到迭代器耗尽,触发StopIteration异常并停止循环,并销毁未命名的迭代器。

        使用内置函数next()也可以进行迭代,它们之间的差别不大,如下所示。

  1. # 使用列表迭代器
  2. print(next(ListIter))
  3. print(ListIter.__next__())
  4. print(next(ListIter))
  5. print(ListIter.__next__())
  6. # 输出:
  7. 1
  8. 2
  9. 3
  10. StopIteration Traceback (most recent call last)

        注意从 Python 3.7 开始,字典的遍历顺序一定和输入顺序一样。所以字典迭代器的迭代顺序是确定的,对字典使用iter()函数,默认返回的是针对键的迭代器,可以通过使用values()方法或items()方法明确指定想要遍历的元素,并返回相应的迭代器。

  1. # 使用字典迭代器
  2. print(next(DictIter))
  3. print(next(DictIter))
  4. print(next(DictIter))
  5. ValueIter = iter(Dict.values())
  6. print(next(ValueIter))
  7. print(next(ValueIter))
  8. print(next(ValueIter))
  9. 输出:
  10. name
  11. age
  12. gender
  13. Alice
  14. 13
  15. Female

        集合迭代器无法保证遍历顺序,因此不应该对此有任何期待。

        下面的例子定义了一个,只含有__getitem__()方法的可迭代对象,并直接使用循环迭代,还创建了一个迭代器并用next()函数进行迭代。

  1. class MyIterable:
  2. def __init__(self, data):
  3. self.data = data
  4. def __getitem__(self, index):
  5. return self.data[index]
  6. # 示例用法
  7. my_iterable = MyIterable([1, 2, 3, 4, 5])
  8. for item in my_iterable:
  9. print(item)
  10. my_iterator = iter(my_iterable)
  11. print(next(my_iterator))
  12. print(next(my_iterator))
  13. print(next(my_iterator))
  14. 输出:
  15. 1
  16. 2
  17. 3
  18. 4
  19. 5
  20. 1
  21. 2
  22. 3

        迭代器对象只能按照顺序前行,无法后退,这意味着对于一个迭代器对象,只能遍历一轮,如果想要多轮迭代,可以重新创建迭代器。

        写这篇文章的起因,是接触了神经网络框架Pytorch中的torchvision.datasets包,里面定义了很多数据集,它们是拥有__getitem__()方法的可迭代对象,因此可以使用索引访问,而dataloader是拥有__iter__()方法的可迭代对象,所以不可以使用索引访问。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/611296
推荐阅读
相关标签
  

闽ICP备14008679号