python 可迭代对象(Iterable)、迭代器(Iterator)、iter()、next()、__iter__()、__next__()、__getitem__()用法

  1. 疑问

我们所熟悉的列表、字符串、元组、集合、字典等都是常见的可迭代对象。由于经常会用到,所以分享一下自己的理解,不是简单的拿网上一知半解的例子。看完之后保证再也不迷惑~

首先我们要回答这些问题:

一、什么算是可迭代对象?对象中有__iter__()方法就一定是可迭代对象吗?没有__iter__()方法的对象可以是可迭代对象吗?

二、可迭代对象一定能用for循环吗?for 循环到底在干嘛?

三、可迭代对象一定能用iter()吗?iter()到底干了啥?iter()和__iter__()有什么区别?

四、迭代器一定要实现__next__()和__iter__()方法吗,可不可以没有,或者只有__next__()

五、next()干了啥?next()和__next__()有什么区别?

六、__iter__()、__next__()、__getitem__()三个方法到底有什么联系?

七、可迭代对象和迭代器什么时候可以用索引取值,什么时候不可以?

以上问题如果全都搞懂,迭代器的用法基本算是随手拈来,接下来就一步步走进迭代器的世界

  1. 可迭代对象

2.1 什么是可迭代对象,从一个错误的例子入手

from collections.abc import Iterator, Iterable


class IterableA(object):
    def __init__(self, text):
        self.text = text

    def __iter__(self):
        print("__iter__()被调用了")
        return self.text
    

iterable_a = IterableA("iterable_a")
print(isinstance(iterable_a, Iterable))

输出:

True

没错,只要实现了__iter__()方法的对象确实是一个可迭代对象,就像上面的iterable_a这个对象,接下来对其使用iter()和for 循环试一试

itora = iter(iterable_a)
输出:
True
__iter__()被调用了
Traceback (most recent call last):

    ...

    itora = iter(iterable_a)
TypeError: iter() returned non-iterator of type 'str'

不好意思输出报错,但是__iter__()被调用了,打印了iter函数的用法,原来iter()函数要求参数是可迭代对象型(iterable),返回要求迭代器型(iterator)。虽然参数满足,但是我们__iter__()返回的是self.text 这是个字符串型(str)。所以才会报TypeError: iter() returned non-iterator of type 'str'。

help(iter)
Help on built-in function iter in module builtins:
iter(...)
    iter(iterable) -> iterator
    iter(callable, sentinel) -> iterator
    
    Get an iterator from an object.  In the first form, the argument must
    supply its own iterator, or be a sequence.
    In the second form, the callable is called until it returns the sentinel.

使用__iter__()方法呢?

itora = iterable_a.__iter__()
print(itora)
输出:
True
__iter__()被调用了
iterable_a

这样是不是就明白了iter()和__iter__()的区别了,__iter__()就是这个对象的方法,就按照正常方法调用。而iter(iterable)是会调用__iter__(),并且返回对象是要为迭代器类型,是不是可迭代对象必须要有__iter__()才能使用iter()函数,答案并非如此,实现了__getitem__()方法也可以,后面会细说。

在来看一下for 循环

for i in iterable_a:
    print(i)
输出:
Traceback (most recent call last):

    ...

    for i in iterable_a:
TypeError: iter() returned non-iterator of type 'str'
True
__iter__()被调用了

可以发现for 循环执行的时候,iterable_a 的__iter__()被调用了,报错类型和iter()一样,原因是在执行for循环其实是执行了for i in iter(iterable_a),所以for循环干了两件事:第一个调用了iter()让可迭代对象变成了一个迭代器(不管这个可迭代对象是否是迭代器,都调用一次iter()),第二个循环调用迭代器的__next__()函数。但是由于第一步就失败了,就谈不上第二步骤了。如果迭代器中没有__next__()函数呢?这里是不是觉得迭代器必须要有__next__()方法?答案是肯定的,只要是迭代器必须实现__next__()方法,后面会细说迭代器。

