flyEn'blog

寥雪峰python2.7学习笔记

廖雪峰 python2.7
python2.7 内置函数

列表(list):可变,可添加删除或者重新赋值。
元组(tuple):不可变。
字典(dict):使用键-值(key-value)存储,具有极快的查找速度。

set可以看成数学意义上的无序和无重复元素的集合。两个set可以做数学意义上的交集、并集等操作。

1
2
3
4
5
6
>>> s1 = set([1, 2, 3])
>>> s2 = set([2, 3, 4])
>>> s1 & s2
set([2, 3])
>>> s1 | s2
set([1, 2, 3, 4])

定义可变参数

1
2
3
4
5
def calc(*numbers):
sum = 0
for n in numbers:
sum = sum + n * n
return sum

参数前加了个*号。调用函数时可以传入任意个参数。

1
2
3
>>> nums = [1, 2, 3]
>>> calc(*nums)
14

在list或tuple前面加一个*号,把list或tuple的元素变成可变参数传进去

关键字参数/可变参数

1
2
def func(a, b, c=0, *args, **kw):
print 'a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw

args是可变参数,args接收的是一个tuple/list;
**kw是关键字参数,kw接收的是一个dict。
必选参数a/b、默认参数(c=0)、可变参数(
args)和关键字参数(**kw)。顺序不可乱。

1
2
3
4
>>> func(1, 2)
a = 1 b = 2 c = 0 args = () kw = {}
>>> func(1, 2, 3, 'a', 'b', x=99)
a = 1 b = 2 c = 3 args = ('a', 'b') kw = {'x': 99}

对于任意函数,都可以通过类似func(args, *kw)的形式调用它,无论它的参数是如何定义的。

以及调用函数时如何传入可变参数和关键字参数的语法:
可变参数既可以直接传入:func(1, 2, 3),又可以先组装list或tuple,再通过*args传入:func(*(1, 2, 3))
关键字参数既可以直接传入:func(a=1, b=2),又可以先组装dict,再通过**kw传入:func(**{'a': 1, 'b': 2})

递归函数

一个函数在内部调用自身本身

1
2
3
4
def fact(n):
if n==1:
return 1
return n * fact(n - 1)

即fact(n) = n! = 1 x 2 x 3 x … x (n-1) x n = (n-1)! x n = fact(n-1) x n
函数返回引入了乘法表达式所以并不是尾递归。

【注】使用递归函数需要注意防止栈溢出。
解决方法:尾递归(指在函数返回时候调用自身本身)。并且return语句不能包含表达式,这样编译器或者解释器就可以把尾递归做优化,使递归本身无论调用多少次,都只占用一个栈帧,不会出现栈溢出的情况。

改成:

1
2
3
4
5
6
7
def fact(n):
return fact_iter(n, 1)

def fact_iter(num, product):
if num == 1:
return product
return fact_iter(num - 1, num * product)

返回递归函数本身,num - 1和num * product在函数调用前就会被计算,不影响函数调用。

【注】不过Python标准的解释器没有针对尾递归做优化,任何递归函数都存在栈溢出的问题。

高级特性

但是在Python中,代码不是越多越好,而是越少越好。代码不是越复杂越好,而是越简单越好。

1.切片

略。

2.迭代(Iterable)

for ... in ...
只要是可迭代对象都可以迭代。
比如dict,

1
2
3
4
5
6
7
>> d = {'a': 1, 'b': 2, 'c': 3}
>>> for key in d:
... print key
...
a
c
b

字典的存储不是按照list顺序排列的,所以迭代的结果顺序可能不一样。

【注】默认情况下,dict迭代的是key,如果要迭代value,可以用for value in d.itervalues(),如果要同时迭代key和value,可以用for k, v in d.iteritems()

判断对象是否可迭代:

1
2
3
>>> from collections import Iterable
>>> isinstance('abc', Iterable) # str是否可迭代
True

如果要对list实现下标循环:enumerate函数可把list变成索引-元素对

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

3.列表生成式

如果要生成list[1x1, 2x2, 3x3, ..., 10x10]可用列表生成式:

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

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']

也可使用两个变量来生成list:

1
2
3
>>> d = {'x': 'A', 'y': 'B', 'z': 'C' }
>>> [k + '=' + v for k, v in d.iteritems()]
['y=B', 'x=A', 'z=C']

4.生成器(generator)

generator也是一个可迭代对象。
创建一个generator,
第一种方法:把一个列表生成式的[]改成()

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

打印元素:1.g.next()方法。2.使用for循环。

1
2
for n in g:
... print n

另一种方法:yield

1
2
3
4
5
6
def fib(max):
n, a, b = 0, 0, 1
while n < max:
yield b
a, b = b, a + b
n = n + 1

斐波拉契数列: 1, 1, 2, 3, 5, 8, 13, 21, 34, …
用列表生成式无法实现,但用函数很容易。

1
2
>>> fib(6)
<generator object fib at 0x104feaaa0>

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

在Python中,可以简单地把列表生成式改成generator,也可以通过函数实现复杂逻辑的generator。

函数式编程

map()
map()函数接收两个参数,一个是函数,一个是序列。map将传入的函数依次作用到序列的每个元素,并把结果作为新的list返回。

1
2
3
4
5
>>> def f(x):
... return x * x

>>> map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])
[1, 4, 9, 16, 25, 36, 49, 64, 81]

reduce()
reduce把一个函数作用在一个序列[x1, x2, x3…]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算,即

1
reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)

例如

1
2
3
4
5
>>> def fn(x, y):
... return x * 10 + y
...
>>> reduce(fn, [1, 3, 5, 7, 9])
13579

filter
map()不同的时,filter()把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。

1
2
3
4
def is_odd(n):
return n % 2 == 1

filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15])

【练】
请尝试用filter()删除1~100的素数。

1
2
3
4
5
6
7
8
def is_su(n):
if n>1:
for i in range(2,n):
if n%i == 0:
return False
return True
else:
return False

1
2
>>> filter(is_su, range(1,100))
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]

sorted()
可以对list进行排序(默认从小到大)
如果按自定义排序,可自定义一个函数作为参数传入。

1
2
3
4
5
6
def reversed_cmp(x, y):
if x > y:
return -1
if x < y:
return 1
return 0

1
2
>>> sorted([36, 5, 12, 9, 21], reversed_cmp)
[36, 21, 12, 9, 5]
返回函数

用函数作为对象,需要调用才能返回处理结果。
【闭包】

1
2
3
4
5
6
7
8
9
def count():
fs = []
for i in range(1, 4):
def f():
return i*i
fs.append(f)
return fs

f1, f2, f3 = count()

返回的函数引用了变量i,但它并非立刻执行。等到函数都返回时引用的变量i已经变成了3,所以f1(),f2(),f3()返回结果都为9。
返回闭包时牢记的一点就是:返回函数不要引用任何循环变量,或者后续会发生变化的变量。
如果一定要引用循环变量,方法就是中间再创建一个函数。

