常用模块
urllib2 :用于发送网络请求,获取数据
(Pyhton2中的urllib2工具包,在Python3中分拆成了urllib.request和urllib.error两个包。)
json:用于解析获得的数据
模块安装
在cmd下执行:python -m pip install 模块名
pip升级
升级命令: python -m pip install --upgrade pip
urllib2 :用于发送网络请求,获取数据
import urllib2
web = urllib2.urlopen('http://www.baidu.com')
content = web.read()
print content
json:解析json数据
如:
{
"weatherinfo": {
"city": "南京",
"cityid": "101190101",
"temp1": "37℃",
"temp2": "28℃",
"weather": "多云",
"img1": "d1.gif",
"img2": "n1.gif",
"ptime": "11:00"
}
}
可以看出,它像是一个字典的结构,但是有两层。最外层只有一个key--“weatherinfo”,它的value是另一个字典,里面包含了好几项天气信息。
虽然看上去像字典,但它对于程序来说,仍然是一个字符串,只不过是一个满足json格式的字符串。用python中json模块提供的loads方法,把它转成一个真正的字典。
import json
data = json.loads(content)
data已经是一个字典,在控制台中输出它,看上去和content没什么区别,只是编码上有些不同:
{u'weatherinfo': {u'city': u'u5357u4eac', u'ptime': u'11:00', u'cityid': u'101190101', u'temp2': u'28u2103', u'temp1': u'37u2103', u'weather': u'u591au4e91', u'img2': u'n1.gif', u'img1': u'd1.gif'}}
面向对象
类的属性:域(变量,包括类变量和属性变量)和方法
python是一种高度面向对象的语言,它其中的所有东西其实都是对象。比如,字符串即是一个对象
dir()方法可以查看一个类/变量的所有属性:s='a' ;dir(s);
关键字class用于创建一个类。pass语句表示一个空的代码块。类名()表示创建对象。
class MyClass:
pass
mc = MyClass()
print mc
调用类变量的方法是“对象.变量名”。
注意:类方法和函数的区别在于,类方法第一个参数必须为self。而在调用类方法的时候,通过“对象.方法名()”格式进行调用,而不需要额外提供self这个参数的值。self在类方法中的值,就是你调用的这个对象本身。
class MyClass:
name = 'Sam'
def sayHi(self):
print 'Hello %s' % self.name
mc = MyClass()
print mc.name
mc.name = 'Lily'
mc.sayHi()
类的继承
class Vehicle: ----------基类
def __init__(self, speed): -----__init__是python内置方法(函数名前后有__),在类创建时会自动调用以初始化类,其参数要在创建类时提供。
self.speed = speed
def drive(self, distance):
print 'need %f hour(s)' % (distance / self.speed)
class Bike(Vehicle):-----------类名后的括号内参数代表父类(继承关系)
pass ------不需要有额外的功能
class Car(Vehicle):
def __init__(self, speed, fuel):-----------扩展自己的初始化函数
Vehicle.__init__(self, speed)----------首先调用父类的初始化方法,注意此处要加上self
self.fuel = fuel
def drive(self, distance):
Vehicle.drive(self, distance)
print 'need %f fuels' % (distance * self.fuel)
b = Bike(15.0)--------初始化类时的参数是__init__的参数
c = Car(80.0, 0.012)
b.drive(100.0)
c.drive(100.0)
and-or 技巧
看下面这段代码:
a = "heaven"
b = "hell"
c = True and a or b
print c
d = False and a or b
print d
输出:
heaven
hell
bool and a or b语句中,当bool条件为真时,结果是a;当bool条件为假时,结果是b。这和c/c++等语言的bool?a:b表达式很像。
因此,一个if-else语句表述的逻辑:
if a > 0:
print "big"
else:
print "small"
就可以直接写成:print (a > 0) and "big" or "small"
但是,和c语言中的?:表达式不同,这里的and or语句是利用了python中的逻辑运算实现的。当a本身是个假值(如0,"")时,结果就不同了:
a = ""
b = "hell"
c = True and a or b
print c
得到的结果不是""而是"hell"。因为""和"hell"做or的结果是"hell"。
所以,and-or真正的技巧在于,确保a的值不会为假。最常用的方式是使 a 成为 [a] 、 b 成为 [b],然后使用返回值列表的第一个元素:
a = ""
b = "hell"
c = (True and [a] or [b])[0]
print c
由于[a]是一个非空列表,所以它决不会为假。即使a是0或者''或者其它假值,列表[a]也为真,因为它有一个元素。
在两个常量值进行选择时,and-or会让你的代码更简单。常用于lambda表达式。
python自带数学运算模块 math
import math
math包里有两个常量:
1)math.pi 圆周率π:3.141592...
2)math.e 自然常数:2.718281...
数值运算:
math.ceil(x) 对x向上取整,比如x=1.2,返回2.0(py3返回2)
math.floor(x) 对x向下取整,比如x=1.2,返回1.0(py3返回1)
math.pow(x,y) 指数运算,得到x的y次方
math.log(x) 对数,默认基底为e。可以使用第二个参数,来改变对数的基底。比如math.log(100, 10)
math.sqrt(x) 平方根
math.fabs(x) 绝对值
三角函数:
math.sin(x)
math.cos(x)
math.tan(x)
math.asin(x)
math.acos(x)
math.atan(x)
注意:这里的x是以弧度为单位,所以计算角度的话,需要先换算
角度和弧度互换:
math.degrees(x) 弧度转角度
math.radians(x) 角度转弧度
正则表达式
正则表达式就是记录文本规则的代码。python中的正则表达式库,所做的事情是利用正则表达式来搜索文本。或者用来判断输入的文本是否符合规范,或进行分类
示例:
import re
text = "Hi, I am Shirley Hilton. I am his wife."
m = re.findall(r"hi", text)
if m:
print m
else:
print 'not match'
re模块
re是python里的正则表达式模块。findall是其中一个方法,用来按照提供的正则表达式,去匹配文本中的所有符合条件的字符串。返回结果是一个包含所有匹配的list。
规则:
“”表示单词的开头或结尾,空格、标点、换行都算是单词的分割。“”自身又不会匹配任何字符,它代表的只是一个位置。所以单词前后的空格标点之类不会出现在结果里。
B
B - 匹配不是单词开头或结束的位置
[]
[]表示满足括号中任一字符,比如“[hi]”,它就不是匹配“hi”了,而是匹配“h”或者“i”。如果把正则表达式改为“[Hh]i”,就可以既匹配“Hi”,又匹配“hi”了。
r
r,是raw的意思,它表示对字符串不进行转义。\也表示不转义,但是如果有很多个\会显得比较乱,这样直接在字符串前加个r就能解决。
>>> print "hi"
hi
>>> print r"hi"
hi
>>> print "\bhi"
hi
.
“.”在正则表达式中表示除换行符以外的任意字符。
S
“S”表示任意非空白字符。注意是大写字符S。
s
s - 匹配任意的空白符,小写s,与大写S相反
^
1)、^匹配字符串的开始位置。^1d*x?表示以1开头的数字,结尾可能有x
2)、^与[]合用,表示非。[a]的反义是[^a],表示除a以外的任意字符。[^abcd]就是除abcd以外的任意字符。
$
$ - 匹配字符串的结束
?
“?”表示任意一个字符
*
“*”表示其前面字符重复任意次数(0个或多个)。“*”不是表示字符,而是表示数量:它表示前面的字符可以重复任意多次(包括0次),只要满足这样的条件,都会被表达式匹配上。
+
与*类似,+表示1个或多个
?
表示重复0次或1次
{n,}
重复n次或更多次
{n,m}
重复n到m次
d
d表示数字,即等价于【0-9】。表示任意长度的数字,可以用[0-9]*或者d*。
D
D - 匹配任意非数字的字符
{} {数字}
比如11位的数字:d{11}
w
w - 匹配字母或数字或下划线或汉字(3.x版本可以匹配汉字,但2.x版本不可以)
W
W - 匹配任意不是字母,数字,下划线,汉字的字符
“|”
“|”相当于python中“or”的作用,它连接的两个表达式,只要满足其中之一,就会被算作匹配成功。使用“|”时,要特别提醒注意的是不同条件之间的顺序。匹配时,会按照从左往右的顺序,一旦匹配成功就停止验证后面的规则。
正则表达式不只是用来从一大段文字中抓取信息,很多时候也被用来判断输入的文本是否符合规范,或进行分类。例子:
^w{4,12}$
这个表示一段4到12位的字符,包括字母或数字或下划线或汉字,可以用来作为用户注册时检测用户名的规则。(但汉字在python2.x里面可能会有问题)
d{15,18}
表示15到18位的数字,可以用来检测身份证号码
^1d*x?
以1开头的一串数字,数字结尾有字母x,也可以没有。有的话就带上x。
转义字符
如果我们确实要匹配.或者*字符本身,而不是要它们所代表的元字符,那就需要用.或*。本身也需要用\。
比如"d+.d+"可以匹配出123.456这样的结果。
random模块
from random import randint
randint(1, 10)
随机数
random模块的作用是产生随机数。
random.randint(a, b)可以生成一个a到b间的随机整数,包括a和b。
a、b都必须是整数,且必须b≥a。当等于的时候,比如:random.randint(3, 3)的结果就永远是3
random.random()生成一个0到1之间的随机浮点数,包括0但不包括1,也就是[0.0, 1.0)。
random.uniform(a, b)生成a、b之间的随机浮点数。不过与randint不同的是,a、b无需是整数,也不用考虑大小。
random.uniform(1.5, 3),random.uniform(3, 1.5),这两种参数都是可行的。random.uniform(1.5, 1.5)永远得到1.5。
random.choice(seq)从序列中随机选取一个元素。seq需要是一个序列,比如list、元组、字符串。
random.choice([1, 2, 3, 5, 8, 13]) #list
random.choice('hello') #字符串
random.choice(['hello', 'world']) #字符串组成的list
random.choice((1, 2, 3)) #元组
random.randrange(start, stop, step)生成一个从start到stop(不包括stop),间隔为step的一个随机数。start、stop、step都要为整数,且start<stop。start和step都可以不提供参数,默认是从0开始,间隔为1。但如果需要指定step,则必须指定start。
比如:
random.randrange(1, 9, 2)
就是从[1, 3, 5, 7]中随机选取一个。
start和step都可以不提供参数,默认是从0开始,间隔为1。但如果需要指定step,则必须指定start。
random.randrange(4) #[0, 1, 2, 3]
random.randrange(1, 4) #[1, 2, 3]
random.randrange(start, stop, step)其实在效果上等同于random.choice(range(start, stop, step))
random.sample(population, k)从population序列中,随机获取k个元素,生成一个新序列。sample不改变原来序列。
random.shuffle(x)把序列x中的元素顺序打乱。shuffle直接改变原有的序列。
random.seed(x) 设置生成随机数用的整数起始值。调用任何其他random模块函数之前调用这个函数。
参数x 是下一个随机数的种子。如果省略,则需要系统时间,以产生下一个随机数。
time模块提供了一些与时间相关的方法。利用time,可以简单地计算出程序运行的时间。对于一些比较复杂、耗时较多的程序,可以通过这种方法了解程序中哪里是效率的瓶颈,从而有针对性地进行优化。
计时
在计算机领域有一个特殊的时间,叫做epoch,它表示的时间是1970-01-01 00:00:00 UTC。
time模块的一个方法time.time(),返回的就是从epoch到当前的秒数(不考虑闰秒)。这个值被称为unix时间戳。
用这个方法得到程序开始和结束所用的时间,进而算出运行的时间:
import time
starttime = time.time()
print 'start:%f' % starttime
for i in range(10):
print i
endtime = time.time()
print 'end:%f' % endtime
print 'total time:%f' % (endtime-starttime)
time.sleep(secs),可以让程序暂停secs秒。
在抓取网页的时候,适当让程序sleep一下,可以减少短时间内的请求,提高请求的成功率。
Python Shell
一般来说,有两种运行 python 代码的方法:
1. 使用交互式的带提示符的解释器
2. 使用源文件
第一种方法,所谓“交互式的带提示符的解释器”,也被称做 python shell。当你安装好 python,并正确配置系统变量 PATH 后(linux 和 mac 上通常都预装并配置好了 python),在命令行里输入 python,会看到诸如以下的提示:
$ python
Python 2.7.5 (default, Aug 25 2013, 00:04:04)
[GCC 4.2.1 Compatible Apple LLVM 5.0 (clang-500.0.68)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>
python shell 里写好的代码也很难保存(至少我目前还不知道有什么可行的方法)。所以一般并不会用它来“真正地”写代码。当你需要写一个相对完整的 python 程序时,你需要写在一个“源文件”中。这就是运行 python 的第二种方法。
用一个文本编辑器新建一个文件,在里面输入:print "hello world"
保存这个文件为 hello.py。注意,不要命名为 print.py。不要以任何 python 的内置方法或者你会使用到的模块名来命名你自己的代码文件。
然后在命令行中,进入到这个文件所在的文件夹,输入 python hello.py。你会看到:
$python hello.py
hello world
$
这时候不会进入 python shell,而是直接输出了程序的结果。换句话说,python 执行了写在源文件 hello.py 中的代码。
python 自带了一个叫做 IDLE 的编辑器。如果要编辑源文件,则需要在菜单栏中选择 File -> New File。这时打开的新窗口就是源文件窗口。在里面写好你的 python 代码后,点击菜单栏上的 Run -> Run Module,即可执行
对象的序列化与反序列化 :pickle模块
pickle可以把任何 Python 对象存储在文件中,再把它原样取出来。
例:
import pickle
test_data = ['Save me!', 123.456, True]
f = file('test.data', 'w')
pickle.dump(test_data, f)
f.close()
这样就把 test_data 这个 list 存储在了文件 test.data 中。你可以用文本编辑器打开 test.data 查看里面的内容:
(lp0
S'Save me!'
p1
aF123.456
aI01
a.
取对象:
import pickle
f = file('test.data')
test_data = pickle.load(f)
f.close()
print test_data
控制台的输出:['Save me!', 123.456, True],和存储前的数据是一致的。
如果要保存多个对象,一种方法是把这些对象先全部放在一个序列中,在对这个序列进行存储:
a = 123
b = "hello"
c = 0.618
data = (a, b, c)
...
pickle.dump(data, f)
另一种方法就是依次保存和提取:
...
pickle.dump(a, f)
pickle.dump(b, f)
pickle.dump(c, f)
...
x = pickle.load(f)
y = pickle.load(f)
z = pickle.load(f)
dump 方法可以增加一个可选的参数,来指定用二进制来存储:pickle.dump(data, f, True)
而 load 方法会自动检测数据是二进制还是文本格式,无需手动指定。
【特别说明】python3中,通过pickle对数据进行存储时,必须用二进制(b)模式读写文件。
Python 还提供了另一个模块 cPickle,它的功能及用法和 pickle 模块完全相同,只不过它是用C语言编写的,因此要快得多(比pickle快1000倍)。
列表表达式List Comprehension
所谓列表表达式(也有翻译成列表解析列表综合),就是通过一个已有的列表生成一个新的列表。例:
假设有一个由数字组成的 list,现在需要把其中的偶数项取出来,组成一个新的 list。一种比较“正常”的方法是:
list_1 = [1, 2, 3, 5, 8, 13, 22]
list_2 = []
for i in list_1:
if i % 2 == 0:
list_2.append(i)
print list_2
输出:[2, 8, 22]
使用列表解析实现同样的效果:
list_1 = [1, 2, 3, 5, 8, 13, 22]
list_2 = [i for i in list_1 if i % 2 == 0]
print list_2
输出:[2, 8, 22]
[i for i in list_1] 会把 list_1 中的每一个元素都取出来,构成一个新的列表。
如果需要对其中的元素进行筛选,就在后面加上判断条件 if。所以 [i for i in list_1 if i % 2 == 0] 就是把 list_1 中满足 i % 2 == 0 的元素取出来组成新列表。
在构建新列表时,还可以对取出的元素做操作。比如,对于原列表中的偶数项,取出后要除以2,则可以通过 [i / 2 for i in list_1 if i % 2 == 0] 来实现。输出为 [1, 4, 11]。
lambda 表达式
lambda 表达可以被看做是一种匿名函数。可以快速定义一个极度简单的单行函数。
lambda 表达式的语法格式:lambda 参数列表: 表达式
定义 lambda 表达式时,参数列表周围没有括号,返回值前没有 return 关键字,也没有函数名称。
它的写法比 def 更加简洁。但是,它的主体只能是一个表达式,不可以是代码块,甚至不能是命令(print 不能用在 lambda 表达式中)。所以 lambda 表达式能表达的逻辑很有限。
lambda 表达式创建了一个函数对象,可以把这个对象赋值给一个变量进行调用。
譬如这样一个实现三个数相加的函数:
def sum(a, b, c):
return a + b + c
调用:
print sum(1, 2, 3)
print sum(4, 5, 6)
如果使用 lambda 表达式来实现:
sum = lambda a, b, c: a + b + c
调用:
print sum(1, 2, 3)
print sum(4, 5, 6)
看一个复杂一点的例子,把 lambda 表达式用在 def 函数定义中:
def fn(x):
return lambda y: x + y
调用:
a = fn(2)
print a(3)
输出:
5
fn 函数的返回值是一个 lambda 表达式,也就等于是一个函数对象。当以参数2来调用 fn 时,得到的结果就是:
lambda y: 2 + y
a = fn(2) 就相当于:a = lambda y: 2 + y
所以 a(3) 的结果就是5。
任何可以使用 lambda 表达式的地方,都可以通过普通的 def 函数定义来替代。在一些需要重复使用同一函数的地方,def 可以避免重复定义函数。而对于像 filter、sort 这种需要内嵌函数的方法,lambda 表达式就会显得比较合适。
变量的作用域
当函数内部定义了一个变量,无论是作为函数的形参,或是另外定义的变量,它都只在这个函数的内部起作用,这样在函数内部定义的变量被称为“局部变量”。这个函数体就是这个变量的作用域。函数外即使有和它名称相同的变量,也没有什么关联。
如:
def func(x):
print 'X in the beginning of func(x): ', x
x = 2
print 'X in the end of func(x): ', x
调用:
x = 50
func(x)
print 'X after calling func(x): ', x
输出:
X in the beginning of func(x): 50
X in the end of func(x): 2
X after calling func(x): 50
如果期望在函数 内部改变外部变量的值,有两个方法:
1、在函数内部用return把改变后的变量值作为函数返回值传递出来,再赋值给变量。
return x
x = func(x)
2、使用全局变量global
在 Python 的函数定义中,可以给变量名前加上 global 关键字,这样其作用域就不再局限在函数块中,而是全局的作用域。
例:
def func():
global x
print 'X in the beginning of func(x): ', x
x = 2
print 'X in the end of func(x): ', x
调用:
x = 50
func()
print 'X after calling func(x): ', x
输出:
X in the beginning of func(x): 50
X in the end of func(x): 2
X after calling func(x): 2
建议在写代码的过程中,显式地通过 global 来使用全局变量,避免在函数中直接使用外部变量。
比如:
def func():
print 'X in the beginning of func(x): ', x
# x = 2
print 'X in the end of func(x): ', x
调用:
x = 50
func()
print 'X after calling func(x): ', x
输出:
X in the beginning of func(x): 50
X in the end of func(x): 50
X after calling func(x): 50
程序可以正常运行。虽然没有指明 global,函数内部还是使用到了外部定义的变量。然而一旦加上x = 2这句,程序就会报错。因为这时候,x 成为一个局部变量,它的作用域从定义处开始,到函数体末尾结束。
map 函数
来看两个问题:
1. 假设有一个数列,如何把其中每一个元素都翻倍?
2. 假设有两个数列,如何求和?
第一个问题,普通程序员大概会这么写:
lst_1 = [1,2,3,4,5,6]
lst_2 = []
for item in lst_1:
lst_2.append(item * 2)
print lst_2
Python 程序员大概会这么写:
lst_1 = [1,2,3,4,5,6]
lst_2 = [i * 2 for i in lst_1]
print lst_2
也可以用map来实现:
lst_1 = (1,2,3,4,5,6) #数据改为了元组
lst_2 = map(lambda x: x * 2, lst_1) #函数用 lambda 表达式替代
print lst_2
map 是 Python 自带的内置函数,它的作用是把一个函数应用在一个(或多个)序列上,把列表中的每一项作为函数输入进行计算,再把计算的结果以列表的形式返回。
map 的第一个参数是一个函数,之后的参数是序列,可以是 list、tuple。如:
lst_1 = [1,2,3,4,5,6]
def double_func(x):
return x * 2
lst_2 = map(double_func, lst_1)
print lst_2
map 中的函数可以对多个序列进行操作。最开始提出的第二个问题,除了通常的 for 循环写法,如果用列表综合的方法比较难实现,但用 map 就比较方便:
lst_1 = [1,2,3,4,5,6]
lst_2 = [1,3,5,7,9,11]
lst_3 = map(lambda x, y: x + y, lst_1, lst_2)
print lst_3
map 中的函数会从对应的列表中依次取出元素,作为参数使用,同样将结果以列表的形式返回。所以要注意的是,函数的参数个数要与 map 中提供的序列组数相同,即函数有几个参数,就得有几组数据。
对于每组数据中的元素个数,如果有某组数据少于其他组,map 会以 None 来补全这组参数。
此外,当 map 中的函数为 None 时,结果将会直接返回参数组成的列表。如果只有一组序列,会返回元素相同的列表,如果有多组数列,将会返回每组数列中,对应元素构成的元组所组成的列表。如:
lst_1 = [1,2,3,4,5,6]
lst_2 = [1,3,5,7,9,11]
lst_3 = map(None, lst_1)
print lst_3
lst_4 = map(None, lst_1, lst_2)
print lst_4
结果:
[1, 2, 3, 4, 5, 6]
[(1, 1), (2, 3), (3, 5), (4, 7), (5, 9), (6, 11)]
reduce 函数
map 可以看作是把一个序列根据某种规则,映射到另一个序列。reduce 做的事情就是把一个序列根据某种规则,归纳为一个输出。
例:求1累加到100的和,
寻常的做法大概是这样:
sum = 0
for i in xrange(1, 101):
sum += i
print sum
如果用 reduce 函数,就可以写成:
lst = xrange(1, 101)
def add(x, y):
return x + y
print reduce(add, lst)
函数:reduce(function, iterable[, initializer])
第一个参数是作用在序列上的方法,第二个参数是被作用的序列,这与 map 一致。另外有一个可选参数,是初始值。
function 需要是一个接收2个参数,并有返回值的函数。它会从序列 iterable 里从左到右依次取出元素,进行计算。每次计算的结果,会作为下次计算的第一个参数。
提供初始值 initializer 时,它会作为第一次计算的第一个参数。否则,就先计算序列中的前两个值。
如果把刚才的 lst 换成 [1,2,3,4,5],那 reduce(add, lst) 就相当于 ((((1+2)+3)+4)+5)。
同样,可以用 lambda 函数:reduce((lambda x, y: x + y), xrange(1, 101))
在对于一个序列进行某种统计操作的时候,比如求和,或者诸如统计序列中元素的出现个数等,可以选择使用 reduce 来实现。相对可以使代码更简洁。
Python3 里,reduce已经被移出内置函数,使用 reduce 需要先通过 from functools import reduce 引入。
多线程
python 里有一个 thread 模块,其中提供了一个函数:
start_new_thread(function, args[, kwargs])
function 是开发者定义的线程函数,args 是传递给线程函数的参数,必须是tuple类型,kwargs 是可选参数。
调用 start_new_thread 之后,会创建一个新的线程,来执行 function 函数。而代码原本的主线程将继续往下执行,不再等待 function 的返回。通常情况,线程在 function 执行完毕后结束。
例:用 python 编写“爬虫”程序,抓取网上的数据。通过豆瓣的 API 抓取 30 部影片的信息:
单线程写法:
import urllib, time
time_start = time.time()
data = []
for i in range(30):
print 'request movie:', i
id = 1764796 + i
url = 'https://api.douban.com/v2/movie/subject/%d' % id
d = urllib.urlopen(url).read()
data.append(d)
print i, time.time() - time_start
print 'data:', len(data)
参考输出结果:用了 time.time() 来计算抓取花费的时间。运行一遍,大约需要十几秒
> python test.py
request movie: 0
0 0.741228103638
request movie: 1
1 1.96586918831
...
request movie: 28
28 12.0225770473
request movie: 29
29 12.4063940048
data: 30
多线程写法:
import urllib, time, thread
def get_content(i):
id = 1764796 + i
url = 'https://api.douban.com/v2/movie/subject/%d' % id
d = urllib.urlopen(url).read()
data.append(d)
print i, time.time() - time_start
print 'data:', len(data)
time_start = time.time()
data = []
for i in range(30):
print 'request movie:', i
thread.start_new_thread(get_content, (i,))
raw_input('press ENTER to exit... ') #因为主线程不在等待函数返回结果,所以在代码最后,增加了 raw_input,避免程序提前退出。
参考输出结果:
> python test.py
request movie: 0
request movie: 1
...
request movie: 28
request movie: 29
press ENTER to exit...
1 0.39500784874
data: 1
9 0.428859949112
data: 2
...
data: 28
21 1.03756284714
data: 29
8 2.66121602058
data: 30
从输出结果可以看出:
-
在程序刚开始运行时,已经发送所有请求
-
收到的请求并不是按发送顺序,先收到就先显示
-
总共用时两秒多
-
data 里同样记录了所有30条结果
对于这种耗时长,但又独立的任务,使用多线程可以大大提高运行效率。但在代码层面,可能额外需要做一些处理,保证结果正确。如上例中,如果需要电影信息按 id 排列,就要另行排序。
多线程通常会用在网络收发数据、文件读写、用户交互等待之类的操作上,以避免程序阻塞,提升用户体验或提高执行效率。
多线程的实现方法不止这一种。另外多线程也会带来一些单线程程序中不会出现的问题。这里只是简单地开个头。