掌握了 Python 的数据类型、语句和函数,基本上就可以编写出很多有用的程序了。
例:
构造一个元素为1, 3, 5, 7, ..., 99的列表.
L = []
n = 1
while n <= 99:
L. append (n) n += 2
L = [n for n in range(100) if n % 2 == 1]
但是,Python 代码不是越多越好,而是越少越好;不是越复杂越好,而是越简单越好。
基于这一思想,这一节来介绍一些 Python 中非常有用的高级特性,能一行代码实现的功能,绝不写五行。
请始终牢记,代码越少,开发效率越高。
8.1 切片
取一个 list 或 tuple 的部分元素是非常常见的操作。
例:
给定一个 list
L = ['apple ', 'banana ', 'cherry ', 'grape ', 'peach ']
如何取出其前三个元素?
• 笨办法
r = [L[0] , L[1] , L[2]]
print(r)
• 使用循环
r = []
for i in range(3):
r. append (L[i]) print(r)
• 使用切片 (slice)
在很多编程语言中,针对字符串提供了很多各种截取函数(例如,substring),其实目的就是对字符串切 片。Python 没有针对字符串的截取函数,只需要切片一个操作就可以完成,非常简单。
8.2 迭代
如果给定一个 list 或 tuple,我们可以通过for循环来遍历这个 list 或 tuple,这种遍历称为迭代(Iteration)。
只要是可迭代对象,无论有无下标,都可以迭代。
例:
dict 没有下标,但它也可以迭代:
d = {'a': 1, 'b': 2, 'c': 3}
for key in d:
print(key)
a b c
因为 dict 的存储不是顺序排列的,所以迭代出的结果顺序可能会不一样。
例:
因 str 也是可迭代对象,它也可用for循环进行迭代:
for ch in 'abcd ':
print(ch)
a b c d
当我们使用for循环时,只要作用于一个可迭代对象,for循环就可以正常运行,而我们不太关心该对象究 竟是 list 还是其他数据类型。
8.2.1 如何判断一个对象是可迭代对象?
可通过 collections 模块的 Iterable 类型判断:
from collections .abc import Iterable print( isinstance('abc ', Iterable ) ) print( isinstance([] , Iterable ) ) print( isinstance(() , Iterable ) ) print( isinstance({} , Iterable ) ) print( isinstance({1} , Iterable ) )
print( isinstance((x for x in range(10)), Iterable ) )
print( isinstance(123 , Iterable ) )
True True True True True False
8.2.2 enumerate()函数
Python 内置的enumerate()可以把一个 list 变成索引-元素对,这样就可以在for循环中同时迭代索引和元素 本身:
for i, value in enumerate([ 'a', 'b', 'c']):
print(f"{i}: { value }")
0: a 1: b 2: c
for i, value in enumerate(( 'a', 'b', 'c')):
print(f"{i}: { value }")
0: a 1: b 2: c
for i, value in enumerate('abc '):
print(f"{i}: { value }")
0: a 1: b 2: c 以上代码中,for循环同时引用两个变量,这在 Python 中非常常见,如 for x, y in [[1 , 1], [2, 4], [3, 9]]:
print(f'{x}, {y}')
1, 1 2, 4 3, 9
8.3 列表生成式
列表生成式即 List Comprehensions,是 Python 内置的非常简单却强大的可以用来创建 list 的生成式。
例: print(L)
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
2. 列表生成式
L = [x * x for x in range(1, 11)]
print(L)
[1, 4, 9, 16, 25, 36, 49, 64, 81,
例:
L = [m + n for m in 'ABC ' for n in 'XYZ ']
print(L)
['AX ', 'AY ', 'AZ ', 'BX ', 'BY', 'BZ ', ' CX ', 'CY ', 'CZ ']
8.3.1 列表生成式的应用 例:
利用os模块列出当前目录下的所有文件和目录名 dirs = [d for d in os. listdir ('.')]
print(dirs)
['lec01.out ', 'lec01 .log ', 'src ', 'tmp.out ', 'lec01 .aux ', 'lec01 .tex ', 'asset ', 'lec01.pdf ', '. DS_Store ', 'makefile ']
例:
将一个 list 中的字符串变为小写
L = ['Hello ', 'Wuhan ', 'University ']
l = [s. lower () for s in L]
print(l)
['hello ', 'wuhan ', 'university ']
例:
将字典{'a': 1, 'b': 2, 'c': 3}转换为列表的形式['a=1', 'b=2', 'c=3']
d = {'a': 1, 'b': 2, 'c': 3}
l = [k + '=' + str(v) for k, v in d. items ()]
print(l)
['a=1 ', 'b=2 ', 'c=3 ']
8.4 生成器
通过列表生成式,可以直接创建一个列表,而受到内存限制,列表容量肯定是有限的。假设创建了一个包含 100 万个元素的列表,而仅需访问前面几个元素,这不仅占用很大的存储空间,并且后面绝大多数元素占用的 空间都白白浪费了。
所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素 呢?这样就不必创建完整的 list,从而节省大量的空间。在 Python 中,这种一边循环一边计算的机制,称为 生成器:generator。
8.4.1 generator 的创建
• 把列表生成式的[]改为(),即可创建一个 generator:
L = [x *x for x in range(3)]
print(L)
G = (x *x for x in range(3)) print(G)
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
<generator object <genexpr > at 0 x7fd6a02f26d8 >
创建L和G的区别仅在于最外层的[]和(),L是一个 list,而G是一个 generator。
• 使用关键字yield. 例如,生成斐波拉契数列可以这么做 def fib(n):
i, a, b = 0, 0, 1 while i < n:
yield b
a, b = b, a + b i = i + 1
f = fib (6) print(f)
<generator object fib at 0 x7fc4e6b576d8 >
如果一个函数中包含关键字yield,则这个函数就不再是一个普通函数,而是一个 generator.
8.4.2 generator 的使用
• 使用next.
print(next(G)) print(next(G)) print(next(G)) print(next(G)) 0
1 4
Traceback (most recent call last):
File "src/ lec01 /code/ generator .py", line 9, in <module >
print(next(G)) StopIteration
这种方式过于复杂,正确的使用方式是for ... in循环,因为 generator 也是可迭代对象。
• 使用for ... in循环.
for i in fib (4):
print(i)
1 1 2 3
注意,generator 和 function 的执行流程不一样。
• function 是顺序执行,遇到return语句或者最后一行语句就返回。
• 变成 generator 的 function,在每次调用next()时执行,遇到yield语句返回,再次执行时从上次返回 的yield语句处继续执行。
8.5 可迭代对象 (Iterable) 与迭代器 (Iterator)
可以直接作用于 for 循环的数据类型有以下几种:
• 集合数据类型,如 list、tuple、dict、set、str 等;
• generator,包括生成器和带 yield 的 generator function。
这些可以直接作用于 for 循环的对象统称为可迭代对象 (Iterable)。可使用isinstance()来判断一个对象是否 为Iterable对象。
from collections .abc import Iterable print( isinstance('abc ', Iterable ) ) print( isinstance([] , Iterable ) ) print( isinstance(() , Iterable ) ) print( isinstance({} , Iterable ) ) print( isinstance({1} , Iterable ) )
print( isinstance((x for x in range(10)), Iterable ) ) print( isinstance(123 , Iterable ) )
True True True True True True False
可使用next()函数调用并不断返回下一个值的对象称为迭代器 (Iterator). 可使用isinstance()来判断一 个对象是否为Iterator对象。
from collections .abc import Iterator print(isinstance([], Iterator )) print(isinstance({} , Iterator )) print(isinstance('abc ', Iterator ))
print(isinstance((x for x in range(5)), Iterator ))
False False False True
生成器都是 Itertor 对象,但 list、dict、str 虽然是 Iterable 对象,却不是 Iterator。可使用iter()函数将 list、dict、str 等 Iterable 对象变成 Iterator.
print(isinstance(iter([]) , Iterator )) print(isinstance(iter('abc '), Iterator )) True
True
为什么 list、dict、str 等数据类型不是 Iterator 呢?
因为 Python 的 Iterator 对象表示的是一个数据流,它可以被next()函数调用并不断返回下一个数据,直 到没有数据时抛出 StopIteration 错误。可以把这个数据流看做是一个有序序列,但不能提前知道其长度,只 能不断通过next()函数来按需计算下一个数据。因此,Iterator 的计算是惰性的,只有在需要返回下一个数据 时它才计算。Iterator 甚至可以是一个无限大的数据流,如全体自然数;而使用 list 则不可能存储无限个元素。
小结
• 凡是可作用于 for 循环的对象都是 Iterable 类型;
• 凡是可作用于 next() 函数的对象都是 Iterator 类型,它们表示一个惰性计算的序列。
集合数据类型如 list、dict、str 等是 Iterable 但不是 Iterator,不过可以通过 iter() 函数获得一个 Iterator 对 象。
8.6 习题
1. 编写函数min_and_max(L),求一个 list 的最小值和最大值,并以 tuple 将它们返回。
2. 编写一个 generator,生成杨辉三角。