1
2
3
4
...         def f(j):
... def g():
... return j*j
... return g
匿名函数

lambda

1
2
>>> map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9])
[1, 4, 9, 16, 25, 36, 49, 64, 81]

匿名函数:lambda x: x * x
关键字lambda表示匿名函数,冒号前面的x表示函数参数。只能有一个表达式,不用写return,返回值就是该表达式的结果。

装饰器

在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。
比如,在函数调用前后自动打印日志,但又不希望修改now()函数的定义:

1
2
3
4
5
def log(func):
def wrapper(*args, **kw):
print 'call %s():' % func.__name__
return func(*args, **kw)
return wrapper

decorator就是一个返回函数的高阶函数。

log接受一个函数作为参数,并返回一个函数。

1
2
3
@log
def now():
print '2013-12-25'

调用now()函数,不仅会运行函数本身,还会在运行now()前打印出一行日志。

1
2
3
>>> now()
call now():
2013-12-25

如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数:

1
2
3
4
5
6
def log(text):
def decorator(func):
def wrapper(*args, **kw):
......
return wrapper
return decorator

运用:@log('execute')
由于函数也是对象,它有__name__等属性,但经过decorator装饰后的函数,它们的__name__已经从原来的now变成了wrapper,因为返回的wrapper()函数名字就是’wrapper’,所以需要把原始函数的__name__等属性复制到wrapper()函数。python内置的functools.wraps就是干这个事的。

1
2
3
4
5
6
7
8
9
import functools

def log(text):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
....
return wrapper
return decorator

偏函数

int还提供base参数,可做n进制的转换:

1
2
3
4
>>> int('12345', base=8)  # 八进制的12345转换为十进制
5349
>>> int('12345', 16)
74565

创建一个偏函数functools.partial,不需要自己定义

1
2
3
>>> int2 = functools.partial(int, base=2)
>>> int2('1000000')
64

作用:把一个函数的某些参数设置默认值,返回一个新的函数。
创建偏函数实际上可以接受函数对象、*args**kw这3个参数。

模块

1
import sys

导入了sys模块后,就有了变量sys指向该模块。
例:hello.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/usr/bin/env python
# -*- coding: utf-8 -*-

' a test module ' # 表示模块的文档注释

__author__ = 'Michael Liao' # 作者

import sys

def test():
args = sys.argv
if len(args)==1:
print 'Hello, world!'
elif len(args)==2:
print 'Hello, %s!' % args[1]
else:
print 'Too many arguments!'

if __name__=='__main__':
test()

sys.argv:sys模块有一个argv变量,用list存储了命令行的所有参数。argv第一个参数永远时该.py文件的名称。
最后两行:当我们在命令行运行hello模块文件时,Python解释器把一个特殊变量__name__置为__main__,而如果在其他地方导入该hello模块时,if判断将失败,因此,这种if测试可以让一个模块通过命令行运行时执行一些额外的代码,最常见的就是运行测试。

测试结果:

1
2
3
4
$ python hello.py
Hello, world!
$ python hello.py Michael
Hello, Michael!

别名
比如Python标准库一般会提供StringIO和cStringIO两个库,这两个库的接口和功能是一样的,但是cStringIO是C写的,速度更快

1
2
3
4
try:
import cStringIO as StringIO
except ImportError: # 导入失败会捕获到ImportError
import StringIO

这样就可以优先导入cStringIO。如果有些平台不提供cStringIO,还可以降级使用StringIO。导入cStringIO时,用import … as …指定了别名StringIO,因此,后续代码引用StringIO即可正常工作。

还有类似simplejson这样的库,在Python 2.6之前是独立的第三方库,从2.6开始内置,所以,会有这样的写法:

1
2
3
4
try:
import json # python >= 2.6
except ImportError:
import simplejson as json # python <= 2.5

作用域
在一个模块中,有的函数和变量我们希望给别人使用,有的函数和变量我们希望仅仅在模块内部使用。在Python中,是通过_前缀来实现的。
类似__xxx__这样的变量时特殊变量,可以被直接引用,但是有特殊用途,比如__author____name__就是特殊变量,文档注释也可以用特殊变量__doc__访问。
类似_xxx__xxx这样的函数或变量就是非公开的(private),不应该被直接引用,比如_abc__abc等。
【注】’不应该’而不是’不能’
作用:是一种非常有用的代码封装和抽象的方法,即外部不需要引用的函数全部定义成private,只有外部需要引用的函数才定义为public。

安装第三方模块
在Python中,安装第三方模块,是通过setuptools这个工具完成的。Python有两个封装了setuptools的包管理工具:easy_installpip
比如安装一个第三方库处理图像工具库——Python Imaging Library——pip install Pillow

视图加载一个模块时,python解释器会搜索当前目录、所有已安装的内置模块和第三方模块,搜索路径存放在sys模块的path变量中:

1
2
>>> import sys
>>> sys.path

如果要添加自己的搜索目录,一是直接修改sys.path,添加要搜索的目录,这种方法是在运行时修改,运行结束后失效。,二是设置环境变量PYTHONPATH

__future__
从Python 2.7到Python 3.x就有不兼容的一些改动,比如2.x里的字符串用’xxx’表示str,Unicode字符串用u’xxx’表示unicode,而在3.x中,所有字符串都被视为unicode,因此,写u’xxx’和’xxx’是完全一致的,而在2.x中以’xxx’表示的str就必须写成b’xxx’,以此表示“二进制字符串”。

Python提供了__future__模块,把下一个新版本的特性导入到当前版本,就可以在当前版本中测试一些新版本的特性。
例:

1
2
3
4
5
6
from __future__ import unicode_literals

print '\'xxx\' is unicode?', isinstance('xxx', unicode)
print 'u\'xxx\' is unicode?', isinstance(u'xxx', unicode)
print '\'xxx\' is str?', isinstance('xxx', str)
print 'b\'xxx\' is str?', isinstance(b'xxx', str)

再如python3.x中,所有的除法都是精确除法。地板除用//表示:

1
2
3
4
from __future__ import division

print '10 / 3 =', 10 / 3
print '10 // 3 =', 10 // 3

面向对象编程

而面向对象的程序设计把计算机程序视为一组对象的集合,而每个对象都可以接收其他对象发过来的消息,并处理这些消息,计算机程序的执行就是一系列消息在各个对象之间传递。

在Python中,所有数据类型都可以视为对象,当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类(Class)的概念。

面向对象的设计思想
类(Class)和实例(instance)的概念在自然界中很自然,class是一种抽象概念,比如定义class—student,是指学生这个概念,而instancce则是一个个具体的student。

所以,面向对象的设计思想是抽象出Class,根据Class创建Instance。

数据封装、继承和多态是面向对象的三大特点

例:
面向过程方式