最后放一个正常的可迭代对象例子。记住一点,正常的可迭代对象,__iter__()一定要返回迭代器型

from collections.abc import Iterator, Iterable


class IterableA(object):
    def __init__(self, text):
        self.text = text

    def __iter__(self):
        print("__iter__()被调用了")
        return iter(self.text)


iterable_a = IterableA("iterable_a")
print(isinstance(iterable_a, Iterable))
itora = iter(iterable_a)
print(itora)
for i in iterable_a:
    print(i)
输出:
True
__iter__()被调用了
<str_iterator object at 0x000001BDA55A7700>
__iter__()被调用了
i
t
e
r
a
b
l
e
_
a

2.2 使用__getitem__()代替__iter__()构造可迭代对象

没错,如果对象中有__getitem__()而没有__iter__()也能够实现可迭代对象,这就是__iter__()方法是可迭代对象的充分不必要条件。其实官方文档给出的解释是实现了__iter__() or __getitem__()方法的对象都叫可迭代对象,有时候看官网文档要比网上乱七八糟的说的明白。下面看个例子。

from collections.abc import Iterator, Iterable


class IterableB(object):
    def __init__(self, text):
        self.text = text

    def __getitem__(self, item):
        print("IterableB.__getitem__()被调用了")
        return self.text[item]


iterable_b = IterableB("iterable_a")
print(isinstance(iterable_b, Iterable))
itorb = iter(iterable_b)
print('*******', '\n', dir(iterable_b), '\n', dir(itorb), '\n', '*******')

for i in iterable_b:
    print(i)
输出:
False
******* 
 ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'text'] 
 ['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__length_hint__', '__lt__', '__ne__', '__new__', '__next__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__', '__str__', '__subclasshook__'] 
 *******
IterableB.__getitem__()被调用了
i
IterableB.__getitem__()被调用了
t
IterableB.__getitem__()被调用了
e
IterableB.__getitem__()被调用了
r
IterableB.__getitem__()被调用了
a
IterableB.__getitem__()被调用了
b
IterableB.__getitem__()被调用了
l
IterableB.__getitem__()被调用了
e
IterableB.__getitem__()被调用了
_
IterableB.__getitem__()被调用了
a
IterableB.__getitem__()被调用了

Process finished with exit code 0

很奇怪吧,不是说实现__getitem__()方法就能够实现一个可迭代对象吗?为什么collections.abc检测会报错,原因是这个函数是检测对象有没有实现__iter__()方法来判断的,因此这种检测不是很准确。准确的方法是用iter()函数来检测。后面4.1详细介绍iter()函数。这里会有一个疑问,之前不是说for循环调用的是iter()生成迭代器,然后循环执行该迭代器的__next__()吗,为什么这里却调用的是可迭代对象__getitem__()?这里也和iter()有关,放在后面介绍。可看4.2。

  1. 迭代器

所谓迭代器,是实现了__next__()方法的对象,所有的迭代器都是可迭代对象,因此也必须包含__iter__() or __getitem__()方法。

看下面例子。

第一个for循环调用了三次next(),next() = __next__(),其实等价于调用了三次iterator_c.__next__(),打印了三个字符。

第二个for循环,我们看一下执行逻辑

for j in iterator_c

还记得for执行的逻辑吗?

第一步:for j in iter(iterator_c)

iter(iterator_c) = iterator_c.__iter__() # 返回的是self

iter(iterator_c) = iterator_c

第二步:

iter(iterator_c).__next__= iterator_c.__next__()

直到__next__()越界抛出StopIteration(),被for监听到结束for循环。

那么问题来了,__iter__()如果不返回self呢,那么请看6中举的例子。

class IteratorC(object):
    def __init__(self, text):
        self.text = text
        self.index = 0

    def __iter__(self):
        return self

    def __next__(self):
        print("IteratorC.__next__()被调用了")
        try:
            word = self.text[self.index]
        except IndexError:
            raise StopIteration()
        self.index += 1
        return word


