美文网首页
学习python——迭代器

学习python——迭代器

作者: 天命_风流 | 来源:发表于2020-02-03 01:55 被阅读0次
迭代器的方方面面

迭代是一个非常有趣的概念,新计算的基础来源于旧的计算结果,同时新的结果将会成为下一次计算的基础。
在python中有很多工具来支持迭代的思想,最具代表性的就是迭代器。下面我将介绍关于迭代的方方面面。

认识迭代

说起迭代,最先想到的就是for循环:

for i in [1,2,3,4,5]:
    print( i**2 )

1
4
9
16
25

这就是迭代的最常见的应用,不仅是列表,python中大部分的内置数据结构都支持迭代,像这种可以使用for循环调用的东西,我们将之成为可迭代对象
在可迭代对象中,有一个方法iter可以构造出一个迭代器,在for循环的工作中,有一部分内容就是使用这个方法构造出一个迭代器(迭代器本身就是一个可迭代对象)。在构造出迭代器之后,for循环会自动对其进行迭代。

  • 使用iter(f)或者f.iter()可以手动构造迭代器
  • 使用 next(f)或者f.next( )可以手动对迭代器进行迭代
  • 当迭代器走到尽头(手动)的时候会触发异常:StopIteration
i = [1,2,3].__iter__()
i.__next__()
Out[44]: 1
i.__next__()
Out[45]: 2
i.__next__()
Out[46]: 3
i.__next__()
Traceback (most recent call last):
  File "F:\Anaconda\lib\site-packages\IPython\core\interactiveshell.py", line 2963, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "<ipython-input-47-aa5506447029>", line 1, in <module>
    i.__next__()
StopIteration

上面的代码使用列表构造了一个迭代器,并且手动迭代至结束。

  • 迭代器有点愣,它只能向前走,不会回头
  • 迭代器每向前迭代一步,上一步的内容就会消失
i = [1,2,3,4,5].__iter__()
i.__next__()
Out[57]: 1
i.__next__()
Out[58]: 2
list(i)
Out[59]: [3, 4, 5] 

可以看到,上面的迭代器转换成列表之后,没有已经迭代过的内容

python中关于迭代的内容

上面的例子拆解了列表生成迭代器的过程,你可以自己尝试对字典、集合、字符串、文件句柄等数据类型进行迭代,除此之外,map、zip、range、enumerate等函数会直接产生迭代器,你可以试试看会有什么效果。
在这里我想说一说列表解析式

列表解析式

列表解析式利用迭代原理构造列表,这个东西很有意思,某些时候写起来也非常方便,同时它的执行性能非常快(由C完成)

[ i * 2 for i in '123' ]
Out[65]: ['11', '22', '33']

如上面的代码所示,列表解析式可以很方便的构造列表

  • 列表解析式中可以实现for 的嵌套
[ x + y for x in 'abc' for y in '123']
Out[64]: ['a1', 'a2', 'a3', 'b1', 'b2', 'b3', 'c1', 'c2', 'c3']
  • 列表解析式可以实现if的判断
[ i for i in [0,1,2,3,4,5,6,7,8,9] if i % 2 == 0 ]
Out[66]: [0, 2, 4, 6, 8]

共享迭代进度

迭代器也可以使用iter(f)或者f.iter()构造迭代器

  • 由迭代器构造的迭代器共享同样的迭代进度
a = [1,2,3,4,5,6,7,8,9]
i = a.__iter__()
i.__next__()
Out[76]: 1
i.__next__()
Out[77]: 2
ii = i.__iter__()      #使用迭代器构造迭代器
ii.__next__()
Out[79]: 3        #发现两个迭代器的进度相同
ii.__next__()
Out[80]: 4
  • 任意一个迭代器结束,共享进度的迭代器也会结束