1
2
3
4
5
6
# 处理学生的成绩表,调用函数打印出来
std1 = { 'name': 'Michael', 'score': 98 }
std2 = { 'name': 'Bob', 'score': 81 }

def print_score(std):
print '%s: %s' % (std['name'], std['score'])

面向对象方式

1
2
3
4
5
6
7
8
class Student(object):

def __init__(self, name, score):
self.name = name
self.score = score

def print_score(self):
print '%s: %s' % (self.name, self.score)

面向对象首选思考的不是程序的执行流程,而是Student这种数据类型应该被视为对象,这个对象拥有name和score这两个属性。然后,给对象发一个print_score消息,让对象自己把自己的数据打印出来。

1
2
bart = Student('Bart Simpson', 59)
bart.print_score()

给对象发消息实际上就是调用对象对应的关联函数,我们称之为对象的方法

类和实例

创建类
class Student(object),(object)表示该类是从哪个类继承下来的,通常如果没有合适的继承类,就使用object类,这是所有类最终都会继承的类。
创建实例是通过类名+()实现的:

1
2
3
4
5
6
7
8
class Student(object):
pass

>>> bart = Student()
>>> bart
<__main__.Student object at 0x10a67a590>
>>> Student
<class '__main__.Student'>

变量bart指向的就是一个Student的object,每个object的地址都不一样。而Student本身则是一个类。

通过定义一个特殊的__init__方法,在创建实例的时候,就把name,score等属性绑上去。

__init__方法的第一个参数永远是self,表示创建的实例本身,因此,在init方法内部,就可以把各种属性绑定到self,因为self就指向创建的实例本身。

有了__init__方法,在创建实例的时候就不能传入空的参数了,必须传入__init__方法匹配的参数。

1
2
3
>>> bart = Student('Bart Simpson', 59)
>>> bart.name
'Bart Simpson'

和普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量self,并且调用时不用传递该参数,除此与普通函数没什么区别,仍然可以用默认参数、可变参数和关键字参数。

数据封装
在Student类的内部定义访问数据的函数,把“数据”给封装起来,封装数据的函数是和Student类本身是关联起来的,称为类的方法。

调用可在实例变量上直接调用。数据和逻辑被“封装”,不用知道内部实现的细节。

1
2
>>> bart.print_score()
Bart Simpson: 59

类是创建实例的模板,而实例则是一个一个具体的对象,各个实例拥有的数据都互相独立,互不影响。

访问权限

在Class内部可以有属性和方法,而外部代码可以通过直接调用实例变量的方法来操作数据,这样就隐藏了内部的复杂逻辑。

如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,实例变量名如果以__开头,就变成了一个私有变量,只有内部可以访问。

1
2
3
4
5
class Student(object):

def __init__(self, name, score):
self.__name = name
self.__score = score

这样确保了外部代码不能随意修改对象内部的状态。
如果外部代码要获取name和score,可在类内部增加方法

1
2
3
4
5
6
7
8
class Student(object):
...

def get_name(self):
return self.__name

def get_score(self):
return self.__score

如果允许修改score

1
2
3
4
5
class Student(object):
...

def set_score(self, score):
self.__score = score

实用性:在方法中,可以对参数做检查,避免传入无效的参数

1
2
3
4
5
6
7
8
class Student(object):
...

def set_score(self, score):
if 0 <= score <= 100:
self.__score = score
else:
raise ValueError('bad score')

【附】其实不能直接访问__name是因为Python解释器对外把__name变量改成了_Student__name,所以,仍然可以通过_Student__name来访问__name变量。但因为不同版本的Python解释器可能会把__name改成不同的变量名。

继承和多态

当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),被继承的class称为基类、父类或超类(Base class、Super class)。

继承最大的好处是子类获得了父类的全部功能。

在继承关系中,如果一个实例的数据类型是某个子类,那它的数据类型也可以被看做是父类

获取对象信息

判断对象类型——type()
比较两个变量的type类型是否相同

1
2
3
4
5
6
7
>>> import types
>>> type('abc')==types.StringType
True
>>> type(u'abc')==types.UnicodeType
True
>>> type([])==types.ListType
True

对于class继承关系,判断class类型可以使用instance()函数

1
2
3
>>> h = Husky()
>>> isinstance(h, Husky)
True

获得一个对象的所有属性和方法,dir()函数

配合getattr()setattr()以及hasattr(),我们可以直接操作一个对象的状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>>> hasattr(obj, 'x') # 有属性'x'吗?
True
>>> hasattr(obj, 'y') # 有属性'y'吗?
False
>>> setattr(obj, 'y', 19) # 设置一个属性'y'
>>> hasattr(obj, 'y') # 有属性'y'吗?
True
>>> getattr(obj, 'y') # 获取属性'y'
19
>>> obj.y # 获取属性'y'
19
>>> getattr(obj, 'z', 404) # 获取属性'z',如果不存在,返回默认值404
404
>>> fn = getattr(obj, 'power')
>>>> fn() # 调用fn()与调用obj.power()是一样的
81

正确的用法例子:

1
2
3
4
def readImage(fp):
if hasattr(fp, 'read'):
return readData(fp)
return None

从文件流fp中读取图像,我们首先要判断该fp对象是否存在read方法,如果存在,则该对象是一个流,如果不存在,则无法读取。hasattr()就派上了用场。

面向对象高级编程

高级特性:多重继承、定制类、元类等。

使用__slots__
给实例绑定一个属性

1
2
3
4
5
6
7
>>> class Student(object):
... pass

>>> s = Student()
>>> s.name = 'Michael' # 动态给实例绑定一个属性
>>> print s.name
Michael

给实例绑定一个方法

1
2
3
4
5
6
7
8
>>> def set_age(self, age): # 定义一个函数作为实例方法
... self.age = age
...
>>> from types import MethodType
>>> s.set_age = MethodType(set_age, s, Student) # 给实例绑定一个方法
>>> s.set_age(25) # 调用实例方法
>>> s.age # 测试结果
25

但给实例绑定一个方法对另一个实例不起作用
为了给所有实例都绑定方法,可以给class绑定方法

1
>>> Student.set_score = MethodType(set_score, None, Student)

通常情况下,set_score方法可以直接定义在class中,但动态绑定允许我们在程序运行的过程中动态给class加上功能,这在静态语言中很难实现。

使用__slots__

限制实例添加属性。
只允许对Student实例添加name和age属性。

1
2
>>> class Student(object):
... __slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称

__slots__定义的属性仅对当前类起作用,对继承的子类是不起作用的

使用@property

Python内置的@property装饰器就是负责把一个方法变成属性调用的:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Student(object):

@property
def score(self):
return self._score

@score.setter # 把一个setter方法变成属性赋值
def score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100!')
self._score = value

可控的属性操作

1
2
3
4
>>> s = Student()
>>> s.score = 60 # OK,实际转化为s.set_score(60)
>>> s.score # OK,实际转化为s.get_score()
60

