python迭代器第二次迭代为空的探究

今天在看《机器学习实战》第11章,使用Apriori算法来发现频繁项集的代码时。P205的scanD函数在执行内层遍历时,第二次遍历访问的迭代对象为空。按理说按照书上代码原封不动誊写下来应该不会有错,本着技术要严肃的态度一探究竟,在查阅了相关资料后,整理为本篇博客。

问题重现

# 问题抽象代码
def func(list, map):
    for listEach in list:
        print(listEach)
        for mapEach in map:
            print(mapEach)

list = [
    [1,2,3],
    [4,5,6],
    [7,8,9]
]

map = map(str,['a','b','c'])

func(list, map)

 上述代码输出为:

[1, 2, 3]
a
b
c
[4, 5, 6]
[7, 8, 9]

可见:第二次遍历list时,内层的map为空,导致了没有进入。

在说明出现此问题的原因前,需要明确以下几个点:

  • python2的map函数返回list;python3的map函数返回迭代器(Iterator)。

因为书中的代码使用的python2,所以没有出现此错误。

  • 迭代器(Iterator)和可迭代对象(Iterable)的区别和联系。

可迭代对象(Iterable):它的特点其实就是序列的大小长度已经确定了(list,tuple,dict,string等),遵循可迭代协议。

可迭代协议:含__iter__()方法。且可迭代对象中的__iter__()方法返回的是一个对应的迭代器。(如list对应的迭代器就是list_iterator)。

迭代器(Iterator):它的特点就是不知道要执行多少次,所以可以理解不知道有多少个元素,每调用一次__next__()方法,就会往下走一步,当然是用__next__()方法只能往下走,不会回退,遵循迭代器协议。

迭代器协议:含__iter__()方法,且方法返回的Iterator对象本身;含__next__()方法,每当__next__()方法被调用,返回下一个值,直到没有值可以访问,这个时候会抛出stopinteration的异常。

通俗地讲就是类如果满足可迭代协议也就是有__iter__()的时候就可以成为可迭代对象,如果类有__iter__()和__next__()方法就可以称之为迭代器。

迭代器和可迭代对象之间是什么关系呢?

>>>from collections import Iterator,Iterable
>>>help(Iterator)

Help on class Iterator in module collections.abc:

class Iterator(Iterable)
 |  Method resolution order:
 |      Iterator
 |      Iterable
 |      builtins.object
 |  ****注解:从这里可以看出Iterable继承自object, Iterator继承自Iterable。
 |  Methods defined here:
 |  
 |  __iter__(self)
 |  
 |  __next__(self)
 |      Return the next item from the iterator. When exhausted, raise StopIteration
 |  
 |  ----------------------------------------------------------------------
 |  Class methods defined here:
 |  
 |  __subclasshook__(C) from abc.ABCMeta
 |      Abstract classes can override this to customize issubclass().
 |      
 |      This is invoked early on by abc.ABCMeta.__subclasscheck__().
 |      It should return True, False or NotImplemented.  If it returns
 |      NotImplemented, the normal algorithm is used.  Otherwise, it
 |      overrides the normal algorithm (and the outcome is cached).
 |  
 |  ----------------------------------------------------------------------
 |  Data and other attributes defined here:
 |  
 |  __abstractmethods__ = frozenset({'__next__'})

可见,迭代器继承自可迭代对象。那么Iterable如何转为Iterator?Iterator直接调用iter()方法即可,iter()方法是调用类中的__iter__()方法,而调用__iter__()方法后返回的是一个Iterator。即:

iter(iterable)-->iterator
iter(iterator)-->iterator

参见下面代码:

list = [1,2,3,4]

# 调用iter()返回迭代器
list_itator = list.__iter__()
# 迭代器有__next__()方法,可调用next迭代
list_itator.__next__()
# list为可迭代对象,没有__next__方法,调用next报错
list.__next__()

Traceback (most recent call last):
  File "F:/pytorch/main.py", line 8, in <module>
    list.__next__()
AttributeError: 'list' object has no attribute '__next__'

在python的for循环中,对Iterable有一个隐形的转换:

for x in [1,2,3,4,5]:
    pass


等价于

# 将list转化为迭代器,再调用迭代器的next
it = iter([1,2,3,4,5])
while True:
    try:
        x = next(it);
    except StopIteration:
        break

下面解释,为何问题复现中的内层遍历代码在第二次遍历时没有内容,而如果将map的函数返回改为一个list,则全部正常输出:

因为list不是迭代器,只是一个可迭代对象,不含__next__()方法,在对list的for遍历中,每次遍历都是将list转化为迭代器,遍历多少次就转化多少次。而如果使用map,则函数返回了迭代器,此时的遍历就是不断调用__next__()的过程,而这个过程是单向的,到最后一个元素时会抛出StopIteration异常终止此次遍历,迭代器用完了就没有了。

对于此类问题的解决方案,可采用copy模块的浅复制和深复制。

关于浅复制和深复制的学习,在后面的文章中探究。

 

参考:https://zhuanlan.zhihu.com/p/32162554

 

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值