#接上面的代码,同时省略一些步骤...
ii.__next__()
Out[85]: 9
ii.__next__()    #  迭代器ii已经结束
Traceback (most recent call last):
  File "F:\Anaconda\lib\site-packages\IPython\core\interactiveshell.py", line 2963, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "<ipython-input-86-090408da02bb>", line 1, in <module>
    ii.__next__()
StopIteration
i.__next__()    #迭代器  i 也结束了
Traceback (most recent call last):
  File "F:\Anaconda\lib\site-packages\IPython\core\interactiveshell.py", line 2963, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "<ipython-input-87-aa5506447029>", line 1, in <module>
    i.__next__()
StopIteration

构造自己的迭代器

使用生成器构造迭代器

  • 生成器可以生成迭代器,使用yield关键字控制迭代进度
  • 在发出下一次迭代的信号前,生成器构造的迭代器将处于挂起状态挂起位置为本次yield的位置
  • 由生成器构成的迭代器推进到没有yield且函数执行完毕时,会抛出StopIteration异常
def gen():
    for i in [1,2,3]:
        yield i
    
i = gen()  #使用生成器构造迭代器
i.__next__()
Out[90]: 1
i.__next__()
Out[91]: 2
i.__next__()
Out[92]: 3
i.__next__()    #迭代器走到尽头
Traceback (most recent call last):
  File "F:\Anaconda\lib\site-packages\IPython\core\interactiveshell.py", line 2963, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "<ipython-input-93-aa5506447029>", line 1, in <module>
    i.__next__()
  • 由生成器构造的迭代器可以使用 i.send()接收参数
  • send( ) 会触发下一次迭代,如果使用使用__next __进行迭代,默认传出的参数为None
  • 接收的参数为yield左边的变量,具体请看代码
  • 请注意看下面的例子,并理解生成器将挂起于yield的位置的含义
def gen():
    print('start')
    p1 = yield 1    #第一次迭代将在 yield 1 处挂起,此时 p1 还没有被赋值
    print(p1)
    p2 = yield 2   # 第二次迭代将在 yield 2 处挂起,此时 p2 没有被赋值, p1 = '我是p1'
    print(p2)
    print('over')
i = gen()    # 构建一个生成器
i.__next__()  # 第一次迭代必须使用__next()__
start  # print('start')
Out[96]: 1  #挂起于 yield 1 的位置,此时 p1 没有被赋值

i.send('我是p1')    #使用send( )为p1赋值,同时开启下一次迭代
我是p1   # print(p1)
Out[97]: 2  # 在 yield 2 的地方挂起
i.send('我是p2')  # 开启下一次迭代
我是p2  # print(p2)
over
Traceback (most recent call last):    #走到尽头,触发异常
  File "F:\Anaconda\lib\site-packages\IPython\core\interactiveshell.py", line 2963, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "<ipython-input-98-015be98d3e32>", line 1, in <module>
    i.send('我是p2')
StopIteration

生成器表达式

生成器表达式会直接生成一个迭代器,其使用方法和列表解析式如出一辙,只不过使用()替代了 [ ]

i = ( i ** 2 for i in range(3) )
i
Out[101]: <generator object <genexpr> at 0x00000186AB42A410>
i.__next__()
Out[102]: 0
i.__next__()
Out[103]: 1
i.__next__()
Out[104]: 4
i.__next__()
Traceback (most recent call last):
  File "F:\Anaconda\lib\site-packages\IPython\core\interactiveshell.py", line 2963, in run_code
    exec(code_obj, self.user_global_ns, self.user_ns)
  File "<ipython-input-105-aa5506447029>", line 1, in <module>
    i.__next__()
StopIteration
  • 生成器表达式的输出是迭代和计算的结果,而不使用内存来保存数据,这是它和列表解析式的最大区别

使用类构造可迭代对象

使用__getitem __ 方法

  • 这个方法会重载索引
  • for 语句可以为对象提供从 0 到更大的索引值的索引,重复索引运算,直到检测出超出边界异常
  • 所以__getitem __ 方法本质上不是提供迭代的方法,但是可以在for循环中实现相同的效果