@property通过getter和setter方法来实现的
还可以定义只读属性,只定义getter方法,不定义setter方法就是一个只读属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Student(object):

@property
def birth(self):
return self._birth

@birth.setter
def birth(self, value):
self._birth = value

@property
def age(self):
return 2014 - self._birth

birth是可读写属性,而age就是一个只读属性,因为age可以根据birth和当前时间计算出来。

多重继承

比如要实现以下4种动物:

  • Dog - 狗狗
  • Bat - 蝙蝠
  • Parrot - 鹦鹉
  • Ostrich - 鸵鸟

主要的类层次按照哺乳类和鸟类设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class Runnable(object):
def run(self):
print('Running...')

class Flyable(object):
def fly(self):
print('Flying...')

class Animal(object):
pass

# 大类:
class Mammal(Animal):
pass

class Bird(Animal):
pass

# 各种动物:
class Dog(Mammal, Runnable):
pass

class Bat(Mammal, Flyable):
pass

class Parrot(Bird, Flyable):
pass

class Ostrich(Bird, Runnable):
pass

Mixin
在设计类的继承关系时,通常,主线都是单一继承下来的,但是如果需要“混入”额外的功能,通过多重继承就可以实现。这种设计通常称之为Mixin。

为了更好地看出继承关系,我们把RunnableFlyabl改为RunnableMixinFlyableMixin。类似的,你还可以定义出肉食动物CarnivorousMixin和植食动物HerbivoresMixin,让某个动物同时拥有好几个Mixin:

1
2
class Dog(Mammal, RunnableMixin, CarnivorousMixin):
pass

Mixin的目的就是给一个类增加多个功能

定制类

__len__():让class作用于len()函数
__str__:打印实例时返回自定义的内容
__repr__():返回程序开发者看到的字符串,为调试服务(如果直接敲变量不用print)

可直接定义:__repr__ = __str__

__iter__:如果一个类想被用于for...in循环,类似list或tuple那样,就必须实现一个__iter__()方法,该方法返回一个迭代对象,for循环会不断调用该迭代对象的next()方法拿到循环的下一个值。
以斐波那契数列为例:

1
2
3
4
5
6
7
8
9
10
11
12
class Fib(object):
def __init__(self):
self.a, self.b = 0, 1 # 初始化两个计数器a,b

def __iter__(self):
return self # 实例本身就是迭代对象,故返回自己

def next(self):
self.a, self.b = self.b, self.a + self.b # 计算下一个值
if self.a > 100000: # 退出循环的条件
raise StopIteration();
return self.a # 返回下一个值

__getitem__:如果要表现的像list那样按照下标取出元素,需要实现__getitem__()

1
2
3
4
5
6
class Fib(object):
def __getitem__(self, n):
a, b = 1, 1
for x in range(n):
a, b = b, a + b
return a

这样就可以按下标访问数列的任意一项了:

1
2
3
4
5
6
7
8
9
10
11
>>> f = Fib()
>>> f[0]
1
>>> f[1]
1
>>> f[2]
2
>>> f[3]
3
>>> f[10]
89

但list有个切片方法对于Fid却报错,因为__getitem__()传入的参数可能是一个int也可能是一个切片对象slice,所以要判断:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Fib(object):
def __getitem__(self, n):
if isinstance(n, int):
a, b = 1, 1
for x in range(n):
a, b = b, a + b
return a
if isinstance(n, slice):
start = n.start
stop = n.stop
a, b = 1, 1
L = []
for x in range(stop):
if x >= start:
L.append(a)
a, b = b, a + b
return L

>>> f = Fib()
>>> f[0:5]
[1, 1, 2, 3, 5]

__getattr__:正常情况下调用不存在的方法或属性时会报错,如果要避免,除了可以加上一个score属性外,Python还有另一个机制,那就是写一个__getattr__()方法,动态返回一个属性:

1
2
3
4
5
6
7
8
class Student(object):

def __init__(self):
self.name = 'Michael'

def __getattr__(self, attr):
if attr=='score':
return 99

返回函数也可以,只是调用时候要变成s.age()

__call__:在对象实例本身上调用方法,类似instance():

1
2
3
4
5
6
class Student(object):
def __init__(self, name):
self.name = name

def __call__(self):
print('My name is %s.' % self.name)

1
2
3
>>> s = Student('Michael')
>>> s()
My name is Michael.

__call__还可以定义参数,对实例进行直接调用就好比对一个函数进行调用一样,如果要判断一个对象是否能被调用,能被调用的对象就是一个Callable对象所以可以用callable()判断,比如函数和带有__call__的实例:

1
2
3
4
5
6
>>> callable(Student())
True
>>> callable(max)
True
>>> callable([1, 2, 3])
False

使用元类

type()函数可以查看一个类型或变量的类型

1
2
3
4
5
6
7
8
>>> from hello import Hello
>>> h = Hello()
>>> h.hello()
Hello, world.
>>> print(type(Hello))
<type 'type'>
>>> print(type(h))
<class 'hello.Hello'>

Hello是一个class,它的类型就是type,而h是一个实例,它的类型就是class Hello。

type()函数既可以返回一个对象的类型,又可以创建出新的类型:

1
2
3
4
>>> def fn(self, name='world'): # 先定义函数
... print('Hello, %s.' % name)
...
>>> Hello = type('Hello', (object,), dict(hello=fn)) # 创建Hello class

要创建一个class对象,type()函数依次传入3个参数:

  1. class的名称;
  2. 继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法;
  3. class的方法名称与函数绑定,这里我们把函数fn绑定到方法名hello上。

metaclass
除了使用type()动态创建类以外,要控制类的创建行为,还可以使用metaclass(元类)。
先定义metaclass,然后创建类,最后创建实例。
metaclass允许你创建类或者修改类。

1
2
3
4
5
6
7
8
# metaclass是创建类,所以必须从`type`类型派生:
class ListMetaclass(type):
def __new__(cls, name, bases, attrs):
attrs['add'] = lambda self, value: self.append(value)
return type.__new__(cls, name, bases, attrs)

class MyList(list):
__metaclass__ = ListMetaclass # 指示使用ListMetaclass来定制类

它指示Python解释器在创建MyList时,要通过ListMetaclass.__new__()来创建,在此我们可以修改类的定义,比如,加上新的方法,然后,返回修改后的定义。
__new__()方法接收到的参数依次是:

  1. 当前准备创建的类的对象;
  2. 类的名字;
  3. 类继承的父类集合;
  4. 类的方法集合。
    1
    2
    3
    4
    >>> L = MyList()
    >>> L.add(1)
    >>> L
    [1]

意义:需要通过metaclass修改类定义。比如ORM

ORM全称“Object Relational Mapping”,即对象-关系映射,就是把关系数据库的一行映射为一个对象,也就是一个类对应一个表,这样,写代码更简单,不用直接操作SQL语句。

编写一个ORM框架,所有的类都只能动态定义,因为只有使用者才能根据表的结构定义出对应的类来。尝试编写(另一篇)

