跳转至

迭代和推导

一、可迭代对象与迭代器

1. 可迭代对象

可以直接作用于 for 循环的对象统称为可迭代对象(Iterable)。内部含有__iter__方法。如字符串、列表、元组、字典、集合、文件句柄等。可迭代对象.__iter__():可得到迭代器对象。

1
2
3
>>> l = []
>>> l.__iter__()
<list_iterator object at 0x000001931878FBE0>

1.1 判断一个对象是否是可迭代对象

1
2
3
4
5
6
>>> '__iter__' in dir('hello')
True
# 或者
>>> from collections import Iterable
>>> isinstance([], Iterable)
True

凡是可作用于 for 循环的对象都是 Iterable 类型

1.2. 可迭代对象的特点

1.2.1 优点
  1. 存储的数据能直接显示,比较直观
  2. 拥有的方法比较多,操作方便
1.2.2 缺点
  1. 占用内存
  2. 不能直接通过for循环取值

2. 迭代器

可以被 next()函数调用并不断返回下一个值的对象称为迭代器(Iterator),内部含有__iter__方法和__next__方法的对象。迭代器指的是迭代取值的工具,迭代是一个重复的过程,每次重复,都是基于上一次的结果而继续的,单纯的重复并不是迭代。

2.1 判断一个对象是不是迭代器

1
2
3
4
5
>>> from collections import Iterator
>>> isinstance((x for x in range(10)), Iterator)
True
>>> isinstance([], Iterator)
False

凡是可作用于 next()函数的对象都是 Iterator 类型,是一个惰性

计算的序列;

2.2 可迭代对象转换为迭代器

方式一:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
>>> d = {'a': 1, 'b': 2, 'c': 3}
>>> d_iterator = d.__iter__()
>>> print(d_iterator)
<dict_keyiterator object at 0x0000019318519598>
>>>
>>> print(d_iterator.__next__())
a
>>> print(d_iterator.__next__())
b
>>> print(d_iterator.__next__())
c
>>> print(d_iterator.__next__())
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

方式二:

1
2
3
>>> s = 'hello'
>>> iter(s)
<str_iterator object at 0x000002157774D898>

2.3 for循环的工作原理

1
2
3
4
5
6
d = {'a': 1, 'b': 2, 'c': 3}
# 1、d.__iter__()得到一个迭代器对象
# 2、迭代器对象.__next__()拿到一个返回值,然后将该返回值赋值给k
# 3、循环往复步骤2,直到抛出StopIteration异常for循环会捕捉异常然后结束循环
for k in d:
    print(k)

2.4 迭代器的优缺点

2.4.1.优点
  1. 为序列和非序列类型提供了一种统一的迭代取值方式。
  2. 惰性计算:迭代器对象表示的是一个数据流,可以只在需要时才去调用next来计算出一个值,就迭代器本身来说,同一时刻在内存中只有一个值,因而可以存放无限大的数据流,而对于其他容器类型,如列表,需要把所有的元素都存放于内存中,受内存大小的限制,可以存放的值的个数是有限的。
2.4.2 缺点
  1. 除非取尽,否则无法获取迭代器的长度
  2. 只能取下一个值,不能回到开始,更像是‘一次性的’,迭代器产生后的唯一目标就是重复执行next方法直到值取尽,否则就会停留在某个位置,等待下一次调用next;若是要再次迭代同个对象,你只能重新调用iter方法去创建一个新的迭代器对象,如果有两个或者多个循环使用同一个迭代器,必然只会有一个循环能取到值。

二、生成器

生成器的本质是迭代器,

1. 生成器的获取方式

  1. 生成器函数
  2. 生成器表达式
  3. python内部提供

2. 生成器函数

如果def定义的函数中有yield,这个函数就是生成器函数,不再是普通函数。生成器函数中可以有多个yield,一个yield对应一个next

2.1 yield

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
>>> def func():
...     print('第一次')
...     yield 1
...     print('第二次')
...     yield 2
...     print('第三次')
...     yield 3
...     print('第四次')
...
>>> g=func()
>>> print(g)
<generator object func at 0x0000019318767A98>
>>> next(g)
第一次
1
>>> next(g)
第二次
2
>>> next(g)
第三次
3
>>> next(g)
第四次
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>> #next() 会触发函数体代码的运行,然后遇到yield停下来,将yield后的值
... # 当做本次调用的结果返回
...

有了yield关键字,我们就有了一种自定义迭代器的实现方式。yield可以用于返回值,但不同于return,函数一旦遇到return就结束了,而yield可以保存函数的运行状态挂起函数,用来返回多次值

2.1 yield from

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
>>> def func():
...     l = [1,2,3]
...     yield l
...
>>> next(func())
[1, 2, 3]
# yield from 把列表变成迭代器
>>> def func():
...     l = [1,2,3]
...     yield from l
...
>>> next(func())
1

3. 生成器表达式

把列表生成式的[]改成()

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
>>> g = (x * x for x in range(10) if x % 3 == 0)
>>> print(next(g))
0
>>> print(next(g))
9
>>> print(next(g))
36
>>> print(next(g))
81
>>> print(next(g))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration
>>> g
<generator object <genexpr> at 0x0000019318767A98>

三、生成式

1. 列表生成式

列表推导是通过对序列中的每一项运行一个表达式来创建一个新列表的方法。

1.1 循环模式

1
2
>>> [ x**2 for x in range(1,11) ]
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

2.2. 筛选模式

1
2
>>> [ x for x in range(1,11) if x % 2 == 0 ]
[2, 4, 6, 8, 10]

2. 字典生成式

场景一

1
2
3
4
>>> keys = ['name', 'age', 'gender']
>>> dic = {key: None for key in keys}
>>> print(dic)
{'name': None, 'age': None, 'gender': None}

场景二

1
2
3
4
>>> items = [('name', 'egon'), ('age', 18), ('gender', 'male')]
>>> res = {k: v for k, v in items}
>>> print(res)
{'name': 'egon', 'age': 18, 'gender': 'male'}

3. 集合生成式

1
2
3
4
>>> keys = ['name', 'age', 'gender']
>>> set1 = {key for key in keys}
>>> print(set1, type(set1))
{'name', 'age', 'gender'} <class 'set'>