关于迭代,之前我写过一篇比较全面的文章,所以今天在这里,主要做一些总结。
什么是迭代
- 我的理解:在 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
具体函数之间的优先级关系,可以参考之前的内容
网友评论