错误处理

try...except...finally...

1
2
3
4
5
6
7
8
9
try:
print 'try...'
r = 10 / 0
print 'result:', r
except ZeroDivisionError, e:
print 'except:', e
finally:
print 'finally...'
print 'END'

上面的代码在计算10 / 0时会产生一个除法运算错误:

1
2
3
4
try...
except: integer division or modulo by zero
finally...
END

常见的错误类型

调用堆栈
如果错误没有被捕获,它就会一直往上抛,最后被Python解释器捕获,打印一个错误信息,然后程序退出。

记录错误
如果不捕获错误,自然可以让Python解释器来打印出错误堆栈,但程序也被结束了。既然我们能捕获错误,就可以把错误堆栈打印出来,然后分析错误原因,同时,让程序继续执行下去。
Python内置的logging模块可以非常容易地记录错误信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# err.py
import logging

def foo(s):
return 10 / int(s)

def bar(s):
return foo(s) * 2

def main():
try:
bar('0')
except StandardError, e:
logging.exception(e)

main()
print 'END'

同样是出错,但程序打印完错误信息后会继续执行,并正常退出:

1
2
3
4
5
6
7
8
9
10
11
$ python err.py
ERROR:root:integer division or modulo by zero
Traceback (most recent call last):
File "err.py", line 12, in main
bar('0')
File "err.py", line 8, in bar
return foo(s) * 2
File "err.py", line 5, in foo
return 10 / int(s)
ZeroDivisionError: integer division or modulo by zero
END

抛出错误
Python的内置函数会抛出很多类型的错误,我们自己编写的函数也可以抛出错误。

1
2
3
4
5
6
7
8
9
10
class FooError(StandardError):
pass

def foo(s):
n = int(s)
if n==0:
raise FooError('invalid value: %s' % s)
return 10 / n

foo(0)

raise语句如果不带参数,就会把当前错误原样抛出。此外,在except中raise一个Error,还可以把一种类型的错误转化成另一种类型:

1
2
3
4
try:
10 / 0
except ZeroDivisionError:
raise ValueError('input error!')

但是,决不应该把一个IOError转换成毫不相干的ValueError。

调试
logging
它允许你指定记录信息的级别,有debuginfowarningerror等几个级别。

pdb
让程序以单步方式运行,可以随时查看运行状态。

1
2
3
4
# err.py
s = '0'
n = int(s)
print 10 / n

然后启动:

1
2
3
$ python -m pdb err.py
> /Users/michael/Github/sicp/err.py(2)<module>()
-> s = '0'

输入命令l来查看代码:

1
2
3
4
5
6
(Pdb) l
1 # err.py
2 -> s = '0'
3 n = int(s)
4 print 10 / n
[EOF]

输入命令n可以单步执行代码:

1
2
3
4
5
6
(Pdb) n
> /Users/michael/Github/sicp/err.py(3)<module>()
-> n = int(s)
(Pdb) n
> /Users/michael/Github/sicp/err.py(4)<module>()
-> print 10 / n

任何时候都可以输入命令p 变量名来查看变量:

1
2
3
4
(Pdb) p s
'0'
(Pdb) p n
0

输入命令q结束调试,退出程序

pdb.set_trace()
这个方法也是用pdb,但是不需要单步执行,只需要import pdb,然后,在可能出错的地方放一个pdb.set_trace(),就可以设置一个断点:

1
2
3
4
5
6
7
# err.py
import pdb

s = '0'
n = int(s)
pdb.set_trace() # 运行到这里会自动暂停
print 10 / n

运行代码,程序会自动在pdb.set_trace()暂停并进入pdb调试环境,可以用命令p查看变量,或者用命令c继续运行

IDE

1
2
3
4
5
6
7
8
9
10
11
12
13
class Dict(dict):

def __init__(self, **kw):
super(Dict, self).__init__(**kw)

def __getattr__(self, key):
try:
return self[key]
except KeyError:
raise AttributeError(r"'Dict' object has no attribute '%s'" % key)

def __setattr__(self, key, value):
self[key] = value
1
2
3
4
5
6
>>> d = Dict(a=1, b=2)
>>> d['a']
1
>>> d.a # 查看
1
>>> d['b'] = 3 # 创建

测试
引入Python自带的unittest模块,编写mydict_test.py如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import unittest

from mydict import Dict

class TestDict(unittest.TestCase):

def test_init(self):
d = Dict(a=1, b='test')
self.assertEquals(d.a, 1)
self.assertEquals(d.b, 'test')
self.assertTrue(isinstance(d, dict))

def test_key(self):
d = Dict()
d['key'] = 'value'
self.assertEquals(d.key, 'value')

def test_attr(self):
d = Dict()
d.key = 'value'
self.assertTrue('key' in d)
self.assertEquals(d['key'], 'value')

def test_keyerror(self):
d = Dict()
with self.assertRaises(KeyError):
value = d['empty']

def test_attrerror(self):
d = Dict()
with self.assertRaises(AttributeError):
value = d.empty

IO编程

读写文件是最常见的IO操作。Python内置了读写文件的函数。

读写文件就是请求操作系统打开一个文件对象(通常称为文件描述符),然后,通过操作系统提供的接口从这个文件对象中读取数据(读文件),或者把数据写入这个文件对象(写文件)。

读文件
以读文件的模式打开一个文件对象

1
f = open('/Users/michael/test.txt', 'r')

如果文件打开成功,可调用read()方法一次读取文件的全部内容,python把文件读到内存,用一个str对象表示。

1
f.read()

关闭文件

1
f.close()

由于文件读写时都有可能产生IOError,一旦出错,后面的f.close()就不会调用。
无论是否出错都能正确地关闭文件try... finally

1
2
3
4
5
6
try:
f = open('/path/to/file', 'r')
print f.read()
finally:
if f:
f.close()

由于每次这样写太繁琐,所以Python引入了with语句来自动帮我们调用close()方法

1
2
with open('/path/to/file', 'r') as f:
print f.read()

调用read()会一次性读取文件的全部内容,如果文件有10G,内存就爆了,所以,要保险起见,可以反复调用read(size)方法,每次最多读取size个字节的内容。调用readline()可以每次读取一行内容,调用readlines()一次读取所有内容并按行返回list。

如果文件很小,read()一次性读取最方便;如果不能确定文件大小,反复调用read(size)比较保险;如果是配置文件,调用readlines()最方便

1
2
for line in f.readlines():
print(line.strip()) # 把末尾的'\n'删掉

字符编码
读取非ASCII编码的文本文件,就必须以二进制模式打开,再解码。比如GBK编码的文件:

1
2
3
4
5
6
>>> f = open('/Users/michael/gbk.txt', 'rb')
>>> u = f.read().decode('gbk')
>>> u
u'\u6d4b\u8bd5'
>>> print u
测试

Python提供了一个codecs模块帮我们在读文件时自动转换编码,直接读出unicode:

