Python学习笔记-高级特性

Py笔记大部分来自于:廖雪峰的BLOG
非常适合初学者的系列教程!
写的太好了!简直无可挑剔,果然大牛~

切片

取一个list或tuple的部分元素是非常常见的操作,用一般的方法处理比如循环啦是非常繁琐的,因此,Python提供了切片(Slice)操作符,能大大简化这种操作。

1
2
3
4
5
6
7
8
9
>>> L = ['Michael', 'Sarah', 'Tracy', 'Bob', 'Jack']

# 取前三个元素
>>> L[0:3]
['Michael', 'Sarah', 'Tracy']

# 如果从0开始可以省略
>>> L[:3]
['Michael', 'Sarah', 'Tracy']

L[0:3]表示,从索引0开始取,直到索引3为止,但不包括索引3,正好是3个元素。
类似的,既然Python支持L[-1]取倒数第一个元素,那么它同样支持倒数切片

1
2
3
4
>>> L[-2:]
['Bob', 'Jack']
>>> L[-2:-1]
['Bob']

还可以支持第三个参数,用于隔数取,如 L[0:3:2]隔两个取一个
tuple也是一种list,唯一区别是tuple不可变。因此,tuple也可以用切片操作,只是操作的结果仍是tuple
字符串'xxx'也可以看成是一种list,每个元素就是一个字符。因此,字符串也可以用切片操作,只是操作结果仍是字符串,所以Python没有针对字符串的截取函数,只需要切片一个操作就可以完成。

迭代

如果给定一个list或tuple,我们可以通过for循环来遍历这个list或tuple,这种遍历我们称为迭代(Iteration)。
在Python中,迭代是通过for ... in来完成的,而很多语言比如C或者Java,迭代list是通过下标完成的
Python中的for…in可以迭代任何可迭代的对象,无论是否具有下标
如何判断是否可以迭代呢?方法是通过collections模块的Iterable类型判断:

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

如果要对list实现类似Java那样的下标循环怎么办?Python内置的enumerate函数可以把一个list变成索引-元素对,这样就可以在for循环中同时迭代索引和元素本身:

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

在for里使用两个变量也是很常见的,例如

1
2
3
4
5
6
>>> for x, y in [(1, 1), (2, 4), (3, 9)]:
... print(x, y)
...
1 1
2 4
3 9

列表生成式

举个例子,要生成list [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]可以用list(range(1, 11))
但如果要生成[1x1, 2x2, 3x3, ..., 10x10]怎么做?方法一是循环:

1
2
3
4
5
6
>>> L = []
>>> for x in range(1, 11):
... L.append(x * x)
...
>>> L
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

但是循环太繁琐,而列表生成式则可以用一行语句代替循环生成上面的list:

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

写列表生成式时,把要生成的元素x * x放到前面,后面跟for循环,就可以把list创建出来,十分有用,多写几次,很快就可以熟悉这种语法。

for循环后面还可以加上if判断,这样我们就可以筛选出仅偶数的平方:

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

还可以使用两层循环,可以生成全排列:

1
2
>>> [m + n for m in 'ABC' for n in 'XYZ']
['AX', 'AY', 'AZ', 'BX', 'BY', 'BZ', 'CX', 'CY', 'CZ']

生成器

通过列表生成式,我们可以直接创建一个列表。但是,受到内存限制,列表容量肯定是有限的。而且,创建一个包含100万个元素的列表,不仅占用很大的存储空间,如果我们仅仅需要访问前面几个元素,那后面绝大多数元素占用的空间都白白浪费了。
所以,如果列表元素可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素呢?这样就不必创建完整的list,从而节省大量的空间。在Python中,这种一边循环一边计算的机制,称为生成器:generator。
要创建一个generator,有很多种方法。第一种方法很简单,只要把一个列表生成式的[]改成(),就创建了一个generator:

1
2
3
>>> g = (x * x for x in range(10))
>>> g
<generator object <genexpr> at 0x1022ef630>

可以通过next()函数获得generator的下一个返回值

1
2
3
4
>>> next(g)
0
>>> next(g)
1

generator保存的是算法,每次调用next(g),就计算出g的下一个元素的值,直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。
但是这种不断调用next(g)实在是太变态了,正确的方法是使用for循环,因为generator也是可迭代对象
generator非常强大。如果推算的算法比较复杂,用类似列表生成式的for循环无法实现的时候,还可以用函数来实现。
比如,著名的斐波拉契数列(Fibonacci),除第一个和第二个数外,任意一个数都可由前两个数相加得到:

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