class gen:
    def __init__(self):
        self.data = [1,2,3]
    def __getitem__(self, item):
        return self.data[item]
g = gen()
for i in g:
    print(i)

1
2
3

使用__iter __ 和 __next __ 方法

  • __iter __会返回一个迭代器
  • 迭代器的迭代过程本质上是执行__next __ 的过程
  • 在迭代环境中,python会先使用__iter __ 获取一个迭代器,然后使用__next __不断迭代
  • 使用__iter __ 和 __next __ 可以构造可迭代对象和迭代器对象
  • 这种方法构造迭代器的优先级高于使用索引的优先级
class gen:
    def __init__(self):
        self.data = [1,2,3]
        self.index = 0
    def __iter__(self):
        return self
    def __next__(self):
        if self.index < len(self.data):
            self.index += 1
            return self.data[self.index-1]
        else:
            raise StopIteration

a = gen()
for i in a:
    print(i)

1
2
3

注意__contains __

  • __contains __重载了在 in 后面的行为
  • 判断一个元素是否属于某个结构时使用的 in 也是迭代环境,如 1 in [1,2.3]
  • 在这种情况下先尝试__contains __,然后再尝试使用__iter __
  • 我在下面的综合示例中会用到它

综合示例

下面一段代码使用了上面的所有可以应对迭代环境的重载方法,也将会展现出在迭代环境下各个方法加载的优先级:

class Iters:
    def __init__(self, value):
        self.data = value

    def __getitem__(self, item):
        print('get[%s]:' % item, end=' ')
        return self.data[item]

    def __iter__(self):  # 将这个方法注释掉,看看会发生什么
        print('iter=> ', end=' ')
        self.ix = 0
        return self

    def __next__(self):
        print('next', end=' ')
        if self.ix == len(self.data): raise StopIteration
        item = self.data[self.ix]
        self.ix += 1
        return item

    def __contains__(self, item):  # 将这个方法注释掉,看看会发生什么
        print('contains:', end=' ')
        return item in self.data


X = Iters([1, 2, 3, 4, 5])
print(3 in X)
for i in X:
    print(i, end='|')
print()
print([i ** 2 for i in X])
print(list(map(bin, X)))

I = iter(X)
while True:
    try:
        print(next(I), end='@')
    except StopIteration:
        break

执行结果:

contains: True
iter=> next 1|next 2|next 3|next 4|next 5|next
iter=> next next next next next next [1, 4, 9, 16, 25]
iter=> next next next next next next ['0b1', '0b10', '0b11', '0b100', '0b101']
iter=> next 1@next 2@next 3@next 4@next 5@next

将__contains __注释掉:

iter=> next next next True
iter=> next 1|next 2|next 3|next 4|next 5|next
iter=> next next next next next next [1, 4, 9, 16, 25]
iter=> next next next next next next ['0b1', '0b10', '0b11', '0b100', '0b101']
iter=> next 1@next 2@next 3@next 4@next 5@next

将__contains __、__iter __和__next __都注释掉

get[0]: get[1]: get[2]: True
get[0]: 1|get[1]: 2|get[2]: 3|get[3]: 4|get[4]: 5|get[5]:
get[0]: get[1]: get[2]: get[3]: get[4]: get[5]: [1, 4, 9, 16, 25]
get[0]: get[1]: get[2]: get[3]: get[4]: get[5]: ['0b1', '0b10', '0b11', '0b100', '0b101']
get[0]: 1@get[1]: 2@get[2]: 3@get[3]: 4@get[4]: 5@get[5]:

结论:迭代优先级:__contains __ > __iter __ > getitem

上面就是迭代器的理论知识了,迭代的特性是非常有趣的,希望可以在编码中用到它

相关文章

网友评论

      本文标题:学习python——迭代器

      本文链接:https://www.haomeiwen.com/subject/wqkcxhtx.html