1
2
3
import codecs
with codecs.open('/Users/michael/gbk.txt', 'r', 'gbk') as f:
f.read() # u'\u6d4b\u8bd5'

写文件

1
2
3
>>> f = open('/Users/michael/test.txt', 'w')
>>> f.write('Hello, world!')
>>> f.close()

——>

1
2
with open('/Users/michael/test.txt', 'w') as f:
f.write('Hello, world!')

操作文件和目录

python的os模块可以直接调用操作系统提供的接口函数。

1
import os

在操作系统中定义的环境变量,全部保存在os.environ这个dict中。
要获取某个环境变量的值,可以调用os.getenv()函数。
操作文件和目录

1
2
3
4
5
6
7
8
9
10
11
# 查看当前目录的绝对路径:
>>> os.path.abspath('.')
'/Users/michael'
# 在某个目录下创建一个新目录,
# 首先把新目录的完整路径表示出来:
>>> os.path.join('/Users/michael', 'testdir')
'/Users/michael/testdir'
# 然后创建一个目录:
>>> os.mkdir('/Users/michael/testdir')
# 删掉一个目录:
>>> os.rmdir('/Users/michael/testdir')

os.path.split()拆分成目录或文件名

1
2
>>> os.path.split('/Users/michael/testdir/file.txt')
('/Users/michael/testdir', 'file.txt')

os.path.splitext()可以直接让你得到文件扩展名

1
2
>>> os.path.splitext('/path/to/file.txt')
('/path/to/file', '.txt')

1
2
3
4
# 对文件重命名:
>>> os.rename('test.txt', 'test.py')
# 删掉文件:
>>> os.remove('test.py')

列出当前目录下的所有目录

1
2
>>> [x for x in os.listdir('.') if os.path.isdir(x)]
>>> [x for x in os.listdir('.') if os.path.isfile(x) and os.path.splitext(x)[1]=='.py']

序列化
Python提供两个模块来实现序列化:cPickle和pickle

1
2
3
4
try:
import cPickle as pickle
except ImportError:
import pickle

把一个对象序列化并写入文件
pickle.dumps()方法把任意对象序列化为一个str,

1
2
3
4
>>> d = dict(name='Bob', age=20, score=88)
>>> f = open('dump.txt', 'wb')
>>> pickle.dump(d, f)
>>> f.close()

反序列化

1
2
3
4
5
>>> f = open('dump.txt', 'rb')
>>> d = pickle.load(f)
>>> f.close()
>>> d
{'age': 20, 'score': 88, 'name': 'Bob'}

JSON
Python—JSON

1
2
3
4
>>> import json
>>> d = dict(name='Bob', age=20, score=88)
>>> json.dumps(d)
'{"age": 20, "score": 88, "name": "Bob"}'

JSON—Python

1
2
3
>>> json_str = '{"age": 20, "score": 88, "name": "Bob"}'
>>> json.loads(json_str)
{u'age': 20, u'score': 88, u'name': u'Bob'}

JSON进阶

1
2
3
4
5
6
7
class Student(object):
def __init__(self, name, age, score):
self.name = name
self.age = age

s = Student('Bob', 20, 88)
print(json.dumps(s, default=lambda obj: obj.__dict__))

直接print(json.dumps(s))会报错,需要可选参数来定制JSON序列化。

进程和线程 (*)

多进程
Unix/Linux操作系统提供了一个fork()系统调用,普通的函数调用,调用一次,返回一次,但是fork()调用一次,返回两次。因为操作系统自动把当前进程(父进程)复制了一份(子进程),然后分别在父进程和子进程内返回。
子进程永远返回0,而父进程返回子进程的ID,理由是,一个父进程可以fork很多子进程,所以父进程要记下每个子进程的ID ,而子进程只需要调用getpid()就可以拿到父进程的ID。

multiprocessing:跨平台版本的多进程模块
Pool:如果要启动大量的子进程,可以用进程池的方式批量创建子进程
进程间通信是通过QueuePipes等实现的。

多线程
进程是由若干线程组成的,一个进程至少有一个线程。
Python的标准库提供了两个模块:threadthreading,thread是低级模块,threading是高级模块,对thread进行了封装。
启用一个线程就是把一个特函数传入并创建Thread实例,然后调用start()开始执行。Python的threading模块有个current_thread()函数,它永远返回当前线程的实例。

Lock
由于锁只有一个,无论多少线程,同一时刻最多只有一个线程持有该锁,所以,不会造成修改的冲突。
创建一个锁就是通过threading.Lock()来实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
balance = 0
lock = threading.Lock()

def run_thread(n):
for i in range(100000):
# 先要获取锁:
lock.acquire()
try:
# 放心地改吧:
change_it(n)
finally:
# 改完了一定要释放锁:
lock.release()

多核CPU
死循环线程会100%占用一个CPU。

但Python写个死循环,启动与CPU核心数量相同的N个线程,在4核CPU上可以监控到CPU占有率不到两核,如果C、C++或Java来改写相同的死循环,直接可以把全部核心跑满。
因为Python的线程虽然是真正的线程,但解释器执行代码时,有一个GIL锁:Global Interpreter Lock,任何Python线程执行前,必须先获得GIL锁,然后,每执行100条字节码,解释器就自动释放GIL锁,让别的线程有机会执行。这个GIL全局锁实际上把所有线程的执行代码都给上了锁,所以,多线程在Python中只能交替执行,即使100个线程跑在100核CPU上,也只能用到1个核。
Python虽然不能利用多线程实现多核任务,但可以通过多进程实现多核任务。多个Python进程有各自独立的GIL锁,互不影响。

ThreadLocal
多线程环境下,每个线程都有自己的数据。一个线程使用自己的局部变量比使用全局变量好,但局部变量也有问题,就是在函数调用的时候传递起来很麻烦。ThreadLocal可以解决。

计算密集型 vs. IO密集型

计算密集型任务由于主要消耗CPU资源,因此,代码运行效率至关重要。Python这样的脚本语言运行效率很低,完全不适合计算密集型任务。对于计算密集型任务,最好用C语言编写。

IO密集型任务,任务越多,CPU效率越高,但也有一个限度。常见的大部分任务都是IO密集型任务,比如Web应用。这类任务的特点是CPU消耗很少,任务的大部分时间都在等待IO操作完成(因为IO的速度远远低于CPU和内存的速度)。对于IO密集型任务,最合适的语言就是开发效率最高(代码量最少)的语言,脚本语言是首选,C语言最差。

异步IO
考虑到CPU和IO之间巨大的速度差异,一个任务在执行的过程中大部分时间都在等待IO操作,单进程单线程模型会导致别的任务无法并行执行,因此,我们才需要多进程模型或者多线程模型来支持多任务并发执行。

