美文网首页
python拾遗4 - 迭代器

python拾遗4 - 迭代器

作者: 天命_风流 | 来源:发表于2020-05-04 17:48 被阅读0次

关于迭代,之前我写过一篇比较全面的文章,所以今天在这里,主要做一些总结。

什么是迭代

  • 我的理解:在 python 中,依次遍历某个对象就是迭代。通常,我们使用 for 和 in 可以很方便地对一个对象进行迭代。
  • 当然,在迭代中,你可以做一些操作,比如:
a = [0,1,2,3,4]
for i in a:
    if i % 2 == 0:
        print(i)
--------------------输出
0
2
4

迭代基础

  • 可迭代对象:该对象可以通过 iter( ) 方法生成迭代器。在 python中,大部分对象都是可迭代对象,如:列表、字典、字符串、集合...
  • 使用 for ... in ... 可以自动对一个对象进行迭代。
  • 使用iter( )可以手动生成一个迭代器。
  • next( x ) 或 x.next( ) 可以手动推进迭代
  • 迭代是顺序进行的,当迭代走到终点,会抛出异常:
iterator = iter(a)
print(next(iterator))
print(next(iterator))
print(next(iterator))
print(next(iterator))
print(next(iterator))
print(next(iterator))
----------------------输出
0
1
2
3
4
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-4-4eba7ae94107> in <module>()
      5 print(next(iterator))
      6 print(next(iterator))
----> 7 print(next(iterator))

迭代的本质

迭代的本质,就是生成一个迭代器,这个迭代器会依次返回原对象中的元素:

a = [[1,2,3],2,3,4]
iterator = iter(a)
a[0] is next(iterator)
---------------------------输出
True

迭代环境

  • 当遇到 in 关键字,会进入迭代环境,也就是说,我们可能使用了迭代器。
  • 实际上,这种通过重载类的 __contains__()__iter__()__next__() 方法,实现了迭代的行为,具体在后面我们会讲到。

生成器

  • 我们使用 yield 关键字分阶段地抛出数据,利用这个特性,我们可以将一个函数构造为生成器,使用生成器,我们可以生成一个迭代器:
def genrator(n): # 这是一个生成器
    for i in range(n):
        yield i

g1 = genrator(3) # 构造一个迭代器
g2 = genrator(3) # 另一个迭代器
print(next(g1))
print(next(g2))
print(next(g1))
print(next(g2))
print(next(g1))
print(next(g2))
print(next(g1))
print(next(g2))

-----------------------输出
0
0
1
1
2
2
---------------------------------------------------------------------------
StopIteration                             Traceback (most recent call last)
<ipython-input-29-7f77c3761ef9> in <module>()
      5 print(next(g1))
      6 print(next(g2))
----> 7 print(next(g1))
      8 print(next(g2))
  • 使用生成器构造的迭代器可以有更多的行为,从某种意义上说,这样构造的迭代器已经不仅仅是遍历一个对象了。下面是我在之前的文章中的例子,介绍了 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
  • 生成器其实可以构建一个渐进式的计算工具,这样就可以极大地减少内存消耗(具体可以参考下面的生成器表达式)
  • python2 中协程的构建就使用了 yield,所以不要小看他。

列表表达式 和 生成器表达式

  • 通过前面的学习,我们可以使用迭代构建列表,这种构建方法,就是使用列表表达式实现的。
  • 列表表达式中,可以添加简单的逻辑判断:
a = [ i for i in range(5)] # 直接生成
b = [ i for i in range(10) if i % 2 == 0 ] # 使用 if 继续条件过滤
c = [ i if i % 2== 0 else '奇数' for i in range(5)] # 使用 if...else 进行定制
d = [ x*y for x in range(3) for y in range(5,8) ] # 生成式可以嵌套
e = [ (lambda x,y : x* y)(x,y) for x in range(3) for y in range(5,8) ] # 使用 lambda 表达式生成

print(a)
print(b)
print(c)
print(d)
print(e)
--------------------输出
[0, 1, 2, 3, 4]
[0, 2, 4, 6, 8]
[0, '奇数', 2, '奇数', 4]
[0, 0, 0, 5, 6, 7, 10, 12, 14]
[0, 0, 0, 5, 6, 7, 10, 12, 14]
  • 列表表达式可以生成一个列表,如果将代码中的 [ ] 改为 ( ),列表表达式就会变成生成器表达式,使用生成式表达式返回的是一个迭代器。
f = ( i for i in range(5) )
print(type(f))
----------------------------输出
<class 'generator'>
  • 还记得我们之前说过的吗?使用生成器可以节省内存,你可以看一下下面的代码,感受一下他们的区别:
import os
import psutil

# 显示当前 python 程序占用的内存大小
def show_memory_info(hint):
    pid = os.getpid()
    p = psutil.Process(pid)
    
    info = p.memory_full_info()
    memory = info.uss / 1024. / 1024
    print('{} memory used: {} MB'.format(hint, memory))


def test_iterator():
    show_memory_info('initing iterator')
    list_1 = [i for i in range(100000000)]
    show_memory_info('after iterator initiated')
    print(sum(list_1))
    show_memory_info('after sum called')

def test_generator():
    show_memory_info('initing generator')
    list_2 = (i for i in range(100000000))
    show_memory_info('after generator initiated')
    print(sum(list_2))
    show_memory_info('after sum called')

%time test_iterator()
%time test_generator()

########## 输出 ##########

initing iterator memory used: 48.9765625 MB
after iterator initiated memory used: 3920.30078125 MB # 列表使用了 3GB+ 的内存
4999999950000000
after sum called memory used: 3920.3046875 MB
Wall time: 17 s
initing generator memory used: 50.359375 MB
after generator initiated memory used: 50.359375 MB # 迭代器只使用了几乎不可见的内存
4999999950000000
after sum called memory used: 50.109375 MB
Wall time: 12.5 s

实现迭代器的方法

  • 前面已经介绍过,如果你想通过函数构造迭代器,就使用 yield 关键字
  • 如果你想使一个对象可以生成迭代器(让该对象变成一个可迭代对象),你需要进行运算符重载:
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):  # 定义了 iter(object) 的行为
        print('iter=> ', end=' ')
        self.ix = 0
        return self

    def __next__(self): # 定义了 next(object) 的行为
        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):  # 重载了 in 关键字
        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

具体函数之间的优先级关系,可以参考之前的内容

相关文章

网友评论

      本文标题:python拾遗4 - 迭代器

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