iterator_c = IteratorC("iterator_c")
for i in range(3):
    print(next(iterator_c))

for j in iterator_c:
    print(j)

'''
输出:
IteratorC.__next__()被调用了
i
IteratorC.__next__()被调用了
t
IteratorC.__next__()被调用了
e
IteratorC.__next__()被调用了
r
IteratorC.__next__()被调用了
a
IteratorC.__next__()被调用了
t
IteratorC.__next__()被调用了
o
IteratorC.__next__()被调用了
r
IteratorC.__next__()被调用了
_
IteratorC.__next__()被调用了
c
IteratorC.__next__()被调用了

Process finished with exit code 0
'''

2.2中说了可以用__getitem__()代替__iter__(),我们来看一下是怎样的。

这个迭代器并不像上面那个例子那样完美,因为我们看到第一个for 循环中的next()(等价__next__())调用的是IteratorC.__next__(),而第二个for循环却调用的是IteratorC.__getitem__(),这是为什么呢,原因请看下面4.1、4.2、4.3的介绍

class IteratorC(object):
    def __init__(self, text):
        self.text = text
        self.index = 0

    def __getitem__(self, item):
        print("IteratorC.__getitem__()被调用了")
        return self.text[item]

    def __next__(self):
        print("IteratorC.__next__()被调用了")
        try:
            word = self.text[self.index]
        except IndexError:
            raise StopIteration()
        self.index += 1
        return word


iterator_c = IteratorC("iterator_c")
for i in range(3):
    print(next(iterator_c))

for j in iterator_c:
    print(j)

'''
输出:
IteratorC.__next__()被调用了
i
IteratorC.__next__()被调用了
t
IteratorC.__next__()被调用了
e
IteratorC.__getitem__()被调用了
i
IteratorC.__getitem__()被调用了
t
IteratorC.__getitem__()被调用了
e
IteratorC.__getitem__()被调用了
r
IteratorC.__getitem__()被调用了
a
IteratorC.__getitem__()被调用了
t
IteratorC.__getitem__()被调用了
o
IteratorC.__getitem__()被调用了
r
IteratorC.__getitem__()被调用了
_
IteratorC.__getitem__()被调用了
c
IteratorC.__getitem__()被调用了

Process finished with exit code 0
'''

  1. iter()、next()、__iter__()、__next__()、__getitem__()、for...in...详解

4.1 iter()、__iter__()、__getitem__()联系

看一下下面help解释,只看第一种用法,iter(iterable)要求参数是可迭代对象,返回一个迭代器。

当执行iter(iterable),首先去检查iterable中是否有__iter__()方法。如果有,执行__iter__()方法到return,但是要求返回迭代器型,如果返回的是其他型则报错:返回类型错误。所以一个正常迭代器的__iter__()必须返回迭代器型;如果iterable中没有__iter__()方法呢,它会去找__getitem__()方法,但是__getitem__()方法并不会被调用,就像4.1下面的例子一样,找到后会自动根据__getitem__(self, item)中的参数(索引值)及返回值生成迭代器,而__iter__()是返回迭代器;如果__iter__()和__getitem__()都没找到就会报传入的iterable不是一个可迭代对象。具体可以参照底层c++源码中的定义。https://www.cnblogs.com/traditional/p/14040093.html

另外你会发现下面的例子,执行iter(iterable)后生成的迭代器会增加__iter__()和__next__()方法,这也是为什么2.2中能用for循环的原因。而且只要iterable中没有__iter__()方法,iterable是用__getitem__()构造的可迭代对象, 则iter()一定会生成return self的__iter__() 和__next__()。 我们可以从下面例子打印的dir看到这个结果。这是为了生成的迭代器本身要是可迭代对象即能够使用iter(iter(iter(...)))这样子

