Python:基础知识(二)


常用模块

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'\u5357\u4eac', u'ptime': u'11:00', u'cityid': u'101190101', u'temp2': u'28\u2103', u'temp1': u'37\u2103', u'weather': u'\u591a\u4e91', 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表示单词的开头或结尾,空格、标点、换行都算是单词的分割。“\b”自身又不会匹配任何字符,它代表的只是一个位置。所以单词前后的空格标点之类不会出现在结果里。

\B

\B - 匹配不是单词开头或结束的位置

[]

[]表示满足括号中任一字符,比如“[hi]”,它就不是匹配“hi”了,而是匹配“h”或者“i”。如果把正则表达式改为“[Hh]i”,就可以既匹配“Hi”,又匹配“hi”了。

r  

r,是raw的意思,它表示对字符串不进行转义。\\也表示不转义,但是如果有很多个\\会显得比较乱,这样直接在字符串前加个r就能解决。

>>> print "\bhi"

hi

>>> print r"\bhi"

\bhi

>>> print "\\bhi" 

\bhi

.

“.”在正则表达式中表示除换行符以外的任意字符

\S

“\S”表示任意非空白字符。注意是大写字符S。

\s

\s - 匹配任意的空白符,小写s,与大写S相反

 1)、^匹配字符串的开始位置。^1\d*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位的数字,可以用来检测身份证号码

^1\d*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...\n') #因为主线程不在等待函数返回结果,所以在代码最后,增加了 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 排列,就要另行排序。

多线程通常会用在网络收发数据、文件读写、用户交互等待之类的操作上,以避免程序阻塞,提升用户体验或提高执行效率。

多线程的实现方法不止这一种。另外多线程也会带来一些单线程程序中不会出现的问题。这里只是简单地开个头。

 


 

posted @ 2018-07-22 16:51  闲汉  阅读(976)  评论(0编辑  收藏  举报