列表(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
5def calc(*numbers):
sum = 0
for n in numbers:
sum = sum + n * n
return sum
参数前加了个
*
号。调用函数时可以传入任意个参数。
1 | >>> nums = [1, 2, 3] |
在list或tuple前面加一个*号,把list或tuple的元素变成可变参数传进去
关键字参数/可变参数
1 | def func(a, b, c=0, *args, **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
4def 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
7def 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
2for n in g:
... print n
另一种方法:yield
1
2
3
4
5
6def 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 | >>> for n in fib(6): |
在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
4def 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
8def is_su(n):
if n>1:
for i in range(2,n):
if n%i == 0:
return False
return True
else:
return False
1 | >>> filter(is_su, range(1,100)) |
sorted()
可以对list进行排序(默认从小到大)
如果按自定义排序,可自定义一个函数作为参数传入。1
2
3
4
5
6def reversed_cmp(x, y):
if x > y:
return -1
if x < y:
return 1
return 0
1 | >>> sorted([36, 5, 12, 9, 21], reversed_cmp) |
返回函数
用函数作为对象,需要调用才能返回处理结果。
【闭包】1
2
3
4
5
6
7
8
9def 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 | ... def f(j): |
匿名函数
lambda1
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
5def 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
6def 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
9import 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.py1
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
4try:
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
4try:
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_install
和pip
比如安装一个第三方库处理图像工具库——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
6from __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
4from __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
8class 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 | bart = Student('Bart Simpson', 59) |
给对象发消息实际上就是调用对象对应的关联函数,我们称之为对象的方法
类和实例
创建类class Student(object)
,(object)表示该类是从哪个类继承下来的,通常如果没有合适的继承类,就使用object
类,这是所有类最终都会继承的类。
创建实例是通过类名+()
实现的:1
2
3
4
5
6
7
8class 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
5class 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
如果允许修改score1
2
3
4
5class Student(object):
...
def set_score(self, score):
self.__score = score
实用性:在方法中,可以对参数做检查,避免传入无效的参数
1 | class Student(object): |
【附】其实不能直接访问
__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
4def 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
13class 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
13class 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
30class 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。
为了更好地看出继承关系,我们把Runnable
和Flyabl
改为RunnableMixin
和FlyableMixin
。类似的,你还可以定义出肉食动物CarnivorousMixin
和植食动物HerbivoresMixin
,让某个动物同时拥有好几个Mixin:1
2class 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
12class 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
6class 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
21class 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
8class 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
6class Student(object):
def __init__(self, name):
self.name = name
def __call__(self):
print('My name is %s.' % self.name)
1 | >>> s = Student('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个参数:
- class的名称;
- 继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的单元素写法;
- 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 >>> 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
9try:
print 'try...'
r = 10 / 0
print 'result:', r
except ZeroDivisionError, e:
print 'except:', e
finally:
print 'finally...'
print 'END'
上面的代码在计算10 / 0时会产生一个除法运算错误:1
2
3
4try...
except: integer division or modulo by zero
finally...
END
调用堆栈
如果错误没有被捕获,它就会一直往上抛,最后被Python解释器捕获,打印一个错误信息,然后程序退出。
记录错误
如果不捕获错误,自然可以让Python解释器来打印出错误堆栈,但程序也被结束了。既然我们能捕获错误,就可以把错误堆栈打印出来,然后分析错误原因,同时,让程序继续执行下去。
Python内置的logging模块可以非常容易地记录错误信息:
1 | # err.py |
同样是出错,但程序打印完错误信息后会继续执行,并正常退出: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
10class 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
4try:
10 / 0
except ZeroDivisionError:
raise ValueError('input error!')
但是,决不应该把一个IOError转换成毫不相干的ValueError。
调试
logging
它允许你指定记录信息的级别,有debug
,info
,warning
,error
等几个级别。
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 | class Dict(dict): |
1 | >>> d = Dict(a=1, b=2) |
测试
引入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
32import 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
2with open('/path/to/file', 'r') as f:
print f.read()
调用read()会一次性读取文件的全部内容,如果文件有10G,内存就爆了,所以,要保险起见,可以反复调用
read(size)
方法,每次最多读取size个字节的内容。调用readline()
可以每次读取一行内容,调用readlines()一次读取所有内容并按行返回list。
如果文件很小,read()一次性读取最方便;如果不能确定文件大小,反复调用read(size)比较保险;如果是配置文件,调用readlines()最方便1
2for 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
3import 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
2with 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 | # 对文件重命名: |
列出当前目录下的所有目录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和pickle1
2
3
4try:
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—JSON1
2
3
4>>> import json
>>> d = dict(name='Bob', age=20, score=88)
>>> json.dumps(d)
'{"age": 20, "score": 88, "name": "Bob"}'
JSON—Python1
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
7class 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
:如果要启动大量的子进程,可以用进程池的方式批量创建子进程
进程间通信是通过Queue
、Pipes
等实现的。
多线程
进程是由若干线程组成的,一个进程至少有一个线程。
Python的标准库提供了两个模块:thread
和threading
,thread是低级模块,threading是高级模块,对thread进行了封装。
启用一个线程就是把一个特函数传入并创建Thread
实例,然后调用start()开始执行。Python的threading
模块有个current_thread()函数,它永远返回当前线程的实例。
Lock
由于锁只有一个,无论多少线程,同一时刻最多只有一个线程持有该锁,所以,不会造成修改的冲突。
创建一个锁就是通过threading.Lock()来实现:1
2
3
4
5
6
7
8
9
10
11
12
13balance = 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内建的一个集合模块,提供了许多有用的集合类。
namedtuplenamedtuple
是一个函数,它用来创建一个自定义的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的一种子类。
dequelist
是线性存储,数据量大的时候,插入和删除效率很低。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
3for 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 | >>> for x in itertools.imap(lambda x, y: x * y, [10, 20, 30], itertools.count(1)): |
以[10, 20, 30]序列为准,
imap()
返回一个迭代对象,而map()返回list。
这说明imap()实现了“惰性计算”,也就是在需要获得结果的时候才计算。类似imap()这样能够实现惰性计算的函数就可以处理无限序列。
ifilter()
就是filter()
的惰性实现。
XML
DOM vs SAX
HTMLParser
编写一个搜索引擎,第一步是用爬虫把目标网站的页面抓下来,第二步就是解析该HTML页面,看看里面的内容到底是新闻、图片还是视频。
Python提供了HTMLParser来非常方便地解析HTML1
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
33from 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 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
8from 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支持有smtplib
和email
两个模块,email负责构造邮件,smtplib负责发送邮件。
纯文本邮件:1
2from email.mime.text import MIMEText
msg = MIMEText('hello, send by Python...', 'plain', 'utf-8')
第一个参数就是邮件正文,第二个参数MIME的subtype,传入’plain’表示纯文本,最终的MIME就是‘text/plain‘ ,最后一定要用utf-8编码保证多语言兼容性。
1 | from email import encoders |
发送HTML邮件1
2
3msg = 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