Python切片、迭代、列表生成式、生成器、迭代器

时间:2021-07-10 19:49:47

切片:

在Python中对于具有序列结构的数据来说都可以使用切片操作,比如列表(list)中,我们可以用切片取出其中一部分元素。

需要注意的是序列对象某个索引位置返回的是一个元素,而切片操作返回是和被切片对象相同类型对象的副本

如:

>>> alist = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> alist[0]
0
>>> alist[0:1]
[0]

运行截图如下:

Python切片、迭代、列表生成式、生成器、迭代器

切片的语法表达式:

[start_index : end_index : step]

start_index表示起始索引;

stop_index表示结束索引;

step表示步长,步长不能为0,且默认值为1;

start_index和stop_index不论是正数还是负数索引还是混用都可以,但是要保证 list[stop_index]元素的位置必须在list[start_index]元素的位置右边,否则取不出元素。

如:

>>> L[1:-2]
[1,2,3,4,5,6,7]

运行截图如下:

Python切片、迭代、列表生成式、生成器、迭代器

切片操作是指按照步长,截取从起始索引到结束索引,但不包含结束索引(也就是结束索引减1)的所有元素

python3支持切片操作的数据类型有listtuplestringunicoderange;

切片返回的结果类型与原对象类型一致;

切片不会改变原对象,而是重新生成了一个新的对象。

对元组(tuple)也可以使用切片,切片的结果也是一个元组(tuple)。

如:

>>> (0, 1, 2, 3, 4, 5)[:3]
(0, 1, 2)

对字符串也可以用切片,切片时字符串看成是一个list,每个元素是一个字符。切片结果仍是字符串。

>>> 'ABCDEFG'[:3]
'ABC'

运行截图如下:

Python切片、迭代、列表生成式、生成器、迭代器

迭代:

在Python中,对一个列表(list)或元组(tuple)通过for in循环来遍历它,这种遍历就叫做迭代(Iteration)。

Python中列表(list)、元组(tuple)、字典(Dictionary)、字符串、生成器(generator)都是可迭代对象。

注意只要是可以迭代的对象,无论有无下标,都可以迭代。

如:

>>> d = {'a': 1, 'b': 2, 'c': 3}
>>> for key in d:
...     print(key)
...
a
c
b

运行截图如下:

Python切片、迭代、列表生成式、生成器、迭代器

默认情况下,dict迭代的是key。如果要迭代value,可以用for value in d.values(),如果要同时迭代key和value,可以用for k, v in d.items()

字符串的迭代举例:

>>> for ch in 'ABC':
...     print(ch)
...
A
B
C

运行截图如下:

Python切片、迭代、列表生成式、生成器、迭代器

Python中提供了一个判断函数来判断一个对象是否是可迭代对象。

即collections模块里的Iterable类,使用该类的isinstance()函数即可判断。

如:

>>> from collections import Iterable
>>> isinstance('abc', Iterable) # str是否可迭代
True
>>> isinstance([1,2,3], Iterable) # list是否可迭代
True
>>> isinstance(123, Iterable) # 整数是否可迭代
False

如果在迭代时我们想同时取得元素的下标怎么办?(类似C/C++for循环中从数组取得某个元素后同时得到其下标)

Python内置的enumerate()函数可以把一个list变成索引-元素对,这样就可以在for循环中同时迭代索引和元素本身。

如:

>>> for i, value in enumerate(['A', 'B', 'C']):
...     print(i, value)
...
0 A
1 B
2 C

运行截图如下:

Python切片、迭代、列表生成式、生成器、迭代器

列表生成式:

列表生成式即List Comprehensions,是Python内置的用来生成列表(list)的特定语法形式的表达式。

列表生成式的格式:

[exp for iter_var in iterable]

运行过程:

迭代iterable中的每个元素;

每次迭代都先把结果赋值给iter_var,然后通过exp得到一个新的计算值;

最后把所有通过exp得到的计算值以一个新列表的形式返回。

如,要生成一个元素从1到10的列表(list):

>>> list(range(1, 11))
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

再比如,生成1到10各自的平方的列表(list):

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

把要生成的元素x * x放到前面,后面跟for循环,就可以把创建出这个list。

运行截图如下:

Python切片、迭代、列表生成式、生成器、迭代器

我们还可以进一步在for循环后面加上if判断,筛选出偶数的平方的元素的列表(list):

>>> [x * x for x in range(1, 11) if x % 2 == 0]
[4, 16, 36, 64, 100]

运行截图如下:

Python切片、迭代、列表生成式、生成器、迭代器

生成器:

通过上面列表生成式,我们可以直接创建一个列表。但是列表容量肯定是有限的,而且元素很多时,列表会占用很大的存储空间,且不是所有元素都需要访问,这就造成了浪费。

如果列表元素可以按照某种算法推算出来,在循环的过程中不断推算出后续的元素,这样就不必创建完整的列表(list),从而节省大量的空间。在Python中生成器(generator)就是这样一种一边循环一边计算的机制。

生成器的构造方式:

使用类似列表生成式的方式生成 (2*n + 1 for n in range(3, 11)),即把列表生成式最外层方括号改成圆括号;

使用包含yield的函数来生成。

如:

