在python中, 代码不是越多越好, 而是越少越好. 代码不是越复杂越好, 而是越简单越好
基于这一思想, 在python中, 1行代码能实现的功能, 诀不写5行. 代码越少, 开发效率越高
1. 切片 Slice
(1) list, tuple对经常取指定索引范围的操作, 用循环十分繁琐, 因此python提供了切片操作符, 能大大简化这种操作
(2) 切片的操作
L[0:3] 取前三个元素, 从0开始, 取到3但不包括3,即0,1,2
L[1:2] 从1开始,取出两个元素
L[-2:] 从倒数第2个开始取, 取到最后
L[-2:-1] 从倒数第2个开始取, 取到倒数第1个, 但不包括倒数第1个
L[::5] 从开始到最后, 每5个取一次
L[:10:2] 取前10个数, 每两个取一次
L[:] 原样复制一个list
(3) tuple也是一种list, 唯一区别是tuple不可变, 因此, tuple也可以用切片操作, 只是操作的结果仍是tuple
(0,1,2,3,4,5)[:3] 取前三个 (0,1,2)
(4) 字符串'xxx' 也可以看成是一种list, 每个元素就是一个字符. 因此, 字符串也可以用切片操作, 只是操作的结果仍是字符串
'ABCDEFG'[:3] 取前三个 'ABC'
(5) 有了切片操作, 很多地方循环就不再需要了. python的切片非常灵活, 一行代码就可以实现很多航循环才能完成的操作
2. 迭代
(1) 如果给定一个list或tuple, 我们可以通过for循环来遍历这个list或tuple, 这种遍历我称为迭代(Iteration)
(2) 在python中, 迭代是通过for...in来完成的, 而很多语言比如C或Jave, 迭代list是通过下标完成的
(3) list这种数据类型虽然有下标, 但是很多其他数据类型是没有下标的, 但是, 只要是可迭代对象, 无论有无下标, 都可以迭代, 比如dict就可以迭代. 因为dict的存储方式不是按照list的方式顺序排列, 所以, 迭代出的结果顺序很可能不一样. 默认情况下, sict迭代的是key.
>>> d = {'a': 1, 'b': 2, 'c': 3} >>> for key in d: ... print(key) ... a c b
for value in d.values() # 迭代value
for k, v in d.items() # 同时迭代key和value
(4) 判断一个对象是可迭代对象, 通过collections模块的iterable类型判断:
>>> from collections import Iterable >>> isinstance('abc', Iterable) # str是否可迭代 True >>> isinstance([1,2,3], Iterable) # list是否可迭代 True >>> isinstance(123, Iterable) # 整数是否可迭代 False
3. 列表生成式 List Comprehensions
(1) 列表生成式是python内置的非常简单却强大的可以用来创建list的生成式
(2) 如果要生成[1x1, 2x2, 3x3, ..., 10x10] , 用循环的方法:
>>> L = [] >>> for x in range(1, 11): ... L.append(x * x) ... >>> L [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
但是循环太繁琐, 而列表生成式则可以用一行语句代替循环生成上面的list:
>>> [x * x for x in range(1, 11)] [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
(3) 写列表生成式时, 把生成的元素放到前面, 后面跟for循环, 后面还可以加上if判断, 还可以使用两层循环
>>> [m + n for m in 'ABC' for n in 'XYZ'] ['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']
(4) 把一个list中所有的字符串变成小写:
>>> L = ['Hello', 'World', 'IBM', 'Apple'] >>> [s.lower() for s in L] ['hello', 'world', 'ibm', 'apple']
(5) 列表生成式也可以使用两个变量来生成list:
>>> d = {'x': 'A', 'y': 'B', 'z': 'C' } >>> [k + '=' + v for k, v in d.items()] ['y=B', 'x=A', 'z=C']
(6) 练习:
如果list中既包含字符串,又包含整数,由于非字符串类型没有
lower()
方法,所以列表生成式会报错,使用內建的isinstance函数可以判断一个变量是不是字符串: isinstance(x, str)
修改列表生成式, 通过添加if语句保证列表生成式能正确的执行
L1 = ['Hello', 'World', 18, 'Apple', None] L2 = [s.lower() for s in L1 if isinstance(s, str)] print(L2) # 输出: ['hello', 'world', 'apple']
4. 生成器
(1) 在Python中,这种一边循环一边计算的机制,称为生成器:generator。
(2) 要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的
[]
改成()
,就创建了一个generator:>>> 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>
创建
L
和g
的区别仅在于最外层的[]
和()
,L
是一个list,而g
是一个generator。可以通过
next()
函数获得generator的下一个返回值(3) generator保存的是算法,每次调用
next(g)
,就计算出g
的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration
的错误。但是不断调用next(g)
实在是太变态了,正确的方法是使用for
循环,因为generator也是可迭代对象. 所以,我们创建了一个generator后,基本上永远不会调用next()
,而是通过for
循环来迭代它,并且不需要关心StopIteration
的错误。(4) generator非常强大。如果推算的算法比较复杂,用类似列表生成式的
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
函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。也就是说,上面的函数和generator仅一步之遥。要把
fib
函数变成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'
这就是定义generator的另一种方法。如果一个函数定义中包含
yield
关键字,那么这个函数就不再是一个普通函数,而是一个generator(5) generator和函数的执行流程不一样。函数是顺序执行, 遇到return语句或者最后一行函数语句就返回, 而变成generator的函数, 在每次调用next()的时候执行, 遇到yield语句返回, 再次执行时从上次返回的yield语句出继续执行
(6) 练习:
杨辉三角定义如下:
1 1 1 1 2 1 1 3 3 1 1 4 6 4 1 1 5 10 10 5 1
把每一行看做一个list, 写一个generator, 不断输出下一行的list
def triangles(): L = [1] while True: yield L L.append(0) L = [L[i - 1] + L[i] for i in range(len(L))] n = 0 for t in triangles(): print(t) n = n + 1 if n == 10: break # 期待输出: # [1] # [1, 1] # [1, 2, 1] # [1, 3, 3, 1] # [1, 4, 6, 4, 1] # [1, 5, 10, 10, 5, 1] # [1, 6, 15, 20, 15, 6, 1] # [1, 7, 21, 35, 35, 21, 7, 1] # [1, 8, 28, 56, 70, 56, 28, 8, 1] # [1, 9, 36, 84, 126, 126, 84, 36, 9, 1]
5. 迭代器
(1) 可以直接作用于
for
循环的数据类型有以下几种:一类是集合数据类型, 如list, tuple, dict, set, str等, 一类是generator, 包括生成器和带yield的generator function
这些可以直接作用于for循环的对象统称为可迭代对象: 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
(2) 生成器不但可以作用于for循环, 还可以被next() 函数不断调用返回下一个值, 直到最后抛出 StopIteration错误表示无法继续返回下一个值了. 可以被next() 函数调用并不断返回一下个值得对象称为迭代器: Iterator
(3) 可以使用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
(4) 生成器都是Iterator对象, 但是list, tuple, str虽然是Iterable, 却不是Iterator
(5) 可以使用 iter() 函数把list, dict, str等Iterable 变成 Iterator:
>>> isinstance(iter([]), Iterator) True >>> isinstance(iter('abc'), Iterator) True
(6) 凡是可作用于for循环的对象都是Iterable类型,
(7) 凡是可作用于next() 函数的对象都是Iterator类型, 他们表示一个惰性计算的序列.