高级特性
在Python中,代码越少越简单约好。基于这一思想,后面的几个篇章介绍Python一些非常有用的高级特性。
比方说构造一个1~99的奇数列表,可以简单地用循环实现:
1 | L = [] |
切片
切片即取一个list或tuple部分元素的操作。 当我们需要取列表前n个元素,即索引0~N-1的元素时,有两种方法:
1.方法1是用循环
1 | 'Michael', 'Sarah', 'Tracy', 'Bob', 'Jack'] L = [ |
2.方法2是利用切片操作符
1 | 0:3] L[ |
如果经常要取指定的索引范围,用循环就显得太过繁琐了,Python提供了切片操作来简化这个过程。注意,切片操作的索引左闭右开。
如果索引从0开始,还可以改写为 L[:3]
。 如果索引到列表最后结束,同样可以简略写为 L[0:]
。
此外,Python还支持倒数切片。列表最后一项的索引在倒数中为-1。
1 | 2:] L[- |
特别地,切片操作还支持每隔k个元素取1个这样的操作。先创建一个0~99的整数列表:
1 | list(range(100)) L = |
取后10个只需起始索引为-10即可:
1 | 10:] L[- |
前十个数隔两个取一个:
1 | 10:2] L[: |
所有数,隔五个取一个:
1 | 5] L[:: |
注意!对list进行切片操作得到的还是list;对tuple进行切片操作得到的还是tuple。 特别地,字符串也可看为一种list,同样可以使用切片操作。
迭代
Python中可迭代的对象包括字符串,list,tuple,dict,set和文件等等。 对这些可迭代对象可以使用 for...in
循环来遍历。Python对for循环的抽象程度高于Java和C,所以即使没有下标也能迭代。
比如循环遍历一个dict:
1 | 'a': 1, 'b': 2, 'c': 3} d = { |
直接打印key会打印所有dict中的key,更改迭代的写法为 for value in d.values()
就变为迭代dict中所有的value。 如果同时要访问key和value,还可以使用 for k, v in d.items()
。
字符串同样可以用for循环迭代:
1 | for ch in 'ABC': |
要判断一个对象是否可迭代对象可以通过collections模块的 Iterable
类型来判断:
1 | from collections import Iterable |
正如上面迭代dict一样,for循环可以同时引用两个甚至多个变量:
1 | for i, value in enumerate(['A', 'B', 'C']): |
例子里的 enumerate
方法通过enumerate官方文档了解,它返回一个枚举对象,并且传入参数可迭代时它就是一个可迭代的对象。
可以用 list(enumerate(可迭代对象))
把一个可迭代对象变为元素为tuple类型的list,每个tuple有两个元素,形式如:(序号,原可迭代对象内容)
。
并且使用enumerate时可以指定开始的序号,enumerate(iterable, start=0)
,不写时默认参数为0,即序号从0开始。 可以自己指定为其他数。
列表生成式
列表生成式即List Comprehensions,是Python内置的用于创建list的方式。
比方说生成1到10,可以:
1 | list(range(1, 11)) |
要生成 [1x1, 2x2, 3x3, ..., 10x10]
平方序列,笨办法是用循环:
1 | L = [] |
用列表生成式只用一个语句就可以了:
1 | for x in range(1, 11)] [x * x |
写列表生成式时,把要生成的元素 x * x
放到前面,后面跟for循环,就可以把list创建出来。
在for循环后面还可以加上if判断,比方说这个例子用于筛选出偶数的平方数:
1 | for x in range(1, 11) if x % 2 == 0] [x * x |
使用两层循环还可以生成全排列:
1 | for m in 'ABC' for n in 'XYZ'] [m + n |
列出当前目录下所有文件和目录名也非常简单:
1 | import os # 导入os模块,模块的概念后面讲到 |
前面一节提到for循环迭代可以同时用两个变量,这里列表生成式同样可以用两个变量来生成list。
1 | 'x': 'A', 'y': 'B', 'z': 'C' } d = { |
把list中所有字符串的大写字母换成小写:
1 | 'Hello', 'World', 'IBM', 'Apple'] L = [ |
生成器
通过列表生成式可以简单地创建列表,但受到内存限制,列表容量是有限的。如果列表元素很多,而我们仅需访问前面一部分元素,则会造成很大的存储空间的浪费。
生成器(generator)就意在解决这个问题,允许在循环过程中不断推算出后续元素,而不用创建完整的list。在Python中,这种边循环边计算的机制称为生成器。
和列表生成式的区别很简单,仅仅是把外层的[]方括号换成()圆括号。
1 | for x in range(5)] L = [x * x |
生成器无法通过索引访问,因为它保存的是算法,要遍历生成器需要通过 next()
函数。
1 | next(g) |
当到达最后一个元素时,再使用 next()
就会出现 StopIteration
错误。 当然,实际遍历生成器时不会这样一个一个用 next()
方法遍历,用for循环进行迭代即可。
1 | for x in range(5)) g = (x * x |
当算法比较复杂,用简单for循环无法写出来时,还可以通过函数来实现:
1 | def fib(max): |
比方说这个计算斐波那契数列的函数,稍微改写一下即可变成generator:
1 | def fib(max): |
这是定义generator的另一种方法,如果一个函数定义中包含yield关键字,则该函数就变为一个generator。
1 | 6) f = fib( |
函数是顺序执行,遇到return语句或到达最后一行函数语句就返回。而变成generator的函数,在每次调用 next()
的时候执行,遇到yield就返回,下次执行会从yield的地方开始。
1 | for n in fib(6): |
同样地,把函数改成generator后,我们不需要用next()方法获取写一个返回值,而是只借用for循环进行迭代。
但是这样就拿不到fib函数return语句的值(即字符串done),要获取这个值必须捕获 StopIteration
这个错误,它的value就是我们返回的值:
1 | 6) g = fib( |
生成器的工作原理是:在for循环的过程中不断计算出下一个元素,并在适当的条件结束for循环。
对于函数改成的generator来说,遇到return语句或者执行到函数体最后一行语句就结束generator,for循环随之结束。
普通函数和生成器函数可以通过调用进行区分,调用普通函数会直接返回结果,调用生成器函数则会返回一个生成器对象。
杨辉三角
要求使用生成器生成1~10行的杨辉三角。 提示:把每一行当作一个list。
1 | def triangles(max): |
这段代码非常短,但是已经充分实现了题目要求,值得欣赏!
1 | for L in triangles(6): |
代码里面有两个窍门,一是列表相加,注意不是列表元素相加。 列表相加相当于把后一个列表的元素全部append到前一个列表。如:
1 | 1,2] L = [ |
上面代码中的b即把每一行当作一个list,因为每一行的开头结尾都是1,所以可以每一行的list看作三个list的相加,一头一尾两个list是只有1个元素1的list,中间的list用列表生成式生成。
另一个窍门就是这里的列表生成式。 注意这里计算时还没赋值,引用列表b的内容是上一行的信息,所以能巧妙地借助上一行计算相邻两数之和,最终得到含有n-2项的中间列表。
补充解析一下代码执行的过程:
1 | b = [1], n = 0 |
迭代器
迭代器即Iterator, 前面说到可以通过collections模块的Iterable类型来判断一个对象是否可迭代对象。 这里引入Iterator的概念,可以通过类似的方式判断。
list,dict,str虽然都Iterable,却不是Iterator。 生成器都是Iterator。Iterator的特性允许对象通过next()函数不断返回下一个值。
1 | from collections import Iterator |
要把list,dict,str变为Iterator可以使用 iter()
函数:
1 | isinstance(iter([]), Iterator) |
Python的Iterator对象表示的其实是一个数据流,Iterator对象可以被 next()
函数调用并不断返回下一个数据,直到没有数据时抛出 StopIteration
错误。
可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过 next()
函数实现按需计算下一个数据,所以 Iterator
的计算是惰性的,只有在需要返回下一个数据时它才会计算,也因此能够节省空间。
Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。
小结
凡是可作用于for循环的对象都是 Iterable
类型;
凡是可作用于 next()
函数的对象都是 Iterator
类型,它们表示一个惰性计算的序列;
集合数据类型如list、dict、str等是 Iterable
但不是 Iterator
,不过可以通过 iter()
函数获得一个 Iterator
对象。
Python的for循环本质上就是通过不断调用 next()
函数实现的,例如:
1 | for x in [1, 2, 3, 4, 5]: |
实际上完全等价于:
1 | # 首先获得Iterator对象: |