>>> L = [x * x for x in range(10)]
>>> L
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> g = (x * x for x in range(10))
>>> g
<generator object <genexpr> at 0x1022ef630>

注意:

Python 3.3之前的版本中,不允许迭代函数法中包含return语句。

注意此时我们无法打印出g代表的generator的每一个元素。

此时我们可以通过next()函数获得generator的下一个返回值。

>>> next(g)
0
>>> next(g)
1
>>> next(g)
4
>>> next(g)
9
>>> next(g)
16
>>> next(g)
25
>>> next(g)
36
>>> next(g)
49
>>> next(g)
64
>>> next(g)
81
>>> next(g)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

当计算到最后一个元素,没有更多的元素时,Python抛出StopIteration的错误。

在实际应用时,我们往往使用for循环,因为generator也是可迭代对象。

如:

>>> g = (x * x for x in range(10))
>>> for n in g:
...     print(n)
... 
0
1
4
9
16
25
36
49
64
81

运行截图如下:

Python切片、迭代、列表生成式、生成器、迭代器

当推算的算法比较复杂(即上例的x*x),用类似列表生成式的for循环无法实现的时候,我们还可以用函数来实现。

如著名的斐波拉契数列(Fibonacci):

除第一个和第二个数外,任意一个数都可由前两个数相加得到。

1, 1, 2, 3, 5, 8, 13, 21, 34, ...

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        print(b)
        a, b = b, a + b
        n = n + 1
    return 'done'

该函数输出如下:

>>> fib(6)
1
1
2
3
5
8
'done'

上面的函数只要稍加改造就可以变成generator,即只把print(b)改为yield b

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        yield b
        a, b = b, a + b
        n = n + 1
    return 'done'

如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator。generator在执行过程中,遇到yield关键字就会中断执行,下次调用则继续从上次中断的位置(即yield语句的下一条语句)继续执行。

把函数改成generator后,我们基本上从来不会用next()来获取下一个返回值,而是直接使用for循环来迭代

>>> for n in fib(6):
...     print(n)
...
1
1
2
3
5
8

运行截图如下:

Python切片、迭代、列表生成式、生成器、迭代器

注意:

for循环调用generator时,我们取不到generator的return语句的返回值。

如果想要拿到返回值,必须捕获StopIteration错误,返回值包含在StopIterationvalue中。改写上面的程序:

>>> g = fib(6)
>>> while True:
...     try:
...         x = next(g)
...         print(x)
...     except StopIteration as e:
...         print('Generator return value:', e.value)
...         break
...
g: 1
g: 1
g: 2
g: 3
g: 5
g: 8
Generator return value: done

运行截图如下:

Python切片、迭代、列表生成式、生成器、迭代器

迭代器:

迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能前进不能回退。

同生成器类似,迭代器不要求准备好整个迭代过程中所有的元素。仅仅是在迭代至某个元素时才计算该元素,而在这之前或之后,元素可以不存在或者被销毁。这大大节省了系统存储空间。可以被next()函数调用并不断返回下一个值的对象称为迭代器(Iterator)Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。

可迭代对象(Iterable)有以下几种:

一类是集合数据类型,如列表(list)、元组(tuple)、字典(Dictionary)、集合(set)、字符串等;

一类是生成器(generator),包括列表生成式改写的生成器和带yield的生成器函数。

前面已经说过,collections模块里的Iterable类,使用该类的isinstance()函数可判断一个对象是否是可迭代对象(Iterable)。

>>> from collections import Iterable
>>> isinstance([], Iterable)
True
>>> isinstance({}, Iterable)
True
>>> isinstance('abc', Iterable)
True
>>> isinstance((x for x in range(10)), Iterable)
True
>>> isinstance(100, Iterable)
False

我们还可以用isinstance()函数判断一个对象是否是Iterator。

>>> from collections import Iterator
>>> isinstance((x for x in range(10)), Iterator)
True
>>> isinstance([], Iterator)
False
>>> isinstance({}, Iterator)
False
>>> isinstance('abc', Iterator)
False

生成器都是Iterator对象,但listdictstr虽然是Iterable,却不是Iterator

在Python中我们可以使用iter()函数把listdictstrIterable变成Iterator。

如:

>>> isinstance(iter([]), Iterator)
True
>>> isinstance(iter('abc'), Iterator)
True

Iterator对象特性:

Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。这个对象的数据流可以看成一个有序序列,但我们并不知道这个序列的长度,只能不断通过next()函数计算出下一个数据值,且只有在需要返回下一个数据时它才会计算。这种特性使得Iterator甚至可以表示一个无限大的数据流,例如全体自然数。

Python的for in循环本质上就是通过不断调用next()函数实现的。

如:

for x in [1, 2, 3, 4, 5]:
    pass

实际上完全等价于:

# 首先获得Iterator对象:
it = iter([1, 2, 3, 4, 5])
# 循环:
while True:
    try:
        # 获得下一个值:
        x = next(it)
    except StopIteration:
        # 遇到StopIteration就退出循环
        break

总结:

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

凡是可作用于next()函数的对象都是Iterator类型;

集合数据类型如listdictstr等是Iterable但不是Iterator,不过可以通过iter()函数获得一个Iterator对象。