斐波拉契数列用列表生成式写不出来,但是,用函数把它打印出来却很容易:

1
2
3
4
5
6
7
def fib(max):
n, a, b = 0, 0, 1
while n < max:
print(b)
a, b = b, a + b
n = n + 1
return 'done'

注意,赋值语句:a, b = b, a + b相当于:

1
2
3
t = (b, a + b) # t是一个tuple
a = t[0]
b = t[1]

仔细观察,可以看出,fib函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。
也就是说,上面的函数和generator仅一步之遥。要把fib函数变成generator,只需要把print(b)改为yield b就可以了:

1
2
3
4
5
6
7
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
这里,最难理解的就是generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。
需要注意的是:但是用for循环调用generator时,拿不到generator的return语句的返回值。需要捕获错误才行。

迭代器

可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator。需要注意的是可迭代对象和迭代器是完全不同的两个东西,如果要把Iterable(可迭代对象)变成Iterator(迭代器)可以使用iter()函数,当然也可以用isinstance来进行检测

1
2
3
4
>>> isinstance(iter([]), Iterator)
True
>>> isinstance(iter('abc'), Iterator)
True

你可能会问,为什么list、dict、str等数据类型不是Iterator(迭代器)?
这是因为Python的Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。

Iterator甚至可以表示一个无限大的数据流,例如全体自然数。而使用list是永远不可能存储全体自然数的。
凡是可作用于for循环的对象都是Iterable类型;
凡是可作用于next()函数的对象都是Iterator类型,它们表示一个惰性计算的序列;
Python的for循环本质上就是通过不断调用next()函数实现的;

一些方法的补充

items相关

方法把dict对象转换成了包含tuple的list,我们对这个list进行迭代,可以同时获得key和value 类似方法 iteritems()
需要注意的是因为字典是无序的,所以用items方法返回字典的所有项,也是没有顺序的。还有它是占额外的内存的

1
2
3
>>> d = {'x':'a','y':'b','z':'c'}
>>> d.items()
dict_items([('z', 'c'), ('x', 'a'), ('y', 'b')])

至于iteritems方法:与items方法相比作用大致相同,只是它的返回值不是列表,而是一个迭代器。不占额外内存
在很多情况下使用iteritems更高效(尤其是想要迭代结果的情况下)。

重要:

stackoverflow上这样一个问题:dict.items()和dict.iteritems()有什么区别? ,第一个答案大致的意思是这样的:
“起初 items() 就是返回一个像上面那样的包含dict所有元素的list,但是由于这样太浪费内存,所以后来就加入了(注:在Python 2.2开始出现的)iteritems(), iterkeys(), itervalues()这一组函数,用于返回一个 iterator 来节省内存,但是在 3.x 里items() 本身就返回这样的 iterator,所以在 3.x 里items() 的行为和 2.x 的 iteritems() 行为一致,iteritems()这一组函数就废除了。”
不过更加有意思的是,这个答案虽然被采纳,下面的评论却指出,这种说法并不准确,在 3.x 里 items() 的行为和 2.x 的 iteritems() 不一样,它实际上返回的是一个”full sequence-protocol object”,这个对象能够反映出 dict 的变化,后来在 Python 2.7 里面也加入了另外一个函数 viewitems() 和 3.x 的这种行为保持一致

viewitems和iteritems有什么区别呢,viewitems() 返回的是view object,它可以反映出 dictionary 的变化,就是说当字典变化后依然可以进行遍历

总结起来,在 2.x 里面,最初是 items() 这个方法,但是由于太浪费内存,所以加入了 iteritems() 方法,用于返回一个 iterator,在 3.x 里面将 items() 的行为修改成返回一个 view object,让它返回的对象同样也可以反映出原 dictionary 的变化,同时在 2.7 里面又加入了 viewitems() 向下兼容这个特性。
所以在 3.x 里面不需要再去纠结于三者的不同之处,因为只保留了一个 items() 方法。

文:http://blog.csdn.net/revilwang/article/details/38686635

喜欢就请我吃包辣条吧!

评论框加载失败,无法访问 Disqus

你可能需要魔法上网~~