现代操作系统对IO操作已经做了巨大的改进,最大的特点就是支持异步IO。如果充分利用操作系统提供的异步IO支持,就可以用单进程单线程模型来执行多任务,这种全新的模型称为事件驱动模型,Nginx就是支持异步IO的Web服务器,它在单核CPU上采用单进程模型就可以高效地支持多任务。在多核CPU上,可以运行多个进程(数量与CPU核心数相同),充分利用多核CPU。由于系统总的进程数量十分有限,因此操作系统调度非常高效。用异步IO编程模型来实现多任务是一个主要的趋势。

对应到Python语言,单进程的异步编程模型称为协程,有了协程的支持,就可以基于事件驱动编写高效的多任务程序。我们会在后面讨论如何编写协程。

分布式进程

正则表达式

\d可以匹配一个数字,\w可以匹配一个字母或数字,.可以匹配任意字符,\d{3}表示匹配3个数字,\s可以匹配一个空格(也包括Tab等空白符),所以\s+表示至少有一个空格,\d{3,8}表示3-8个数字,匹配特殊字符需要\转义。

  • [0-9a-zA-Z\_]可以匹配一个数字、字母或者下划线;
  • [0-9a-zA-Z\_]+可以匹配至少由一个数字、字母或者下划线组成的字符串;
  • [a-zA-Z\_][0-9a-zA-Z\_]*可以匹配由字母或下划线开头,后接任意个由一个数字、字母或者下划线组成的字符串,也就是Python合法的变量;
  • [a-zA-Z\_][0-9a-zA-Z\_]{0, 19}更精确地限制了变量的长度是1-20个字符(前面1个字符+后面最多19个字符)。

A|B可以匹配A或B,所以(P|p)ython可以匹配’Python’或者’python’。
^表示行的开头,^\d表示必须以数字开头。
$表示行的结束,\d$表示必须以数字结束。

r前缀,可以不用考虑转义。
match()方法判断是否匹配,如果匹配成功,返回一个Match对象,否则返回None。

1
2
3
>>> import re
>>> re.match(r'^\d{3}\-\d{3,8}$', '010-12345')
<_sre.SRE_Match object at 0x1026e18b8>

切分字符串

1
2
>>> 'a b   c'.split(' ')
['a', 'b', '', '', 'c']

无法识别连续的空格,可以用正则

1
2
>>> re.split(r'\s+', 'a b   c')
['a', 'b', 'c']

r'[\s\,]+'以空格或者逗号切分
r'[\s\,\;]+'以空格逗号分号切分
等等。

分组
()可以分组,比如^(\d{3})-(\d{3,8})$分别定义了两个组,例如:

1
2
3
4
5
6
7
>>> m = re.match(r'^(\d{3})-(\d{3,8})$', '010-12345')
>>> m.group(0)
'010-12345'
>>> m.group(1)
'010'
>>> m.group(2)
'12345'

贪婪匹配
正则匹配默认是贪婪匹配,也就是匹配尽可能多的字符。
比如,匹配除数字后面的‘0’:

1
2
>>> re.match(r'^(\d+)(0*)$', '102300').groups()
('102300', '')

由于\d+采用贪婪匹配,直接把后面的0全部匹配了,结果0*只能匹配空字符串了。
加个?就可以让\d+采用非贪婪匹配

1
2
>>> re.match(r'^(\d+?)(0*)$', '102300').groups()
('1023', '00')

编译
如果一个正则表达式要重复使用多次,可以预编译。

1
2
3
>>> import re
>>> re_telephone = re.compile(r'^(\d{3})-(\d{3,8})$') # 编译
>>> re_telephone.match('010-12345').groups()

常用内建模块

collections

是python内建的一个集合模块,提供了许多有用的集合类。

namedtuple
namedtuple是一个函数,它用来创建一个自定义的tuple对象,并且规定了tuple元素的个数(即属性定义的个数),并可以用属性而不是索引来引用tuple的某个元素。

1
2
3
4
5
>>> from collections import namedtuple
>>> Point = namedtuple('Point', ['x', 'y'])
>>> p = Point(1, 2)
>>> p.x
1

创建的Point对象是tuple的一种子类。

deque
list是线性存储,数据量大的时候,插入和删除效率很低。deque是为了高效实现插入和删除操作的双向列表,适用于队列和栈。

defaultdict
使用dict时,如果引用的Key不存在,就会抛出KeyError。如果希望key不存在时,返回一个默认值,就可以用defaultdict

1
2
3
4
5
>>> from collections import defaultdict
>>> dd = defaultdict(lambda: 'N/A')
>>> dd['key1'] = 'abc'
>>> dd['key2'] # key2不存在,返回默认值
'N/A'

OrderedDict
dict作迭代时,会乱序,用OrderedDict可以保持key的顺序。

OrderedDict的Key会按照插入的顺序排列,不是Key本身排序

OrderedDict可以实现一个FIFO(先进先出)的dict,当容量超出限制时,先删除最早添加的Key(代码略)。

Counter
是一个简单的计数器,例如,统计字符出现的个数:

1
2
3
4
5
6
>>> c = Counter()
>>> for ch in 'programming':
... c[ch] = c[ch] + 1
...
>>> c
Counter({'g': 2, 'm': 2, 'r': 2, 'a': 1, 'i': 1, 'o': 1, 'n': 1, 'p': 1})

实际上也是dict的一个子类。

base64

Base64是一种用64个字符来表示任意二进制数据的方法。、

struct
hashlib

摘要算法(哈希算法、散列算法),它通过一个函数,把任意长度的数据转换为一个长度固定的数据串(通常用16进制的字符串表示)。

MD5是最常见的摘要算法,速度很快,生成结果是固定的128 bit字节,通常用一个32位的16进制字符串表示。

摘要算法应用
密码存储应该存储用户密码的摘要。

要确保存储的用户口令不是那些已经被计算出来的常用口令的MD5,这一方法通过对原始口令加一个复杂字符串来实现,俗称“加盐”;用户名唯一的话,就可以通过把登录名作为Salt的一部分来计算MD5,来实现相同口令的用户也存储不同的MD5。

itertools

Python的内建模块itertools提供了非常有用的用于操作迭代对象的函数。
“无限”迭代器:
count()

1
2
3
4
>>> import itertools
>>> natuals = itertools.count(1)
>>> for n in natuals:
... print n

上述代码会打印出自然数序列,根本停不下来,只能按Ctrl+C退出。

cycle()会把传入的一个序列无限重复下去
repeat()负责把一个元素无限重复下去,不过可提供第二个参数就可以限定重复次数
无限序列只有在for迭代时才会无限迭代下去。
takewhile()等函数根据条件判断来截取出一个有限的序列。

1
2
3
4
>>> natuals = itertools.count(1)
>>> ns = itertools.takewhile(lambda x: x <= 10, natuals)
>>> for n in ns:
... print n

chain()
可以把一组迭代对象串联起来

1
2
3
for c in itertools.chain('ABC', 'XYZ'):
print c
# 迭代效果:'A' 'B' 'C' 'X' 'Y' 'Z'