help(iter)
Help on built-in function iter in module builtins:
iter(...)
    iter(iterable) -> iterator
    iter(callable, sentinel) -> iterator
    
    Get an iterator from an object.  In the first form, the argument must
    supply its own iterator, or be a sequence.
    In the second form, the callable is called until it returns the sentinel.
class IterableC(object):
    def __init__(self, text):
        self.text = text

    def __getitem__(self, item):
        print("IterableC.__getitem__()被调用了")
        return self.text[item]


iterable_c = IterableC("iterable_C")

itorc = iter(iterable_c)

print(dir(iterable_c))
print(dir(itorc))

'''
输出:
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'text']
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__length_hint__', '__lt__', '__ne__', '__new__', '__next__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__', '__str__', '__subclasshook__']

Process finished with exit code 0
'''

当__iter__()和__getitem__()同时存在呢,不好意思,__iter__()的优先级更高,但是正常情况下应该只会存在一个,后面6会介绍一个。

class IterableC(object):
    def __init__(self, text):
        self.text = text

    def __iter__(self):
        print("IterableC.__getitem__()被调用了")
        return iter([1,2,3])

    def __getitem__(self, item):
        print("IterableC.__getitem__()被调用了")
        return self.text[item]

iterable_c = IterableC("iterable_C")

itorc = iter(iterable_c)

for i in iterable_c:
    print(i)

'''
输出:
IterableC.__getitem__()被调用了
IterableC.__getitem__()被调用了
1
2
3

Process finished with exit code 0

'''

4.2 iter()、__next__、__getitem__()联系

首先看下面例子, 3中介绍实现迭代器的两种方式,不难发现下面就是其中一种方式。如果对这个迭代器执行iter(iterable)会是什么样的呢。4.1中说了当可迭代对象中没有__iter__()则执行iter(iterable)生成的迭代器会生成一个__next__()方法,该方法是按照__getitem__()去改写的,即使iterable已经有__next__()方法。因此执行iter(iterable)生成的迭代器调用它的__next__()方法实际上调用的是__getitem__()改写的__next__()方法。

有点绕,直接看例子吧,itorc.__next__()执行的其实是IteratorC.__getitem__()创建的__next__(),而不是IteratorC.__next__()。

重要的一点:

__getitem__()中自动实现了StopIteration(), 而__next__中必须手动添加,当索引越界需要停止。否则for循环最终越界后会报错而不会停止。

其实这个例子是有问题的,虽然没无语法错误,也不会导致什么错误,详细可以看6

class IteratorC(object):
    def __init__(self,):
        self.index = 0
        self.number = [1,2,3,4,5]
        self.str = "abcdefg"

    def __getitem__(self, item):
        print("IteratorC.__getitem__()被调用了")
        return self.number[item]

    def __next__(self):
        print("IteratorC.__next__()被调用了")
        try:
            word = self.str[self.index]
        except IndexError:
            raise StopIteration()
        self.index += 1
        return word


Iterator_c = IteratorC()

itorc = iter(Iterator_c)
itorc.__next__()
print("**********")
for i in Iterator_c:
    print(i)


'''
输出:
IteratorC.__getitem__()被调用了
**********
IteratorC.__getitem__()被调用了
1
IteratorC.__getitem__()被调用了
2
IteratorC.__getitem__()被调用了
3
IteratorC.__getitem__()被调用了
4
IteratorC.__getitem__()被调用了
5
IteratorC.__getitem__()被调用了

Process finished with exit code 0

'''

4.3 next()、__next__()联系

可以认为next(iterator) = iterator.__nexti__()这两个是完全等效的,实际上也是这样。

4.4 for...in...

其实从2.1的例子中都已经介绍过for a in b,只要记住它其实是for a in iter(b),如果把它拆开就是:

c = iter(b)

c.__next__()

c.__next__()

……

是不是瞬间明白了很多。

  1. 索引、__getitem__()