groupby()
groupby()把迭代器中相邻的重复元素挑出来放在一起

1
2
3
4
5
6
>>> for key, group in itertools.groupby('AAABBBCCAAA'):
... print key, list(group)
A ['A', 'A', 'A']
B ['B', 'B', 'B']
C ['C', 'C']
A ['A', 'A', 'A']

imap()
imap()和map()的区别在于,imap()可以作用于无穷序列,并且,如果两个序列的长度不一致,以短的那个为准。

1
2
3
4
5
>>> for x in itertools.imap(lambda x, y: x * y, [10, 20, 30], itertools.count(1)):
... print x
10
40
90

以[10, 20, 30]序列为准,imap()返回一个迭代对象,而map()返回list。

这说明imap()实现了“惰性计算”,也就是在需要获得结果的时候才计算。类似imap()这样能够实现惰性计算的函数就可以处理无限序列。

ifilter()
就是filter()的惰性实现。

XML

DOM vs SAX

HTMLParser

编写一个搜索引擎,第一步是用爬虫把目标网站的页面抓下来,第二步就是解析该HTML页面,看看里面的内容到底是新闻、图片还是视频。
Python提供了HTMLParser来非常方便地解析HTML

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
from html.parser import HTMLParser
from html.entities import name2codepoint

class MyHTMLParser(HTMLParser):

def handle_starttag(self, tag, attrs):
print('<%s>' % tag)

def handle_endtag(self, tag):
print('</%s>' % tag)

def handle_startendtag(self, tag, attrs):
print('<%s/>' % tag)

def handle_data(self, data):
print(data)

def handle_comment(self, data):
print('<!--', data, '-->')

def handle_entityref(self, name):
print('&%s;' % name)

def handle_charref(self, name):
print('&#%s;' % name)

parser = MyHTMLParser()
parser.feed('''<html>
<head></head>
<body>
<!-- test html parser -->
<p>Some <a href=\"#\">html</a> HTML&nbsp;tutorial...<br>END</p>
</body></html>''')

urllib
urllib提供了一系列用于操作URL的功能。
Get
urllib的request模块可以非常方便地抓取URL内容,也就是发送一个GET请求到指定的页面,然后返回HTTP的响应:
例如,对豆瓣的一个URLhttps://api.douban.com/v2/book/2129650进行抓取,并返回响应:

1
2
3
4
5
6
7
8
from urllib import request

with request.urlopen('https://api.douban.com/v2/book/2129650') as f:
data = f.read()
print('Status:', f.status, f.reason)
for k, v in f.getheaders():
print('%s: %s' % (k, v))
print('Data:', data.decode('utf-8'))

POST

常用第三方模块

PIL
PIL提供了操作图像的强大功能,可以通过简单的代码完成复杂的图像处理。(Pillow

virtualenv

就是用来为一个应用创建一套“隔离”的Python运行环境。

图形界面

Python支持多种图形界面的第三方库,包括:

  • Tk
  • wxWidgets
  • Qt
  • GTK

Python自带的库是支持Tk的Tkinter,无需安装任何包,就可以直接使用。

——GUI程序

网络编程

计算机网络就是把各个计算机连接到一起,让网络中的计算机可以互相通信。网络编程就是如何在程序中实现两台计算机的通信。网络通信是两台计算机上的两个进程之间的通信。比如,浏览器进程和新浪服务器上的某个Web服务进程在通信,而QQ进程是和腾讯的某个服务器上的某个进程在通信。

TCP/IP简介

电子邮件

SMTP发送邮件

SMTP是发送邮件的协议,Python内置对SMTP的支持,可以发送纯文本邮件、html邮件以及带附件的邮件。
Python对SMTP支持有smtplibemail两个模块,email负责构造邮件,smtplib负责发送邮件。

纯文本邮件:

1
2
from email.mime.text import MIMEText
msg = MIMEText('hello, send by Python...', 'plain', 'utf-8')

第一个参数就是邮件正文,第二个参数MIME的subtype,传入’plain’表示纯文本,最终的MIME就是‘text/plain‘ ,最后一定要用utf-8编码保证多语言兼容性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from email import encoders
from email.header import Header
from email.mime.text import MIMEText
from email.utils import parseaddr, formataddr

import smtplib

def _format_addr(s):
name, addr = parseaddr(s)
return formataddr((Header(name, 'utf-8').encode(), addr))

from_addr = input('From: ')
password = input('Password: ')
to_addr = input('To: ')
smtp_server = input('SMTP server: ')

msg = MIMEText('hello, send by Python...', 'plain', 'utf-8')
msg['From'] = _format_addr('Python爱好者 <%s>' % from_addr)
msg['To'] = _format_addr('管理员 <%s>' % to_addr)
msg['Subject'] = Header('来自SMTP的问候……', 'utf-8').encode()

server = smtplib.SMTP(smtp_server, 25)
server.set_debuglevel(1)
server.login(from_addr, password)
server.sendmail(from_addr, [to_addr], msg.as_string())
server.quit()

发送HTML邮件

1
2
3
msg = MIMEText('<html><body><h1>Hello</h1>' +
'<p>send by <a href="http://www.python.org">Python</a>...</p>' +
'</body></html>', 'html', 'utf-8')

发送附件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 邮件对象:
msg = MIMEMultipart()
msg['From'] = _format_addr('Python爱好者 <%s>' % from_addr)
msg['To'] = _format_addr('管理员 <%s>' % to_addr)
msg['Subject'] = Header('来自SMTP的问候……', 'utf-8').encode()

# 邮件正文是MIMEText:
msg.attach(MIMEText('send with file...', 'plain', 'utf-8'))

# 添加附件就是加上一个MIMEBase,从本地读取一个图片:
with open('/Users/michael/Downloads/test.png', 'rb') as f:
# 设置附件的MIME和文件名,这里是png类型:
mime = MIMEBase('image', 'png', filename='test.png')
# 加上必要的头信息:
mime.add_header('Content-Disposition', 'attachment', filename='test.png')
mime.add_header('Content-ID', '<0>')
mime.add_header('X-Attachment-Id', '0')
# 把附件的内容读进来:
mime.set_payload(f.read())
# 用Base64编码:
encoders.encode_base64(mime)
# 添加到MIMEMultipart:
msg.attach(mime)

POP3收取邮件

收取邮件就是编写一个MUA作为客户端,从MDA把邮件获取到用户的电脑或者手机上。收取邮件最常用的协议是POP协议,目前版本号是3,俗称POP3。
Python内置一个poplib模块,实现了POP3协议,可以直接用来收邮件。

所以,收取邮件分两步:
第一步:用poplib把邮件的原始文本下载到本地;
第二部:用email解析原始文本,还原为邮件对象。

通过POP3下载邮件
解析邮件

访问数据库

使用SQLite
使用MYSQL
使用SQLAlchemy

Web开发

异步IO

实战

Fork me on GitHub