只要对象中带有__getitem__(),即可实现使用[]去得到某一个索引值,只要没有__getitem__()必无法实现。如下例子,实际上是调用了__getitem__()方法


class IterableC(object):
    def __init__(self, text):
        self.text = text

    def __getitem__(self, item):
        print("IterableC.__getitem__()被调用了")
        return self.text[item]

a = IterableC("245458")

print(a[5])
'''
IterableC.__getitem__()被调用了
8

Process finished with exit code 0


'''
  1. 一个看似很不错的例子,实则不是必须,反倒画蛇添足

上面正常的例子,你会有个疑问,为什么我不能把可迭代对象变成迭代器,还需要单独构造一个迭代器呢?我把两个合成一个不就行了。我们详细再看下4.2例子,它是一个迭代器,__getitem__()方法去实现可迭代对象,__next__()方法实现迭代器作用。看起来还很不错,一举两得。但是有没有发现,我使用next()和for循环得到是两个完全不同的数据。所以说到底想干嘛,这样岂不是把自己都绕晕了,根本没搞清自己想要迭代到底是什么数据。

class IteratorC(object):
    def __init__(self,):
        self.index = 0
        self.number = [1,2,3,4,5]
        self.str = "abcdefg"

    def __getitem__(self, item):
        print("IteratorC.__getitem__()被调用了")
        return self.number[item]

    def __next__(self):
        print("IteratorC.__next__()被调用了")
        try:
            word = self.str[self.index]
        except IndexError:
            raise StopIteration()
        self.index += 1
        return word

iterator_c = IteratorC()
for i in range(2):
    print(next(iterator_c))

for j in iterator_c:
    print(j)

'''
输出:
IteratorC.__next__()被调用了
a
IteratorC.__next__()被调用了
b
IteratorC.__getitem__()被调用了
1
IteratorC.__getitem__()被调用了
2
IteratorC.__getitem__()被调用了
3
IteratorC.__getitem__()被调用了
4
IteratorC.__getitem__()被调用了
5
IteratorC.__getitem__()被调用了

Process finished with exit code 0

'''

同样,下面这个例子其实是跟上面是等价的不科学例子,详细不在说

class IteratorC(object):
    def __init__(self,):
        self.index = 0
        self.number = [1,2,3,4,5]
        self.str = "abcdefg"

    def __iter__(self):
        print("IteratorC.__iter__()被调用了")
        return iter(self.number)

    def __next__(self):
        print("IteratorC.__next__()被调用了")
        try:
            word = self.str[self.index]
        except IndexError:
            raise StopIteration()
        self.index += 1
        return word

iterator_c = IteratorC()
for i in range(2):
    print(next(iterator_c))

for j in iterator_c:
    print(j)

'''
输出:
IteratorC.__next__()被调用了
a
IteratorC.__next__()被调用了
b
IteratorC.__iter__()被调用了
1
2
3
4
5

Process finished with exit code 0

'''
  1. 写一两个正常的使用的例子

最偷懒实用的可迭代对象:

class IterableC(object):
    def __init__(self, text):
        self.text = text

    def __getitem__(self, item):
        print("IterableC.__getitem__()被调用了")
        return self.text[item]

完整不偷懒的可迭代对象:

class IterableC(object):
    def __init__(self, text):
        self.text = text

    def __iter__(self):
        return IteratorC(self.text)


class IteratorC(object):
    def __init__(self, text):
        self.text = text
        self.index = 0

    def __iter__(self):
        return self

    def __next__(self):
        print("IteratorC.__next__()被调用了")
        try:
            word = self.text[self.index]
        except IndexError:
            raise StopIteration()
        self.index += 1
        return word

两个是完全等价的,只是一个用的是__getitem__(),另外一个用的是__next__()。可能__getitem__()看起来更加简单舒适。所以讲那么多,直接记住第一种形式,搞定一切。

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

idealmu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值