大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。
Jetbrains全系列IDE稳定放心使用
from random import randint
num = randint(1, 100)
print('guess what I think?')
answer = int(input())
while(answer!=num):
if answer<num:
print('too small?')
elif answer>num:
print('too big?')
elif answer==num:
break
answer = int(input())
print('equal')
sum = 0
for _ in range(1,101) :
sum = sum + _
print(sum)
print("this is the\
same line")
print('''
"What's your name?" I asked.
"I'm Han Meimei."
''')
num = 18
print ('My age is %d' % num)
print ('Price is %.2f' % 4.6765)
for i in range(0, 5):
for j in range(0, i+1):
print ('*',end=' ')
print('\t')
print ('%s is easy to %d' % ('Python',100))
bool(None)
def sayHello(someone):
print( someone + ' say hello world!')
sayHello('bzz')
def plus(num1, num2):
print (num1+num2)
plus(1,5)
def isEqual(num1, num2):
if num1<num2:
print ('too small')
return False;
elif num1>num2:
print ('too big')
return False;
else :
print ('bingo')
return True
from random import randint
num = randint(1, 100)
print ('Guess what I think?')
bingo = False
while bingo == False:
answer = int(input())
bingo = isEqual(answer, num)
print(list(range(1,10)))
import numpy as np
a=np.arange(1,10)
print(a)
l = [365, 'everyday', 0.618, True]
print (l[1])
#修改list中的元素
l[0] = 123
print (l)
#向list中添加元素
l.append(1024)
print (l)
#删除list中的元素
del l[0]
print (l)
from random import choice
print ('Choose one side to shoot:')
print ('left, center, right')
you = input()
print ('You kicked ' + you)
direction = ['left', 'center', 'right']
com = choice(direction)
print ('Computer saved :' + com)
if you != com:
print ('Goal!')
else:
print ('Oops...')
切片的语法表达式为:[start_index : end_index : step],其中: start_index表示起始索引end_index表示结束索引 step表示步长,步长不能为0,且默认值为1 切片操作是指按照步长,截取从起始索引到结束索引,但不包含结束索引(也就是结束索引减1)的所有元素。
python3支持切片操作的数据类型有list、tuple、string、unicode、range 切片返回的结果类型与原对象类型一致 切片不会改变原对象,而是重新生成了一个新的对象
l = ['a', 'b', 'c', 'd', 'e', 'f']
print(l[-1]) # "-" 表示倒数
print(l[-4])
print(l[0:4])
print(l[1:])
print(l[:3])
print(l[:])
print(l[1:-1])
from random import choice
score_you = 0
score_com = 0
direction = ['left', 'center', 'right']
for i in range(5):
print ('==== Round %d - You Kick! ====' % (i+1))
print ('选一边射门')#Choose one side to shoot:
print ('left, center, right')
you = input()
print ('You kicked ' + you)
com = choice(direction)
print ('Computer saved ' + com)
if you != com:
print ('Goal!')
score_you += 1
else:
print ('Oops...')
print ('Score: %d(you) - %d(com)\n' % (score_you, score_com))
print ('==== Round %d - You Save! ====' % (i+1))
print ('选一边守门:')
print ('left, center, right')
you = input()
print ('You saved ' + you)
com = choice(direction)
print ('Computer kicked ' + com)
if you == com:
print ('Saved!')
else:
print ('Oops...')
score_com += 1
print ('Score: %d(you) - %d(com)\n' % (score_you, score_com))
sentence = 'I am an Englist sentence'
print(sentence.split())
# 指定参数分割
section = 'Hi! I am the one! Bye!'
print(section.split('!')) # ! 所有都被分割 空格也是
print('_a!a a .'.split('a'))
from random import choice score = [0, 0] direction = ['left', 'center', 'right'] def kick(): print ('==== You Kick! ====') print ('Choose one side to shoot:') print ('left, center, right') you = input() print ('You kicked ' + you) com = choice(direction) print ('Computer saved ' + com) if you != com: print ('Goal!') score[0] += 1 else: print ('Oops...') print ('Score: %d(you) - %d(com)\n' % (score[0], score[1])) print ('==== You Save! ====') print ('Choose one side to save:') print ('left, center, right') you = input() print ('You saved ' + you) com = choice(direction) print ('Computer kicked ' + com) if you == com: print ('Saved!') else: print ('Oops...') score[1] += 1 print ('Score: %d(you) - %d(com)\n' % (score[0], score[1])) for i in range(5): print ('==== Round %d ====' % (i+1)) kick() while(score[0] == score[1]): i += 1 print ('==== Round %d ====' % (i+1)) kick() if score[0] > score[1]: print( 'You Win!') else: print ('You Lose.')
- join和昨天说的split正好相反:split是把一个字符串分割成很多字符串组成的list,而join则是把一个list中的所有字符串连接成一个字符串。
- join的格式有些奇怪,它不是list的方法,而是字符串的方法。首先你需要有一个字符串作为list中所有元素的连接符,然后再调用这个连接符的join方法,join的参数是被连接的list:
s = ';' li = ['apple', 'pear', 'orange'] fruit = s.join(li) print (fruit) print(';'.join(['apple', 'pear', 'orange'])) print(''.join(['hello', 'world'])) # 无缝连接
word = 'helloworld' for c in word: print (c,end='.') print('\t') print('0.1.2.3.4.5.6.7.8.9.') print (word[0]) print (word[-2]) print (word[5:7]) print (word[:-5]) print (word[:]) newword = ','.join(word) print(newword)
f = open('data.txt') data1 = f.read() f = open('data.txt') data2 = f.readline() data3 = f.readlines() print(data1) print('--------------') print(data2) print('--------------') print(data3) f.close #释放资源
f = open('output.txt')#, 'w' data1 = f.read() f = open('data.txt') data2 = f.readline() data3 = f.readlines() print(data1) print('--------------') print(data2) print('--------------') print(data3) f.close #释放资源
f = open('scores.txt') lines = f.readlines() f.close() print(lines)
import os # 调用os后, f = open('results.txt','w') , 有此文件则打开,否则新建 results = [] for line in lines: data = line.split() #print(data) sum = 0 for score in data[1:]: sum += int(score) result = '%s\t: %d\n' % (data[0], sum) results.append(result) f = open('result.txt','w') f.writelines(results) f.close()
for i in range(10): a = input() if a == 'EOF': break
f = open('scores.txt') lines = f.readlines() f.close() print(lines) import os # 调用os后, f = open('results.txt','w') , 有此文件则打开,否则新建 results = [] for line in lines: data = line.split() #print(data) sum = 0 for score in data[1:]: point = int(score) if point < 60: continue sum += point result = '%s\t: %d\n' % (data[0], sum) results.append(result) f = open('result.txt','w') f.writelines(results) f.close()
#print (int('0.5')) import os try: #f = open('non-exist.txt') f = open('scores.txt') lines = f.readlines() f.close() print("Open success!",end = ' ') print(lines) except: print( 'File not exists.') print( 'Done')
import urllib3 http = urllib3.PoolManager() r = http.request('GET','http://www.baidu.com') print(r.status) print(r.data)
s = 'how are you' #s被赋值后就是一个字符串类型的对象 l = s.split() #split是字符串的方法,这个方法返回一个list类型的对象 #l是一个list类型的对象 #通过dir()方法可以查看一个类/变量的所有属性: print(dir(s)) print('---------') print(dir(list))
- 关键字class加上类名用来创建一个类。之后缩进的代码块是这个类的内部。在这里,我们用pass语句,表示一个空的代码块
class MyClass: print('bzz') pass mc = MyClass() print (mc) #mc是__main__模块中MyClass来的一个实例(instance)
# 给这个类加上一些域: # 给MyClass类增加了一个类变量name,并把它的值设为'Sam'。然后又增加了一个类方法sayHi # 调用类变量的方法是“对象.变量名”。你可以得到它的值,也可以改变它的值。 # 类方法和我们之前定义的函数区别在于,第一个参数必须为self。 # 而在调用类方法的时候,通过“对象.方法名()”格式进行调用, # 而不需要额外提供self这个参数的值。self在类方法中的值,就是你调用的这个对象本身 class MyClass: name = 'Sam' def sayHi(self): print ('Hello %s' % self.name) mc = MyClass() print (mc.name) MyClass.name = 'Mike' print(MyClass().name) mc.name = 'Lily' mc.sayHi()
- 假设我们有一辆汽车,我们知道它的速度(60km/h),以及A、B两地的距离(100km)。要算出开着这辆车从A地到B地花费的时间。
#面向过程的方法: speed = 60.0 distance = 100.0 time = distance / speed print (time) #面向对象的方法: class Car: speed = 0 def drive(self, distance): time = distance / self.speed print (time) car = Car() car.speed = 60.0 car.drive(100.0)
- 如果我们让题目再稍稍复杂一点。假设我们又有了一辆更好的跑车,它的速度是150km/h,然后我们除了想从A到B,还要从B到C(距离200km)。要求分别知道这两种车在这两段路上需要多少时间。
#面向过程的方法: speed1 = 60.0 distance1 = 100.0 time1 = distance1 / speed1 print (time1) distance2 = 200.0 time2 = distance2 / speed1 print (time2) speed2 = 150.0 time3 = distance1 / speed2 print (time3) time4 = distance2 / speed2 print (time4) print('--------------') #面向对象的方法: class Car: speed = 0 def drive(self, distance): time = distance / self.speed print (time) car1 = Car() car1.speed = 60.0 car1.drive(100.0) car1.drive(200.0) car2 = Car() car2.speed = 150.0 car2.drive(100.0) car2.drive(200.0)
仍然是从A地到B地,这次除了有汽车,我们还有了一辆自行车! 自行车和汽车有着相同的属性:速度(speed)。还有一个相同的方法(drive),来输出行驶/骑行一段距离所花的时间。但这次我们要给汽车增加一个属性:每公里油耗(fuel)。而在汽车行驶一段距离的方法中,除了要输出所花的时间外,还要输出所需的油量。
面向过程的方法,你可能需要写两个函数,然后把数据作为参数传递进去,在调用的时候要搞清应该使用哪个函数和哪些数据。有了面向对象,你可以把相关的数据和方法封装在一起,并且可以把不同类中的相同功能整合起来。这就需要用到面向对象中的另一个重要概念:继承。
我们要使用的方法是,创建一个叫做Vehicle的类,表示某种车,它包含了汽车和自行车所共有的东西:速度,行驶的方法。然后让Car类和Bike类都继承这个Vehicle类,即作为它的子类。在每个子类中,可以分别添加各自独有的属性。
# Vehicle类被称为基本类或超类,Car类和Bike类被成为导出类或子类 class Vehicle: def __init__(self,speed): 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.fuel = fuel def drive(self,distance): Vehicle.drive(self,distance) print('need %f fuels'%(distance*self.fuel)) b = Bike(15.0) c = Car(80.0,0.012) b.drive(100) c.drive(100) ''' 解释一下代码: __init__函数会在类被创建的时候自动调用,用来初始化类。它的参数,要在创建类的时候提供。 于是我们通过提供一个数值来初始化speed的值。 注意:__init__是python的内置方法,类似的函数名前后是两个下英文划线,如果写错了, 则不会起到原本应有的作用。 class定义后面的括号里表示这个类继承于哪个类。 Bike(Vehicle)就是说Bike是继承自Vehicle中的子类。Vehicle中的属性和方法,Bike都会有。 因为Bike不需要有额外的功能,所以用pass在类中保留空块,什么都不用写。 Car类中,我们又重新定义了__init__和drive函数,这样会覆盖掉它继承自Vehicle的同名函数。 但我们依然可以通过“Vehicle.函数名”来调用它的超类方法。 以此来获得它作为Vehicle所具有的功能。注意,因为是通过类名调用方法, 而不是像之前一样通过对象来调用,所以这里必须提供self的参数值。 在调用超类的方法之后,我们又给Car增加了一个fuel属性,并且在drive中多输出一行信息。 '''
a = "heaven" b = "hell" c = True and a or b print (c) d = False and a or b print (d)
结果很奇怪是不是?
表达式从左往右运算,1和”heaven”做and的结果是”heaven”,再与”hell”做or的结果是”heaven”;0和”heaven”做and的结果是0,再与”hell”做or的结果是”hell”。
抛开绕人的and和or的逻辑,你只需记住,在一个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会让你的代码更简单。但如果你觉得这个技巧带来的副作用已经让你头大了,没关系,用if-else可以做相同的事情。不过在python的某些情况下,你可能没法使用if语句,比如lambda函数中,这时候你可能就需要and-or的帮助了。
什么是lambda函数?呵呵,这是python的高阶玩法,暂且按住不表,以后有机会再说。
a = "" b = "hell" c = (True and [a] or [b])[0] print (c)
#上一次pygame的课中有这样一行代码: #x, y = pygame.mouse.get_pos() # 元组(tuple)也是一种序列,和我们用了很多次的list类似, # 只是元组中的元素在创建之后就不能被修改。 # 如: postion = (1, 2) geeks = ('Sheldon', 'Leonard', 'Rajesh', 'Howard') # 都是元组的实例。它有和list同样的索引、切片、遍历等操作(参见25~27课): print (postion[0]) print('------------') for _ in geeks: print (_) print('------------') print (geeks[1:3]) #其实我们之前一直在用元组,就是在print语句中: print('------------') print ('%s is %d years old' % ('Mike', 23)) #('Mike', 23)就是一个元组。这是元组最常见的用处。 #再来看一下元组作为函数返回值的例子: def get_pos(n): return (n/2, n*2) #得到这个函数的返回值有两种形式,一种是根据返回值元组中元素的个数提供变量: x, y = get_pos(50) print (x) print (y) print('------------') #这就是我们在开头那句代码中使用的方式。 #还有一种方法是用一个变量记录返回的元组: pos = get_pos(50) print (pos[0]) print (pos[1])
import math print(math.pi) print(math.e) print(math.ceil(5.4)) print(math.floor(5.4)) print(math.pow(3,2)) print(math.log(4)) print(math.log(100,10)) print(math.sqrt(9)) print(math.fabs(-34)) #math.sin .cos .tan .asin .acos .atan 弧度单位 #math.degrees(x) 弧度转角度 #math.radians(x) 角度转弧度
什么是正则表达式?在回答这个问题之前,先来看看为什么要有正则表达式。
在编程处理文本的过程中,经常会需要按照某种规则去查找一些特定的字符串。比如知道一个网页上的图片都是叫做’image/8554278135.jpg’之类的名字,只是那串数字不一样;又或者在一堆人员电子档案中,你要把他们的电话号码全部找出来,整理成通讯录。诸如此类工作,如果手工去做,当量大的时候那简直就是悲剧。但你知道这些字符信息有一定的规律,可不可以利用这些规律,让程序自动来做这些无聊的事情?答案是肯定的。这时候,你就需要一种描述这些规律的方法,正则表达式就是干这事的。
正则表达式就是记录文本规则的代码。
所以正则表达式并不是python中特有的功能,它是一种通用的方法。python中的正则表达式库,所做的事情是利用正则表达式来搜索文本。要使用它,你必须会自己用正则表达式来描述文本规则。之前多次有同学表示查找文本的事情经常会遇上,希望能介绍一下正则表达式。既然如此,我们就从正则表达式的基本规则开始说起。
1.
首先说一种最简单的正则表达式,它没有特殊的符号,只有基本的字母或数字。它满足的匹配规则就是完全匹配。例如:有个正则表达式是“hi”,那么它就可以匹配出文本中所有含有hi的字符。
来看如下的一段文字:
Hi, I am Shirley Hilton. I am his wife.
如果我们用“hi”这个正则表达式去匹配这段文字,将会得到两个结果。因为是完全匹配,所以每个结果都是“hi”。这两个“hi”分别来自“Shirley”和“his”。默认情况下正则表达式是严格区分大小写的,所以“Hi”和“Hilton”中的“Hi”被忽略了。
为了验证正则表达式匹配的结果,你可以用以下这段代码做实验:
import re text = "Hi, I am Shirley Hilton. I am his wife.hi" m = re.findall(r"[Hh]i", text) if m: print (m) else: print ('not match')
暂时先不解释这其中代码的具体含义,你只要去更改text和findall中的字符串,就可以用它来检测正则表达式的实际效果。
2.
如果我们只想找到“hi”这个单词,而不把包含它的单词也算在内,那就可以使用“\bhi\b”这个正则表达式。在以前的字符串处理中,我们已经见过类似“\n”这种特殊字符。在正则表达式中,这种字符更多,以后足以让你眼花缭乱。
“\b”在正则表达式中表示单词的开头或结尾,空格、标点、换行都算是单词的分割。而“\b”自身又不会匹配任何字符,它代表的只是一个位置。所以单词前后的空格标点之类不会出现在结果里。
在前面那个例子里,“\bhi\b”匹配不到任何结果。但“\bhi”的话就可以匹配到1个“hi”,出自“his”。用这种方法,你可以找出一段话中所有单词“Hi”,想一下要怎么写。
3.
最后再说一下[]这个符号。在正则表达式中,[]表示满足括号中任一字符。比如“[hi]”,它就不是匹配“hi”了,而是匹配“h”或者“i”。
在前面例子中,如果把正则表达式改为“[Hh]i”,就可以既匹配“Hi”,又匹配“hi”了。
1.r”hi”
这里字符串前面加了r,是raw的意思,它表示对字符串不进行转义。为什么要加这个?你可以试试print “\bhi”和r”\bhi”的区别。
print “\bhi”
hi
print r”\bhi”
\bhi
可以看到,不加r的话,\b就没有了。因为python的字符串碰到“\”就会转义它后面的字符。如果你想在字符串里打“\”,则必须要打“\”。
print “\bhi”
\bhi
这样的话,我们的正则表达式里就会多出很多“\”,让本来就已经复杂的字符串混乱得像五仁月饼一般。但加上了“r”,就表示不要去转义字符串中的任何字符,保持它的原样。
2.re.findall(r”hi”, text)
re是python里的正则表达式模块。findall是其中一个方法,用来按照提供的正则表达式,去匹配文本中的所有符合条件的字符串。返回结果是一个包含所有匹配的list。
3.今天主要说两个符号“.”和“*”,顺带说下“\S”和“?”。
“.”在正则表达式中表示除换行符以外的任意字符。在上节课提供的那段例子文本中:
Hi, I am Shirley Hilton. I am his wife.
如果我们用“i.”去匹配,就会得到
[‘i,’, ‘ir’, ‘il’, ‘is’, ‘if’]
你若是暴力一点,也可以直接用“.”去匹配,看看会得到什么。
与“.”类似的一个符号是“\S”,它表示的是不是空白符的任意字符。注意是大写字符S。
4.在很多搜索中,会用“?”表示任意一个字符, 表示任意数量连续字符,这种被称为通配符。但在正则表达式中,任意字符是用 “ . ” 表示,而 则不是表示字符,而是表示数量:它表示前面的字符可以重复任意多次(包括0次),只要满足这样的条件,都会被表达式匹配上。
结合前面的“.”,用“I.e”去匹配,想一下会得到什么结果?
[‘I am Shirley Hilton. I am his wife’]
是不是跟你想的有些不一样?也许你会以为是
[‘I am Shirle’, ‘I am his wife’]
这是因为“”在匹配时,会匹配尽可能长的结果。如果你想让他匹配到最短的就停止,需要用“.?”。如“I.*?e”,就会得到第二种结果。这种匹配方式被称为懒惰匹配,而原本尽可能长的方式被称为贪婪匹配。
最后留一道习题:
从下面一段文本中,匹配出所有s开头,e结尾的单词。
site sea sue sweet see case sse ssee loses
先来公布上一课习题的答案:
\bs\S*e\b
有的同学给出的答案是”\bs.*?e\b”。测试一下就会发现,有奇怪的’sea sue’和’sweet see’混进来了。既然是单词,我们就不要空格,所以需要用”\S”而不是”.”
昨天有位同学在论坛上说,用正则表达式匹配出了文件中的手机号。这样现学现用很不错。匹配的规则是”1.*?\n”,在这个文件的条件下,是可行的。但这规则不够严格,且依赖于手机号结尾有换行符。今天我来讲讲其他的方法。
匹配手机号,其实就是找出一串连续的数字。更进一步,是11位,以1开头的数字。
还记得正则第1讲里提到的[]符号吗?它表示其中任意一个字符。所以要匹配数字,我们可以用
[0123456789]
由于它们是连续的字符,有一种简化的写法:[0-9]。类似的还有[a-zA-Z]的用法。
还有另一种表示数字的方法:
\d
要表示任意长度的数字,就可以用
[0-9]*
或者
\d*
但要注意的是,表示的任意长度包括0,也就是没有数字的空字符也会被匹配出来。一个与类似的符号+,表示的则是1个或更长。
所以要匹配出所有的数字串,应当用
[0-9]+
或者
\d+
如果要限定长度,就用{}代替+,大括号里写上你想要的长度。比如11位的数字:
\d{11}
想要再把第一位限定为1,就在前面加上1,后面去掉一位:
1\d{10}
OK. 总结一下今天提到的符号:
[0-9]
\d
+
{}
现在你可以去一个混杂着各种数据的文件里,抓出里面的手机号,或是其他你感兴趣的数字了。
1.
我们已经了解了正则表达式中的一些特殊符号,如\b、\d、.、\S等等。这些具有特殊意义的专用字符被称作“元字符”。常用的元字符还有:
\w – 匹配字母或数字或下划线或汉字(我试验下了,发现3.x版本可以匹配汉字,但2.x版本不可以)
\s – 匹配任意的空白符
^ – 匹配字符串的开始
$ – 匹配字符串的结束
2.
\S其实就是\s的反义,任意不是空白符的字符。同理,还有:
\W – 匹配任意不是字母,数字,下划线,汉字的字符
\D – 匹配任意非数字的字符
\B – 匹配不是单词开头或结束的位置
[a]的反义是[^a],表示除a以外的任意字符。[^abcd]就是除abcd以外的任意字符。
3.
之前我们用过*、+、{}来表示字符的重复。其他重复的方式还有:
? – 重复零次或一次
{n,} – 重复n次或更多次
{n,m} – 重复n到m次
正则表达式不只是用来从一大段文字中抓取信息,很多时候也被用来判断输入的文本是否符合规范,或进行分类。来点例子看看:
^\w{4,12}$
这个表示一段4到12位的字符,包括字母或数字或下划线或汉字,可以用来作为用户注册时检测用户名的规则。(但汉字在python2.x里面可能会有问题)
\d{15,18}
表示15到18位的数字,可以用来检测身份证号码
^1\d*x?
以1开头的一串数字,数字结尾有字母x,也可以没有。有的话就带上x。
另外再说一下之前提到的转义字符\。如果我们确实要匹配.或者字符本身,而不是要它们所代表的元字符,那就需要用.或\。\本身也需要用\。
比如”\d+.\d+”可以匹配出123.456这样的结果。
留一道稍稍有难度的习题:
写一个正则表达式,能匹配出多种格式的电话号码,包括
(021)88776543
010-55667890
02584453362
0571 66345673
来说上次的习题:
(021)88776543
010-55667890
02584453362
0571 66345673
一个可以匹配出所有结果的表达式是
(?0\d{2,3}[) -]?\d{7,8}
解释一下:
(?
()在正则表达式里也有着特殊的含义,所以要匹配字符”(“,需要用”(“。?表示这个括号是可有可无的。
0\d{2,3}
区号,0xx或者0xxx
[) -]?
在区号之后跟着的可能是”)”、” “、”-“,也可能什么也没有。
\d{7,8}
7或8位的电话号码
可是,这个表达式虽然能匹配出所有正确的数据(一般情况下,这样已经足够),但理论上也会匹配到错误的数据。因为()应当是成对出现的,表达式中对于左右两个括号并没有做关联处理,例如(02188776543这样的数据也是符合条件的。
我们可以用正则表达式中的“|”符号解决这种问题。“|”相当于python中“or”的作用,它连接的两个表达式,只要满足其中之一,就会被算作匹配成功。
于是我们可以把()的情况单独分离出来:
(0\d{2,3})\d{7,8}
其他情况:
0\d{2,3}[ -]?\d{7,8}
合并:
(0\d{2,3})\d{7,8}|0\d{2,3}[ -]?\d{7,8}
使用“|”时,要特别提醒注意的是不同条件之间的顺序。匹配时,会按照从左往右的顺序,一旦匹配成功就停止验证后面的规则。假设要匹配的电话号码还有可能是任意长度的数字(如一些特殊的服务号码),你应该把
|\d+
这个条件加在表达式的最后。如果放在最前面,某些数据就可能会被优先匹配为这一条件。你可以写个测试用例体会一下两种结果的不同。
关于正则表达式,我们已经讲了5篇,介绍了正则表达式最最皮毛的一些用法。接下来,这个话题要稍稍告一段落。推荐一篇叫做《正则表达式30分钟入门教程》的文章(直接百度一下就能找到,我也会转到论坛上),想要对正则表达式进一步学习的同学可以参考。这篇教程是个标题党,里面涉及了正则表达式较多的内容,30分钟绝对看不完。
random模块的作用是产生随机数。之前的小游戏中用到过random中的randint:
import random
num = random.randint(1,100)
random.randint(a, b)可以生成一个a到b间的随机整数,包括a和b。
a、b都必须是整数,且必须b≥a。当等于的时候,比如:
random.randint(3, 3)
的结果就永远是3
除了randint,random模块中比较常用的方法还有:
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。
比如:
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中常见的几个方法。如果你在程序中需要其中某一个方法,也可以这样写:
from random import randint
randint(1, 10)
另外,有些编程基础的同学可能知道,在随机数中有个seed的概念,需要一个真实的随机数,比如此刻的时间、鼠标的位置等等,以此为基础产生伪随机数。在python中,默认用系统时间作为seed。你也可以手动调用random.seed(x)来指定seed。
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中的另一个很有用的方法:
time.sleep(secs)
它可以让程序暂停secs秒。例如:
import time print (1) time.sleep(2) print (2)
- 读错误信息
来看如下一个例程:
import random
a = 0
for i in range(5):
b = random.choice(range(5))
a += i / b
print a
这个程序中,i从0循环到4,每次循环中,b是0到4中的一个随机数。把i/b的结果累加到a上,最后输出结果。
运行这段程序,有时候会输出结果,有时候却跳出错误信息:
Traceback (most recent call last):
File “C:\Users\Crossin\Desktop\py\test.py”, line 5, in
a += i / b
ZeroDivisionError: integer division or modulo by zero
有些同学看见一段英文提示就慌了。其实没那么复杂,python的错误提示做得还是很标准的。
它告诉我们错误发生在test.py文件中的第6行
a += i / b
这一句上。
这个错误是“ZeroDivisionError”,也就是除零错。
“integer division or modulo by zero”,整数被0除或者被0模(取余数)。
因为0不能作为除数,所以当b随机到0的时候,就会引发这个错误。
知道了原因,就可以顺利地解决掉这个bug。
以后在写代码的时候,如果遇到了错误,先别急着去改代码。试着去读一读错误提示,看看里面都说了些啥。
- 输出调试信息
我们在所有课程的最开始就教了输出函数“print”。它是编程中最简单的调试手段。有的时候,仅从错误提示仍然无法判断出程序错误的原因,或者没有发生错误,但程序的结果就是不对。这种情况下,通过输出程序过程中的一些状态,可以帮助分析程序。
把前面那个程序改造一下,加入一些与程序功能无关的输出语句:
import random
a = 0
for i in range(5):
print 'i: %d' % i
b = random.choice(range(5))
print 'b: %d' % b
a += i / b
print 'a: %d' % a
print
print a
运行后的输出结果(每次结果都会不一样):
i: 0
b: 3
a: 0
i: 1
b: 3
a: 0
i: 2
b: 3
a: 0
i: 3
b: 0
Traceback (most recent call last):
File “C:\Users\Crossin\Desktop\py\test.py”, line 7, in
a += i / b
ZeroDivisionError: integer division or modulo by zero
当b的值为0时,发生了除零错。这次可以更清晰地看出程序出错时的状态。
在真实开发中,程序的结构可能会非常复杂。通过输出调试信息,可以有效地缩小范围、定位错误发生的位置,确认错误发生时的场景,进而找出错误原因。
- python3 在字符方面和 python2 区别较大
import pickle test_data = ['Save me!'.encode('utf8'), 123.456, True] f = open('test.data', 'wb+') pickle.dump(test_data, f) f.close()
import pickle f = open('test.data','rb') test_data = pickle.load(f) f.close() print (test_data)
如果你想保存多个对象,一种方法是把这些对象先全部放在一个序列中,在对这个序列进行存储:
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倍)。因此你可以把上述代码中的 pickle 全部替换为 cPickle,从而提高运行速度(尽管在这个小程序中影响微乎其微)。
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)
list_1 = [1, 2, 3, 5, 8, 13, 22] list_2 = [i for i in list_1 if i % 2 == 0] print (list_2)
我们曾经讲过 Python 中函数的参数传递(见第21课)。最基本的方式是:
定义:
def func(arg1, arg2):
print arg1, arg2
调用:
func(3, 7)
我们把函数定义时的参数名(arg1、arg2)称为形参,调用时提供的参数(3、7)称为实参。
这种方式是根据调用时提供参数的位置进行匹配,要求实参与行参的数量相等,默认按位置匹配参数。调用时,少参数或者多参数都会引起错误。这是最常用的一种函数定义方式。
在调用时,也可以根据形参的名称指定实参。如:
func(arg2=3, arg1=7)
但同样,必须提供所有的参数。看看和func(3, 7)的运行结果有什么不同。
Python 语言还提供了其他一些更灵活的参数传递方式,如:
func2(a=1, b=2, c=3)
func3(*args)
func4(**kargs)
今天我们先说说func2这种方式。
这种方式可以理解为,在一般函数定义的基础上,增加了参数的默认值。这样定义的函数可以和原来一样使用,而当你没有提供足够的参数时,会用默认值作为参数的值。
例如:
定义
def func(arg1=1, arg2=2, arg3=3): print (arg1, arg2, arg3)
# 调用: func(2, 3, 4) func(5, 6) func(7)
输出为
2 3 4
5 6 3
7 2 3
提供的参数会按顺序先匹配前面位置的参数,后面未匹配到的参数使用默认值。
也可以指定其中的部分参数,如:
func(arg2=8) func(arg3=9, arg1=10)
输出为
1 8 3
10 2 9
#或者混合起来用: func(11, arg3=12)
输出为
11 2 12
但要注意,没有指定参数名的参数必须在所有指定参数名的参数前面,且参数不能重复。以下的调用都是错误的:
func(arg1=13, 14)
func(15, arg1=16)
定义参数默认值的函数可以在调用时更加简洁。大量 Python 模块中的方法都运用了这一方式,让使用者在调用时可以提供尽可能少的参数。
接着上一次的内容,来介绍一种更加灵活的参数传递方式:
def func(*args)
这种方式的厉害之处在于,它可以接受任意数量的参数。来看具体例子:
def calcSum(*args): sum = 0 for i in args: sum += i print (sum)
# 调用 calcSum(1,2,3) calcSum(123,456) calcSum()
输出:
6
579
0
在变量前加上星号前缀(*),调用时的参数会存储在一个 tuple(元组)对象中,赋值给形参。在函数内部,需要对参数进行处理时,只要对这个 tuple 类型的形参(这里是 args)进行操作就可以了。因此,函数在定义时并不需要指明参数个数,就可以处理任意参数个数的情况。
不过有一点需要注意,tuple 是有序的,所以 args 中元素的顺序受到赋值时的影响。如:
def printAll(*args): for i in args: print (i,end=' ') print()
#调用: printAll(1,2,3) printAll(3,2,1)
输出:
1 2 3
3 2 1
虽然3个参数在总体上是相同的,但由于调用的顺序不一样,结果也是不同的。
还有一种参数传递方式,既可以按参数名传递参数,不受位置的限制,又可以像 tuple 传递一样不受数量限制。这个我将在下次课中做介绍。
今天来说说最为灵活的一种参数传递方式:
func(**kargs)
上次说的 func(*args) 方式是把参数作为 tuple 传入函数内部。而 func(**kargs) 则是把参数以键值对字典的形式传入。
示例:
def printAll(**kargs): for k in kargs: print (k, ':', kargs[k]) printAll(a=1, b=2, c=3) printAll(x=4, y=5)
输出:
a : 1
c : 3
b : 2
y : 5
x : 4
字典是无序的,所以在输出的时候,并不一定按照提供参数的顺序。同样在调用时,参数的顺序无所谓,只要对应合适的形参名就可以了。于是,采用这种参数传递的方法,可以不受参数数量、位置的限制。
当然,这还不够。Python 的函数调用方式非常灵活,前面所说的几种参数调用方式,可以混合在一起使用。看下面这个例子:
def func(x, y=5, *a, **b): print (x, y, a, b) func(1) func(1,2) func(1,2,3) func(1,2,3,4) func(x=1) func(x=1,y=1) func(x=1,y=1,a=1) func(x=1,y=1,a=1,b=1) func(1,y=1) func(1,2,3,4,a=1) func(1,2,3,4,k=1,t=2,o=3)
输出:
1 5 () {}
1 2 () {}
1 2 (3,) {}
1 2 (3, 4) {}
1 5 () {}
1 1 () {}
1 1 () {‘a’: 1}
1 1 () {‘a’: 1, ‘b’: 1}
1 1 () {}
1 2 (3, 4) {‘a’: 1}
1 2 (3, 4) {‘k’: 1, ‘t’: 2, ‘o’: 3}
在混合使用时,首先要注意函数的写法,必须遵守:
带有默认值的形参(arg=)须在无默认值的形参(arg)之后;
元组参数(*args)须在带有默认值的形参(arg=)之后;
字典参数(*kargs)须在元组参数(args)之后。
可以省略某种类型的参数,但仍需保证此顺序规则。
调用时也需要遵守:
指定参数名称的参数要在无指定参数名称的参数之后;
不可以重复传递,即按顺序提供某参数之后,又指定名称传递。
而在函数被调用时,参数的传递过程为:
1.按顺序把无指定参数的实参赋值给形参;
2.把指定参数名称(arg=v)的实参赋值给对应的形参;
3.将多余的无指定参数的实参打包成一个 tuple 传递给元组参数(*args);
4.将多余的指定参数名的实参打包成一个 dict 传递给字典参数(**kargs)。
是不是乍一看有点绕?没关系,赶紧打开你的编辑器,自行体会一下不同调用方式的用法。然后在未来的编程实践中慢慢熟悉吧。
Python 是一门简洁的语言,lambda 表达式则充分体现了 Python 这一特点。
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 表达式的语法格式:
lambda 参数列表: 表达式
定义 lambda 表达式时,参数列表周围没有括号,返回值前没有 return 关键字,也没有函数名称。
它的写法比 def 更加简洁。但是,它的主体只能是一个表达式,不可以是代码块,甚至不能是命令(print 不能用在 lambda 表达式中)。所以 lambda 表达式能表达的逻辑很有限。
lambda 表达式创建了一个函数对象,可以把这个对象赋值给一个变量进行调用,就像上面的例子中一样。
来看一个复杂一点的例子,把 lambda 表达式用在 def 函数定义中:
def fn(x): return lambda y: x + y a = fn(2) print (a(3))
这里,fn 函数的返回值是一个 lambda 表达式,也就等于是一个函数对象。当以参数2来调用 fn 时,得到的结果就是:
lambda y: 2 + y
a = fn(2) 就相当于:
a = lambda y: 2 + y
所以 a(3) 的结果就是5。
lambda 表达式其实只是一种编码风格,这种写法更加 pythonic。这并不意味着你一定要使用它。事实上,任何可以使用 lambda 表达式的地方,都可以通过普通的 def 函数定义来替代。在一些需要重复使用同一函数的地方,def 可以避免重复定义函数。况且 def 函数更加通用,某些情况可以带来更好地代码可读性。
而对于像 filter、sort 这种需要内嵌函数的方法,lambda 表达式就会显得比较合适。这个我以后会再单独介绍。
当然对于初学者来说,了解 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 在函数内部被重新赋值。但在调用了函数之后,x 的值仍然是50。为什么?
这就得说一下变量的“作用域”:
当函数内部定义了一个变量,无论是作为函数的形参,或是另外定义的变量,它都只在这个函数的内部起作用。函数外即使有和它名称相同的变量,也没有什么关联。这个函数体就是这个变量的作用域。像这样在函数内部定义的变量被称为“局部变量”。
要注意的是,作用域是从变量被定义的位置开始。像这样的写法是有问题的:
def func(): print (y) y = 2 print (y) func()
因为在 y = 2 之前,y 并不存在,调用 y 的值就会出错。
回到开始那个例子:
在函数 func 外部,定义的变量 x,赋值为 50,作为参数传给了函数 func。而在函数 func 内部,变量 x 是形参,它的作用域是整个函数体内部。它与外面的那个 x 没有关系。只不过它的初始值是由外面那个 x 传递过来的。
所以,虽然函数体内部的 x 被重新赋值为 2,也不会影响外面那个 x 的值。
不过有时候,我们希望能够在函数内部去改变一些变量的值,并且这些变量在函数外部同样被使用到。怎么办?
一种方法是,用 return 把改变后的变量值作为函数返回值传递出来,赋值给对应的变量。比如开始的那个例子,可以在函数结尾加上
return x
然后把调用改为
x = func(x)
还有一种方法,就是使用“全局变量”。
在 Python 的函数定义中,可以给变量名前加上 global 关键字,这样其作用域就不再局限在函数块中,而是全局的作用域。
#通过 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)
函数 func 不再提供参数调用。而是通过 global x 告诉程序:这个 x 是一个全局变量。于是函数中的 x 和外部的 x 就成为了同一个变量。这一次,当 x 在函数 func 内部被重新赋值后,外部的 x 也随之改变。
前面讲的局部变量和全局变量是 Python 中函数作用域最基本的情况。实际上,还有一些略复杂的情况,比如:
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)
程序可以正常运行。虽然没有指明 global,函数内部还是使用到了外部定义的变量。然而一旦加上
x = 2
这句,程序就会报错。因为这时候,x 成为一个局部变量,它的作用域从定义处开始,到函数体末尾结束。
建议在写代码的过程中,显式地通过 global 来使用全局变量,避免在函数中直接使用外部变量。
来看两个问题:
-
假设有一个数列,如何把其中每一个元素都翻倍?
-
假设有两个数列,如何求和?
第一个问题,普通程序员大概会这么写:
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)
这是我在《【Python 第66课】列表综合》里说到的方法,微信中回复 66 可以查看。
今天来说另一种 Python 程序员常用的写法 — map:
lst_1 = [1,2,3,4,5,6] def double_func(x): return x * 2 lst_2 = map(double_func, lst_1) print (list(lst_2))
map 是 Python 自带的内置函数,它的作用是把一个函数应用在一个(或多个)序列上,把列表中的每一项作为函数输入进行计算,再把计算的结果以列表的形式返回。
map 的第一个参数是一个函数,之后的参数是序列,可以是 list、tuple。
所以刚刚那个问题也可以写成:
lst_1 = (1,2,3,4,5,6) lst_2 = map(lambda x: x * 2, lst_1) print (list(lst_2))
这里原数据改为了元组,函数用 lambda 表达式替代。可参考《【Python 第70课】lambda 表达式》,微信中回复 70。
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 (list(lst_3))
map 中的函数会从对应的列表中依次取出元素,作为参数使用,同样将结果以列表的形式返回。所以要注意的是,函数的参数个数要与 map 中提供的序列组数相同,即函数有几个参数,就得有几组数据。
对于每组数据中的元素个数,如果有某组数据少于其他组,map 会以 None 来补全这组参数。
此外,当 map 中的函数为 None 时,结果将会直接返回参数组成的列表。如果只有一组序列,会返回元素相同的列表,如果有多组数列,将会返回每组数列中,对应元素构成的元组所组成的列表。听上去很绕口是不是……代码试试看就明白了:
def f(x, y): return (x, y) lst_1 = [1,2,3,4,5,6] lst_2 = [1,3,5,7,9,11] lst_3 = list(map(f, lst_1, lst_2)) print (lst_3)
def f(x, y): return (x, y) l1 = [ 0, 1, 2, 3, 4, 5, 6 ] l2 = [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ] list(map(f, l1, l2))
上次说了 Python 中一个比较有意思的内置函数 map,今天再来介绍另一个类似的函数:reduce
map 可以看作是把一个序列根据某种规则,映射到另一个序列。reduce 做的事情就是把一个序列根据某种规则,归纳为一个输出。
上栗子。以前我们给过一个习题,求1累加到100的和。寻常的做法大概是这样:
sum = 0 for i in range(1, 101): sum += i print (sum)
#如果用 reduce 函数,就可以写成: from functools import reduce lst = range(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 做到),可以选择使用 reduce 来实现。相对可以使代码更简洁。
我觉得,写代码的可读性是很重要的事情,简洁易懂的代码,既容易让别人看懂,也便于自己以后的维护。同时,较少的代码也意味着比较高的开发效率和较少的出错可能。应尽量避免写混乱冗长的代码。当然,也不用为了一味追求代码的精简,总是想方设法把代码写在一行里。那就又走了另一个极端,同样也缺乏可读性。而至于是否使用类似 map、reduce 这样的方法,也是根据需要和个人习惯,我认为并没有一定的规则限制。
多线程
很多人使用 python 编写“爬虫”程序,抓取网上的数据。 举个例子,通过豆瓣的 API 抓取 30 部影片的信息:
Python3 多线程 http://www.runoob.com/python3/python3-multithreading.html
import urllib3, time import urllib time_start = time.time() data = [] for i in range(30): print ('request movie:', i) id = 1764796 + i http = urllib3.PoolManager() r = http.request('url','https://api.douban.com/v2/movie/subject/%d' % id) data.append(r) print (i, time.time() - time_start) print ('data:', len(data))
程序里用了 time.time() 来计算抓取花费的时间。运行一遍,大约需要十几秒(根据网络情况会有差异)。
如果我们想用这套代码抓取几万部电影,就算中间不出什么状况,估计也得花上好几个小时。
然而想一下,我们抓一部电影信息的过程是独立,并不依赖于其他电影的结果。因此没必要排好队一部一部地按顺序来。那么有没有什么办法可以同时抓取好几部电影?
答案就是:多线程。
来说一种简单的多线程方法:
python 里有一个 thread 模块,其中提供了一个函数:
start_new_thread(function, args[, kwargs])
function 是开发者定义的线程函数,
args 是传递给线程函数的参数,必须是tuple类型,
kwargs 是可选参数。
调用 start_new_thread 之后,会创建一个新的线程,来执行 function 函数。而代码原本的主线程将继续往下执行,不再等待 function 的返回。通常情况,线程在 function 执行完毕后结束。
改写一下前面的代码,将抓取的部分放在一个函数中:
import urllib3, time, _thread def get_content(i): id = 1764796 + i http = urllib3.PoolManager() r = http.request('url','https://api.douban.com/v2/movie/subject/%d' % id) data.append(r) 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,)) input('press ENTER to exit...\n')
因为主线程不在等待函数返回结果,所以在代码最后,增加了 raw_input,避免程序提前退出。
从输出结果可以看出:
在程序刚开始运行时,已经发送所有请求
收到的请求并不是按发送顺序,先收到就先显示
总共用时两秒多
data 里同样记录了所有30条结果
所以,对于这种耗时长,但又独立的任务,使用多线程可以大大提高运行效率。但在代码层面,可能额外需要做一些处理,保证结果正确。如上例中,如果需要电影信息按 id 排列,就要另行排序。
多线程通常会用在网络收发数据、文件读写、用户交互等待之类的操作上,以避免程序阻塞,提升用户体验或提高执行效率。
多线程的实现方法不止这一种。另外多线程也会带来一些单线程程序中不会出现的问题。这里只是简单地开个头。
from random import randint
num = randint(1, 100)
print('guess what I think?')
answer = int(input())
while(answer!=num):
if answer<num:
print('too small?')
elif answer>num:
print('too big?')
elif answer==num:
break
answer = int(input())
print('equal')
sum = 0
for _ in range(1,101) :
sum = sum + _
print(sum)
print("this is the\
same line")
print('''
"What's your name?" I asked.
"I'm Han Meimei."
''')
num = 18
print ('My age is %d' % num)
print ('Price is %.2f' % 4.6765)
for i in range(0, 5):
for j in range(0, i+1):
print ('*',end=' ')
print('\t')
print ('%s is easy to %d' % ('Python',100))
bool(None)
def sayHello(someone):
print( someone + ' say hello world!')
sayHello('bzz')
def plus(num1, num2):
print (num1+num2)
plus(1,5)
def isEqual(num1, num2):
if num1<num2:
print ('too small')
return False;
elif num1>num2:
print ('too big')
return False;
else :
print ('bingo')
return True
from random import randint
num = randint(1, 100)
print ('Guess what I think?')
bingo = False
while bingo == False:
answer = int(input())
bingo = isEqual(answer, num)
print(list(range(1,10)))
import numpy as np
a=np.arange(1,10)
print(a)
l = [365, 'everyday', 0.618, True]
print (l[1])
#修改list中的元素
l[0] = 123
print (l)
#向list中添加元素
l.append(1024)
print (l)
#删除list中的元素
del l[0]
print (l)
from random import choice
print ('Choose one side to shoot:')
print ('left, center, right')
you = input()
print ('You kicked ' + you)
direction = ['left', 'center', 'right']
com = choice(direction)
print ('Computer saved :' + com)
if you != com:
print ('Goal!')
else:
print ('Oops...')
切片的语法表达式为:[start_index : end_index : step],其中: start_index表示起始索引end_index表示结束索引 step表示步长,步长不能为0,且默认值为1 切片操作是指按照步长,截取从起始索引到结束索引,但不包含结束索引(也就是结束索引减1)的所有元素。
python3支持切片操作的数据类型有list、tuple、string、unicode、range 切片返回的结果类型与原对象类型一致 切片不会改变原对象,而是重新生成了一个新的对象
l = ['a', 'b', 'c', 'd', 'e', 'f']
print(l[-1]) # "-" 表示倒数
print(l[-4])
print(l[0:4])
print(l[1:])
print(l[:3])
print(l[:])
print(l[1:-1])
from random import choice
score_you = 0
score_com = 0
direction = ['left', 'center', 'right']
for i in range(5):
print ('==== Round %d - You Kick! ====' % (i+1))
print ('选一边射门')#Choose one side to shoot:
print ('left, center, right')
you = input()
print ('You kicked ' + you)
com = choice(direction)
print ('Computer saved ' + com)
if you != com:
print ('Goal!')
score_you += 1
else:
print ('Oops...')
print ('Score: %d(you) - %d(com)\n' % (score_you, score_com))
print ('==== Round %d - You Save! ====' % (i+1))
print ('选一边守门:')
print ('left, center, right')
you = input()
print ('You saved ' + you)
com = choice(direction)
print ('Computer kicked ' + com)
if you == com:
print ('Saved!')
else:
print ('Oops...')
score_com += 1
print ('Score: %d(you) - %d(com)\n' % (score_you, score_com))
sentence = 'I am an Englist sentence'
print(sentence.split())
# 指定参数分割
section = 'Hi! I am the one! Bye!'
print(section.split('!')) # ! 所有都被分割 空格也是
print('_a!a a .'.split('a'))
from random import choice score = [0, 0] direction = ['left', 'center', 'right'] def kick(): print ('==== You Kick! ====') print ('Choose one side to shoot:') print ('left, center, right') you = input() print ('You kicked ' + you) com = choice(direction) print ('Computer saved ' + com) if you != com: print ('Goal!') score[0] += 1 else: print ('Oops...') print ('Score: %d(you) - %d(com)\n' % (score[0], score[1])) print ('==== You Save! ====') print ('Choose one side to save:') print ('left, center, right') you = input() print ('You saved ' + you) com = choice(direction) print ('Computer kicked ' + com) if you == com: print ('Saved!') else: print ('Oops...') score[1] += 1 print ('Score: %d(you) - %d(com)\n' % (score[0], score[1])) for i in range(5): print ('==== Round %d ====' % (i+1)) kick() while(score[0] == score[1]): i += 1 print ('==== Round %d ====' % (i+1)) kick() if score[0] > score[1]: print( 'You Win!') else: print ('You Lose.')
- join和昨天说的split正好相反:split是把一个字符串分割成很多字符串组成的list,而join则是把一个list中的所有字符串连接成一个字符串。
- join的格式有些奇怪,它不是list的方法,而是字符串的方法。首先你需要有一个字符串作为list中所有元素的连接符,然后再调用这个连接符的join方法,join的参数是被连接的list:
s = ';' li = ['apple', 'pear', 'orange'] fruit = s.join(li) print (fruit) print(';'.join(['apple', 'pear', 'orange'])) print(''.join(['hello', 'world'])) # 无缝连接
word = 'helloworld' for c in word: print (c,end='.') print('\t') print('0.1.2.3.4.5.6.7.8.9.') print (word[0]) print (word[-2]) print (word[5:7]) print (word[:-5]) print (word[:]) newword = ','.join(word) print(newword)
f = open('data.txt') data1 = f.read() f = open('data.txt') data2 = f.readline() data3 = f.readlines() print(data1) print('--------------') print(data2) print('--------------') print(data3) f.close #释放资源
f = open('output.txt')#, 'w' data1 = f.read() f = open('data.txt') data2 = f.readline() data3 = f.readlines() print(data1) print('--------------') print(data2) print('--------------') print(data3) f.close #释放资源
f = open('scores.txt') lines = f.readlines() f.close() print(lines)
import os # 调用os后, f = open('results.txt','w') , 有此文件则打开,否则新建 results = [] for line in lines: data = line.split() #print(data) sum = 0 for score in data[1:]: sum += int(score) result = '%s\t: %d\n' % (data[0], sum) results.append(result) f = open('result.txt','w') f.writelines(results) f.close()
for i in range(10): a = input() if a == 'EOF': break
f = open('scores.txt') lines = f.readlines() f.close() print(lines) import os # 调用os后, f = open('results.txt','w') , 有此文件则打开,否则新建 results = [] for line in lines: data = line.split() #print(data) sum = 0 for score in data[1:]: point = int(score) if point < 60: continue sum += point result = '%s\t: %d\n' % (data[0], sum) results.append(result) f = open('result.txt','w') f.writelines(results) f.close()
#print (int('0.5')) import os try: #f = open('non-exist.txt') f = open('scores.txt') lines = f.readlines() f.close() print("Open success!",end = ' ') print(lines) except: print( 'File not exists.') print( 'Done')
import urllib3 http = urllib3.PoolManager() r = http.request('GET','http://www.baidu.com') print(r.status) print(r.data)
s = 'how are you' #s被赋值后就是一个字符串类型的对象 l = s.split() #split是字符串的方法,这个方法返回一个list类型的对象 #l是一个list类型的对象 #通过dir()方法可以查看一个类/变量的所有属性: print(dir(s)) print('---------') print(dir(list))
- 关键字class加上类名用来创建一个类。之后缩进的代码块是这个类的内部。在这里,我们用pass语句,表示一个空的代码块
class MyClass: print('bzz') pass mc = MyClass() print (mc) #mc是__main__模块中MyClass来的一个实例(instance)
# 给这个类加上一些域: # 给MyClass类增加了一个类变量name,并把它的值设为'Sam'。然后又增加了一个类方法sayHi # 调用类变量的方法是“对象.变量名”。你可以得到它的值,也可以改变它的值。 # 类方法和我们之前定义的函数区别在于,第一个参数必须为self。 # 而在调用类方法的时候,通过“对象.方法名()”格式进行调用, # 而不需要额外提供self这个参数的值。self在类方法中的值,就是你调用的这个对象本身 class MyClass: name = 'Sam' def sayHi(self): print ('Hello %s' % self.name) mc = MyClass() print (mc.name) MyClass.name = 'Mike' print(MyClass().name) mc.name = 'Lily' mc.sayHi()
- 假设我们有一辆汽车,我们知道它的速度(60km/h),以及A、B两地的距离(100km)。要算出开着这辆车从A地到B地花费的时间。
#面向过程的方法: speed = 60.0 distance = 100.0 time = distance / speed print (time) #面向对象的方法: class Car: speed = 0 def drive(self, distance): time = distance / self.speed print (time) car = Car() car.speed = 60.0 car.drive(100.0)
- 如果我们让题目再稍稍复杂一点。假设我们又有了一辆更好的跑车,它的速度是150km/h,然后我们除了想从A到B,还要从B到C(距离200km)。要求分别知道这两种车在这两段路上需要多少时间。
#面向过程的方法: speed1 = 60.0 distance1 = 100.0 time1 = distance1 / speed1 print (time1) distance2 = 200.0 time2 = distance2 / speed1 print (time2) speed2 = 150.0 time3 = distance1 / speed2 print (time3) time4 = distance2 / speed2 print (time4) print('--------------') #面向对象的方法: class Car: speed = 0 def drive(self, distance): time = distance / self.speed print (time) car1 = Car() car1.speed = 60.0 car1.drive(100.0) car1.drive(200.0) car2 = Car() car2.speed = 150.0 car2.drive(100.0) car2.drive(200.0)
仍然是从A地到B地,这次除了有汽车,我们还有了一辆自行车! 自行车和汽车有着相同的属性:速度(speed)。还有一个相同的方法(drive),来输出行驶/骑行一段距离所花的时间。但这次我们要给汽车增加一个属性:每公里油耗(fuel)。而在汽车行驶一段距离的方法中,除了要输出所花的时间外,还要输出所需的油量。
面向过程的方法,你可能需要写两个函数,然后把数据作为参数传递进去,在调用的时候要搞清应该使用哪个函数和哪些数据。有了面向对象,你可以把相关的数据和方法封装在一起,并且可以把不同类中的相同功能整合起来。这就需要用到面向对象中的另一个重要概念:继承。
我们要使用的方法是,创建一个叫做Vehicle的类,表示某种车,它包含了汽车和自行车所共有的东西:速度,行驶的方法。然后让Car类和Bike类都继承这个Vehicle类,即作为它的子类。在每个子类中,可以分别添加各自独有的属性。
# Vehicle类被称为基本类或超类,Car类和Bike类被成为导出类或子类 class Vehicle: def __init__(self,speed): 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.fuel = fuel def drive(self,distance): Vehicle.drive(self,distance) print('need %f fuels'%(distance*self.fuel)) b = Bike(15.0) c = Car(80.0,0.012) b.drive(100) c.drive(100) ''' 解释一下代码: __init__函数会在类被创建的时候自动调用,用来初始化类。它的参数,要在创建类的时候提供。 于是我们通过提供一个数值来初始化speed的值。 注意:__init__是python的内置方法,类似的函数名前后是两个下英文划线,如果写错了, 则不会起到原本应有的作用。 class定义后面的括号里表示这个类继承于哪个类。 Bike(Vehicle)就是说Bike是继承自Vehicle中的子类。Vehicle中的属性和方法,Bike都会有。 因为Bike不需要有额外的功能,所以用pass在类中保留空块,什么都不用写。 Car类中,我们又重新定义了__init__和drive函数,这样会覆盖掉它继承自Vehicle的同名函数。 但我们依然可以通过“Vehicle.函数名”来调用它的超类方法。 以此来获得它作为Vehicle所具有的功能。注意,因为是通过类名调用方法, 而不是像之前一样通过对象来调用,所以这里必须提供self的参数值。 在调用超类的方法之后,我们又给Car增加了一个fuel属性,并且在drive中多输出一行信息。 '''
a = "heaven" b = "hell" c = True and a or b print (c) d = False and a or b print (d)
结果很奇怪是不是?
表达式从左往右运算,1和”heaven”做and的结果是”heaven”,再与”hell”做or的结果是”heaven”;0和”heaven”做and的结果是0,再与”hell”做or的结果是”hell”。
抛开绕人的and和or的逻辑,你只需记住,在一个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会让你的代码更简单。但如果你觉得这个技巧带来的副作用已经让你头大了,没关系,用if-else可以做相同的事情。不过在python的某些情况下,你可能没法使用if语句,比如lambda函数中,这时候你可能就需要and-or的帮助了。
什么是lambda函数?呵呵,这是python的高阶玩法,暂且按住不表,以后有机会再说。
a = "" b = "hell" c = (True and [a] or [b])[0] print (c)
#上一次pygame的课中有这样一行代码: #x, y = pygame.mouse.get_pos() # 元组(tuple)也是一种序列,和我们用了很多次的list类似, # 只是元组中的元素在创建之后就不能被修改。 # 如: postion = (1, 2) geeks = ('Sheldon', 'Leonard', 'Rajesh', 'Howard') # 都是元组的实例。它有和list同样的索引、切片、遍历等操作(参见25~27课): print (postion[0]) print('------------') for _ in geeks: print (_) print('------------') print (geeks[1:3]) #其实我们之前一直在用元组,就是在print语句中: print('------------') print ('%s is %d years old' % ('Mike', 23)) #('Mike', 23)就是一个元组。这是元组最常见的用处。 #再来看一下元组作为函数返回值的例子: def get_pos(n): return (n/2, n*2) #得到这个函数的返回值有两种形式,一种是根据返回值元组中元素的个数提供变量: x, y = get_pos(50) print (x) print (y) print('------------') #这就是我们在开头那句代码中使用的方式。 #还有一种方法是用一个变量记录返回的元组: pos = get_pos(50) print (pos[0]) print (pos[1])
import math print(math.pi) print(math.e) print(math.ceil(5.4)) print(math.floor(5.4)) print(math.pow(3,2)) print(math.log(4)) print(math.log(100,10)) print(math.sqrt(9)) print(math.fabs(-34)) #math.sin .cos .tan .asin .acos .atan 弧度单位 #math.degrees(x) 弧度转角度 #math.radians(x) 角度转弧度
什么是正则表达式?在回答这个问题之前,先来看看为什么要有正则表达式。
在编程处理文本的过程中,经常会需要按照某种规则去查找一些特定的字符串。比如知道一个网页上的图片都是叫做’image/8554278135.jpg’之类的名字,只是那串数字不一样;又或者在一堆人员电子档案中,你要把他们的电话号码全部找出来,整理成通讯录。诸如此类工作,如果手工去做,当量大的时候那简直就是悲剧。但你知道这些字符信息有一定的规律,可不可以利用这些规律,让程序自动来做这些无聊的事情?答案是肯定的。这时候,你就需要一种描述这些规律的方法,正则表达式就是干这事的。
正则表达式就是记录文本规则的代码。
所以正则表达式并不是python中特有的功能,它是一种通用的方法。python中的正则表达式库,所做的事情是利用正则表达式来搜索文本。要使用它,你必须会自己用正则表达式来描述文本规则。之前多次有同学表示查找文本的事情经常会遇上,希望能介绍一下正则表达式。既然如此,我们就从正则表达式的基本规则开始说起。
1.
首先说一种最简单的正则表达式,它没有特殊的符号,只有基本的字母或数字。它满足的匹配规则就是完全匹配。例如:有个正则表达式是“hi”,那么它就可以匹配出文本中所有含有hi的字符。
来看如下的一段文字:
Hi, I am Shirley Hilton. I am his wife.
如果我们用“hi”这个正则表达式去匹配这段文字,将会得到两个结果。因为是完全匹配,所以每个结果都是“hi”。这两个“hi”分别来自“Shirley”和“his”。默认情况下正则表达式是严格区分大小写的,所以“Hi”和“Hilton”中的“Hi”被忽略了。
为了验证正则表达式匹配的结果,你可以用以下这段代码做实验:
import re text = "Hi, I am Shirley Hilton. I am his wife.hi" m = re.findall(r"[Hh]i", text) if m: print (m) else: print ('not match')
暂时先不解释这其中代码的具体含义,你只要去更改text和findall中的字符串,就可以用它来检测正则表达式的实际效果。
2.
如果我们只想找到“hi”这个单词,而不把包含它的单词也算在内,那就可以使用“\bhi\b”这个正则表达式。在以前的字符串处理中,我们已经见过类似“\n”这种特殊字符。在正则表达式中,这种字符更多,以后足以让你眼花缭乱。
“\b”在正则表达式中表示单词的开头或结尾,空格、标点、换行都算是单词的分割。而“\b”自身又不会匹配任何字符,它代表的只是一个位置。所以单词前后的空格标点之类不会出现在结果里。
在前面那个例子里,“\bhi\b”匹配不到任何结果。但“\bhi”的话就可以匹配到1个“hi”,出自“his”。用这种方法,你可以找出一段话中所有单词“Hi”,想一下要怎么写。
3.
最后再说一下[]这个符号。在正则表达式中,[]表示满足括号中任一字符。比如“[hi]”,它就不是匹配“hi”了,而是匹配“h”或者“i”。
在前面例子中,如果把正则表达式改为“[Hh]i”,就可以既匹配“Hi”,又匹配“hi”了。
1.r”hi”
这里字符串前面加了r,是raw的意思,它表示对字符串不进行转义。为什么要加这个?你可以试试print “\bhi”和r”\bhi”的区别。
print “\bhi”
hi
print r”\bhi”
\bhi
可以看到,不加r的话,\b就没有了。因为python的字符串碰到“\”就会转义它后面的字符。如果你想在字符串里打“\”,则必须要打“\”。
print “\bhi”
\bhi
这样的话,我们的正则表达式里就会多出很多“\”,让本来就已经复杂的字符串混乱得像五仁月饼一般。但加上了“r”,就表示不要去转义字符串中的任何字符,保持它的原样。
2.re.findall(r”hi”, text)
re是python里的正则表达式模块。findall是其中一个方法,用来按照提供的正则表达式,去匹配文本中的所有符合条件的字符串。返回结果是一个包含所有匹配的list。
3.今天主要说两个符号“.”和“*”,顺带说下“\S”和“?”。
“.”在正则表达式中表示除换行符以外的任意字符。在上节课提供的那段例子文本中:
Hi, I am Shirley Hilton. I am his wife.
如果我们用“i.”去匹配,就会得到
[‘i,’, ‘ir’, ‘il’, ‘is’, ‘if’]
你若是暴力一点,也可以直接用“.”去匹配,看看会得到什么。
与“.”类似的一个符号是“\S”,它表示的是不是空白符的任意字符。注意是大写字符S。
4.在很多搜索中,会用“?”表示任意一个字符, 表示任意数量连续字符,这种被称为通配符。但在正则表达式中,任意字符是用 “ . ” 表示,而 则不是表示字符,而是表示数量:它表示前面的字符可以重复任意多次(包括0次),只要满足这样的条件,都会被表达式匹配上。
结合前面的“.”,用“I.e”去匹配,想一下会得到什么结果?
[‘I am Shirley Hilton. I am his wife’]
是不是跟你想的有些不一样?也许你会以为是
[‘I am Shirle’, ‘I am his wife’]
这是因为“”在匹配时,会匹配尽可能长的结果。如果你想让他匹配到最短的就停止,需要用“.?”。如“I.*?e”,就会得到第二种结果。这种匹配方式被称为懒惰匹配,而原本尽可能长的方式被称为贪婪匹配。
最后留一道习题:
从下面一段文本中,匹配出所有s开头,e结尾的单词。
site sea sue sweet see case sse ssee loses
先来公布上一课习题的答案:
\bs\S*e\b
有的同学给出的答案是”\bs.*?e\b”。测试一下就会发现,有奇怪的’sea sue’和’sweet see’混进来了。既然是单词,我们就不要空格,所以需要用”\S”而不是”.”
昨天有位同学在论坛上说,用正则表达式匹配出了文件中的手机号。这样现学现用很不错。匹配的规则是”1.*?\n”,在这个文件的条件下,是可行的。但这规则不够严格,且依赖于手机号结尾有换行符。今天我来讲讲其他的方法。
匹配手机号,其实就是找出一串连续的数字。更进一步,是11位,以1开头的数字。
还记得正则第1讲里提到的[]符号吗?它表示其中任意一个字符。所以要匹配数字,我们可以用
[0123456789]
由于它们是连续的字符,有一种简化的写法:[0-9]。类似的还有[a-zA-Z]的用法。
还有另一种表示数字的方法:
\d
要表示任意长度的数字,就可以用
[0-9]*
或者
\d*
但要注意的是,表示的任意长度包括0,也就是没有数字的空字符也会被匹配出来。一个与类似的符号+,表示的则是1个或更长。
所以要匹配出所有的数字串,应当用
[0-9]+
或者
\d+
如果要限定长度,就用{}代替+,大括号里写上你想要的长度。比如11位的数字:
\d{11}
想要再把第一位限定为1,就在前面加上1,后面去掉一位:
1\d{10}
OK. 总结一下今天提到的符号:
[0-9]
\d
+
{}
现在你可以去一个混杂着各种数据的文件里,抓出里面的手机号,或是其他你感兴趣的数字了。
1.
我们已经了解了正则表达式中的一些特殊符号,如\b、\d、.、\S等等。这些具有特殊意义的专用字符被称作“元字符”。常用的元字符还有:
\w – 匹配字母或数字或下划线或汉字(我试验下了,发现3.x版本可以匹配汉字,但2.x版本不可以)
\s – 匹配任意的空白符
^ – 匹配字符串的开始
$ – 匹配字符串的结束
2.
\S其实就是\s的反义,任意不是空白符的字符。同理,还有:
\W – 匹配任意不是字母,数字,下划线,汉字的字符
\D – 匹配任意非数字的字符
\B – 匹配不是单词开头或结束的位置
[a]的反义是[^a],表示除a以外的任意字符。[^abcd]就是除abcd以外的任意字符。
3.
之前我们用过*、+、{}来表示字符的重复。其他重复的方式还有:
? – 重复零次或一次
{n,} – 重复n次或更多次
{n,m} – 重复n到m次
正则表达式不只是用来从一大段文字中抓取信息,很多时候也被用来判断输入的文本是否符合规范,或进行分类。来点例子看看:
^\w{4,12}$
这个表示一段4到12位的字符,包括字母或数字或下划线或汉字,可以用来作为用户注册时检测用户名的规则。(但汉字在python2.x里面可能会有问题)
\d{15,18}
表示15到18位的数字,可以用来检测身份证号码
^1\d*x?
以1开头的一串数字,数字结尾有字母x,也可以没有。有的话就带上x。
另外再说一下之前提到的转义字符\。如果我们确实要匹配.或者字符本身,而不是要它们所代表的元字符,那就需要用.或\。\本身也需要用\。
比如”\d+.\d+”可以匹配出123.456这样的结果。
留一道稍稍有难度的习题:
写一个正则表达式,能匹配出多种格式的电话号码,包括
(021)88776543
010-55667890
02584453362
0571 66345673
来说上次的习题:
(021)88776543
010-55667890
02584453362
0571 66345673
一个可以匹配出所有结果的表达式是
(?0\d{2,3}[) -]?\d{7,8}
解释一下:
(?
()在正则表达式里也有着特殊的含义,所以要匹配字符”(“,需要用”(“。?表示这个括号是可有可无的。
0\d{2,3}
区号,0xx或者0xxx
[) -]?
在区号之后跟着的可能是”)”、” “、”-“,也可能什么也没有。
\d{7,8}
7或8位的电话号码
可是,这个表达式虽然能匹配出所有正确的数据(一般情况下,这样已经足够),但理论上也会匹配到错误的数据。因为()应当是成对出现的,表达式中对于左右两个括号并没有做关联处理,例如(02188776543这样的数据也是符合条件的。
我们可以用正则表达式中的“|”符号解决这种问题。“|”相当于python中“or”的作用,它连接的两个表达式,只要满足其中之一,就会被算作匹配成功。
于是我们可以把()的情况单独分离出来:
(0\d{2,3})\d{7,8}
其他情况:
0\d{2,3}[ -]?\d{7,8}
合并:
(0\d{2,3})\d{7,8}|0\d{2,3}[ -]?\d{7,8}
使用“|”时,要特别提醒注意的是不同条件之间的顺序。匹配时,会按照从左往右的顺序,一旦匹配成功就停止验证后面的规则。假设要匹配的电话号码还有可能是任意长度的数字(如一些特殊的服务号码),你应该把
|\d+
这个条件加在表达式的最后。如果放在最前面,某些数据就可能会被优先匹配为这一条件。你可以写个测试用例体会一下两种结果的不同。
关于正则表达式,我们已经讲了5篇,介绍了正则表达式最最皮毛的一些用法。接下来,这个话题要稍稍告一段落。推荐一篇叫做《正则表达式30分钟入门教程》的文章(直接百度一下就能找到,我也会转到论坛上),想要对正则表达式进一步学习的同学可以参考。这篇教程是个标题党,里面涉及了正则表达式较多的内容,30分钟绝对看不完。
random模块的作用是产生随机数。之前的小游戏中用到过random中的randint:
import random
num = random.randint(1,100)
random.randint(a, b)可以生成一个a到b间的随机整数,包括a和b。
a、b都必须是整数,且必须b≥a。当等于的时候,比如:
random.randint(3, 3)
的结果就永远是3
除了randint,random模块中比较常用的方法还有:
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。
比如:
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中常见的几个方法。如果你在程序中需要其中某一个方法,也可以这样写:
from random import randint
randint(1, 10)
另外,有些编程基础的同学可能知道,在随机数中有个seed的概念,需要一个真实的随机数,比如此刻的时间、鼠标的位置等等,以此为基础产生伪随机数。在python中,默认用系统时间作为seed。你也可以手动调用random.seed(x)来指定seed。
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中的另一个很有用的方法:
time.sleep(secs)
它可以让程序暂停secs秒。例如:
import time print (1) time.sleep(2) print (2)
- 读错误信息
来看如下一个例程:
import random
a = 0
for i in range(5):
b = random.choice(range(5))
a += i / b
print a
这个程序中,i从0循环到4,每次循环中,b是0到4中的一个随机数。把i/b的结果累加到a上,最后输出结果。
运行这段程序,有时候会输出结果,有时候却跳出错误信息:
Traceback (most recent call last):
File “C:\Users\Crossin\Desktop\py\test.py”, line 5, in
a += i / b
ZeroDivisionError: integer division or modulo by zero
有些同学看见一段英文提示就慌了。其实没那么复杂,python的错误提示做得还是很标准的。
它告诉我们错误发生在test.py文件中的第6行
a += i / b
这一句上。
这个错误是“ZeroDivisionError”,也就是除零错。
“integer division or modulo by zero”,整数被0除或者被0模(取余数)。
因为0不能作为除数,所以当b随机到0的时候,就会引发这个错误。
知道了原因,就可以顺利地解决掉这个bug。
以后在写代码的时候,如果遇到了错误,先别急着去改代码。试着去读一读错误提示,看看里面都说了些啥。
- 输出调试信息
我们在所有课程的最开始就教了输出函数“print”。它是编程中最简单的调试手段。有的时候,仅从错误提示仍然无法判断出程序错误的原因,或者没有发生错误,但程序的结果就是不对。这种情况下,通过输出程序过程中的一些状态,可以帮助分析程序。
把前面那个程序改造一下,加入一些与程序功能无关的输出语句:
import random
a = 0
for i in range(5):
print 'i: %d' % i
b = random.choice(range(5))
print 'b: %d' % b
a += i / b
print 'a: %d' % a
print
print a
运行后的输出结果(每次结果都会不一样):
i: 0
b: 3
a: 0
i: 1
b: 3
a: 0
i: 2
b: 3
a: 0
i: 3
b: 0
Traceback (most recent call last):
File “C:\Users\Crossin\Desktop\py\test.py”, line 7, in
a += i / b
ZeroDivisionError: integer division or modulo by zero
当b的值为0时,发生了除零错。这次可以更清晰地看出程序出错时的状态。
在真实开发中,程序的结构可能会非常复杂。通过输出调试信息,可以有效地缩小范围、定位错误发生的位置,确认错误发生时的场景,进而找出错误原因。
- python3 在字符方面和 python2 区别较大
import pickle test_data = ['Save me!'.encode('utf8'), 123.456, True] f = open('test.data', 'wb+') pickle.dump(test_data, f) f.close()
import pickle f = open('test.data','rb') test_data = pickle.load(f) f.close() print (test_data)
如果你想保存多个对象,一种方法是把这些对象先全部放在一个序列中,在对这个序列进行存储:
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倍)。因此你可以把上述代码中的 pickle 全部替换为 cPickle,从而提高运行速度(尽管在这个小程序中影响微乎其微)。
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)
list_1 = [1, 2, 3, 5, 8, 13, 22] list_2 = [i for i in list_1 if i % 2 == 0] print (list_2)
我们曾经讲过 Python 中函数的参数传递(见第21课)。最基本的方式是:
定义:
def func(arg1, arg2):
print arg1, arg2
调用:
func(3, 7)
我们把函数定义时的参数名(arg1、arg2)称为形参,调用时提供的参数(3、7)称为实参。
这种方式是根据调用时提供参数的位置进行匹配,要求实参与行参的数量相等,默认按位置匹配参数。调用时,少参数或者多参数都会引起错误。这是最常用的一种函数定义方式。
在调用时,也可以根据形参的名称指定实参。如:
func(arg2=3, arg1=7)
但同样,必须提供所有的参数。看看和func(3, 7)的运行结果有什么不同。
Python 语言还提供了其他一些更灵活的参数传递方式,如:
func2(a=1, b=2, c=3)
func3(*args)
func4(**kargs)
今天我们先说说func2这种方式。
这种方式可以理解为,在一般函数定义的基础上,增加了参数的默认值。这样定义的函数可以和原来一样使用,而当你没有提供足够的参数时,会用默认值作为参数的值。
例如:
定义
def func(arg1=1, arg2=2, arg3=3): print (arg1, arg2, arg3)
# 调用: func(2, 3, 4) func(5, 6) func(7)
输出为
2 3 4
5 6 3
7 2 3
提供的参数会按顺序先匹配前面位置的参数,后面未匹配到的参数使用默认值。
也可以指定其中的部分参数,如:
func(arg2=8) func(arg3=9, arg1=10)
输出为
1 8 3
10 2 9
#或者混合起来用: func(11, arg3=12)
输出为
11 2 12
但要注意,没有指定参数名的参数必须在所有指定参数名的参数前面,且参数不能重复。以下的调用都是错误的:
func(arg1=13, 14)
func(15, arg1=16)
定义参数默认值的函数可以在调用时更加简洁。大量 Python 模块中的方法都运用了这一方式,让使用者在调用时可以提供尽可能少的参数。
接着上一次的内容,来介绍一种更加灵活的参数传递方式:
def func(*args)
这种方式的厉害之处在于,它可以接受任意数量的参数。来看具体例子:
def calcSum(*args): sum = 0 for i in args: sum += i print (sum)
# 调用 calcSum(1,2,3) calcSum(123,456) calcSum()
输出:
6
579
0
在变量前加上星号前缀(*),调用时的参数会存储在一个 tuple(元组)对象中,赋值给形参。在函数内部,需要对参数进行处理时,只要对这个 tuple 类型的形参(这里是 args)进行操作就可以了。因此,函数在定义时并不需要指明参数个数,就可以处理任意参数个数的情况。
不过有一点需要注意,tuple 是有序的,所以 args 中元素的顺序受到赋值时的影响。如:
def printAll(*args): for i in args: print (i,end=' ') print()
#调用: printAll(1,2,3) printAll(3,2,1)
输出:
1 2 3
3 2 1
虽然3个参数在总体上是相同的,但由于调用的顺序不一样,结果也是不同的。
还有一种参数传递方式,既可以按参数名传递参数,不受位置的限制,又可以像 tuple 传递一样不受数量限制。这个我将在下次课中做介绍。
今天来说说最为灵活的一种参数传递方式:
func(**kargs)
上次说的 func(*args) 方式是把参数作为 tuple 传入函数内部。而 func(**kargs) 则是把参数以键值对字典的形式传入。
示例:
def printAll(**kargs): for k in kargs: print (k, ':', kargs[k]) printAll(a=1, b=2, c=3) printAll(x=4, y=5)
输出:
a : 1
c : 3
b : 2
y : 5
x : 4
字典是无序的,所以在输出的时候,并不一定按照提供参数的顺序。同样在调用时,参数的顺序无所谓,只要对应合适的形参名就可以了。于是,采用这种参数传递的方法,可以不受参数数量、位置的限制。
当然,这还不够。Python 的函数调用方式非常灵活,前面所说的几种参数调用方式,可以混合在一起使用。看下面这个例子:
def func(x, y=5, *a, **b): print (x, y, a, b) func(1) func(1,2) func(1,2,3) func(1,2,3,4) func(x=1) func(x=1,y=1) func(x=1,y=1,a=1) func(x=1,y=1,a=1,b=1) func(1,y=1) func(1,2,3,4,a=1) func(1,2,3,4,k=1,t=2,o=3)
输出:
1 5 () {}
1 2 () {}
1 2 (3,) {}
1 2 (3, 4) {}
1 5 () {}
1 1 () {}
1 1 () {‘a’: 1}
1 1 () {‘a’: 1, ‘b’: 1}
1 1 () {}
1 2 (3, 4) {‘a’: 1}
1 2 (3, 4) {‘k’: 1, ‘t’: 2, ‘o’: 3}
在混合使用时,首先要注意函数的写法,必须遵守:
带有默认值的形参(arg=)须在无默认值的形参(arg)之后;
元组参数(*args)须在带有默认值的形参(arg=)之后;
字典参数(*kargs)须在元组参数(args)之后。
可以省略某种类型的参数,但仍需保证此顺序规则。
调用时也需要遵守:
指定参数名称的参数要在无指定参数名称的参数之后;
不可以重复传递,即按顺序提供某参数之后,又指定名称传递。
而在函数被调用时,参数的传递过程为:
1.按顺序把无指定参数的实参赋值给形参;
2.把指定参数名称(arg=v)的实参赋值给对应的形参;
3.将多余的无指定参数的实参打包成一个 tuple 传递给元组参数(*args);
4.将多余的指定参数名的实参打包成一个 dict 传递给字典参数(**kargs)。
是不是乍一看有点绕?没关系,赶紧打开你的编辑器,自行体会一下不同调用方式的用法。然后在未来的编程实践中慢慢熟悉吧。
Python 是一门简洁的语言,lambda 表达式则充分体现了 Python 这一特点。
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 表达式的语法格式:
lambda 参数列表: 表达式
定义 lambda 表达式时,参数列表周围没有括号,返回值前没有 return 关键字,也没有函数名称。
它的写法比 def 更加简洁。但是,它的主体只能是一个表达式,不可以是代码块,甚至不能是命令(print 不能用在 lambda 表达式中)。所以 lambda 表达式能表达的逻辑很有限。
lambda 表达式创建了一个函数对象,可以把这个对象赋值给一个变量进行调用,就像上面的例子中一样。
来看一个复杂一点的例子,把 lambda 表达式用在 def 函数定义中:
def fn(x): return lambda y: x + y a = fn(2) print (a(3))
这里,fn 函数的返回值是一个 lambda 表达式,也就等于是一个函数对象。当以参数2来调用 fn 时,得到的结果就是:
lambda y: 2 + y
a = fn(2) 就相当于:
a = lambda y: 2 + y
所以 a(3) 的结果就是5。
lambda 表达式其实只是一种编码风格,这种写法更加 pythonic。这并不意味着你一定要使用它。事实上,任何可以使用 lambda 表达式的地方,都可以通过普通的 def 函数定义来替代。在一些需要重复使用同一函数的地方,def 可以避免重复定义函数。况且 def 函数更加通用,某些情况可以带来更好地代码可读性。
而对于像 filter、sort 这种需要内嵌函数的方法,lambda 表达式就会显得比较合适。这个我以后会再单独介绍。
当然对于初学者来说,了解 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 在函数内部被重新赋值。但在调用了函数之后,x 的值仍然是50。为什么?
这就得说一下变量的“作用域”:
当函数内部定义了一个变量,无论是作为函数的形参,或是另外定义的变量,它都只在这个函数的内部起作用。函数外即使有和它名称相同的变量,也没有什么关联。这个函数体就是这个变量的作用域。像这样在函数内部定义的变量被称为“局部变量”。
要注意的是,作用域是从变量被定义的位置开始。像这样的写法是有问题的:
def func(): print (y) y = 2 print (y) func()
因为在 y = 2 之前,y 并不存在,调用 y 的值就会出错。
回到开始那个例子:
在函数 func 外部,定义的变量 x,赋值为 50,作为参数传给了函数 func。而在函数 func 内部,变量 x 是形参,它的作用域是整个函数体内部。它与外面的那个 x 没有关系。只不过它的初始值是由外面那个 x 传递过来的。
所以,虽然函数体内部的 x 被重新赋值为 2,也不会影响外面那个 x 的值。
不过有时候,我们希望能够在函数内部去改变一些变量的值,并且这些变量在函数外部同样被使用到。怎么办?
一种方法是,用 return 把改变后的变量值作为函数返回值传递出来,赋值给对应的变量。比如开始的那个例子,可以在函数结尾加上
return x
然后把调用改为
x = func(x)
还有一种方法,就是使用“全局变量”。
在 Python 的函数定义中,可以给变量名前加上 global 关键字,这样其作用域就不再局限在函数块中,而是全局的作用域。
#通过 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)
函数 func 不再提供参数调用。而是通过 global x 告诉程序:这个 x 是一个全局变量。于是函数中的 x 和外部的 x 就成为了同一个变量。这一次,当 x 在函数 func 内部被重新赋值后,外部的 x 也随之改变。
前面讲的局部变量和全局变量是 Python 中函数作用域最基本的情况。实际上,还有一些略复杂的情况,比如:
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)
程序可以正常运行。虽然没有指明 global,函数内部还是使用到了外部定义的变量。然而一旦加上
x = 2
这句,程序就会报错。因为这时候,x 成为一个局部变量,它的作用域从定义处开始,到函数体末尾结束。
建议在写代码的过程中,显式地通过 global 来使用全局变量,避免在函数中直接使用外部变量。
来看两个问题:
-
假设有一个数列,如何把其中每一个元素都翻倍?
-
假设有两个数列,如何求和?
第一个问题,普通程序员大概会这么写:
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)
这是我在《【Python 第66课】列表综合》里说到的方法,微信中回复 66 可以查看。
今天来说另一种 Python 程序员常用的写法 — map:
lst_1 = [1,2,3,4,5,6] def double_func(x): return x * 2 lst_2 = map(double_func, lst_1) print (list(lst_2))
map 是 Python 自带的内置函数,它的作用是把一个函数应用在一个(或多个)序列上,把列表中的每一项作为函数输入进行计算,再把计算的结果以列表的形式返回。
map 的第一个参数是一个函数,之后的参数是序列,可以是 list、tuple。
所以刚刚那个问题也可以写成:
lst_1 = (1,2,3,4,5,6) lst_2 = map(lambda x: x * 2, lst_1) print (list(lst_2))
这里原数据改为了元组,函数用 lambda 表达式替代。可参考《【Python 第70课】lambda 表达式》,微信中回复 70。
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 (list(lst_3))
map 中的函数会从对应的列表中依次取出元素,作为参数使用,同样将结果以列表的形式返回。所以要注意的是,函数的参数个数要与 map 中提供的序列组数相同,即函数有几个参数,就得有几组数据。
对于每组数据中的元素个数,如果有某组数据少于其他组,map 会以 None 来补全这组参数。
此外,当 map 中的函数为 None 时,结果将会直接返回参数组成的列表。如果只有一组序列,会返回元素相同的列表,如果有多组数列,将会返回每组数列中,对应元素构成的元组所组成的列表。听上去很绕口是不是……代码试试看就明白了:
def f(x, y): return (x, y) lst_1 = [1,2,3,4,5,6] lst_2 = [1,3,5,7,9,11] lst_3 = list(map(f, lst_1, lst_2)) print (lst_3)
def f(x, y): return (x, y) l1 = [ 0, 1, 2, 3, 4, 5, 6 ] l2 = [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ] list(map(f, l1, l2))
上次说了 Python 中一个比较有意思的内置函数 map,今天再来介绍另一个类似的函数:reduce
map 可以看作是把一个序列根据某种规则,映射到另一个序列。reduce 做的事情就是把一个序列根据某种规则,归纳为一个输出。
上栗子。以前我们给过一个习题,求1累加到100的和。寻常的做法大概是这样:
sum = 0 for i in range(1, 101): sum += i print (sum)
#如果用 reduce 函数,就可以写成: from functools import reduce lst = range(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 做到),可以选择使用 reduce 来实现。相对可以使代码更简洁。
我觉得,写代码的可读性是很重要的事情,简洁易懂的代码,既容易让别人看懂,也便于自己以后的维护。同时,较少的代码也意味着比较高的开发效率和较少的出错可能。应尽量避免写混乱冗长的代码。当然,也不用为了一味追求代码的精简,总是想方设法把代码写在一行里。那就又走了另一个极端,同样也缺乏可读性。而至于是否使用类似 map、reduce 这样的方法,也是根据需要和个人习惯,我认为并没有一定的规则限制。
多线程
很多人使用 python 编写“爬虫”程序,抓取网上的数据。 举个例子,通过豆瓣的 API 抓取 30 部影片的信息:
Python3 多线程 http://www.runoob.com/python3/python3-multithreading.html
import urllib3, time import urllib time_start = time.time() data = [] for i in range(30): print ('request movie:', i) id = 1764796 + i http = urllib3.PoolManager() r = http.request('url','https://api.douban.com/v2/movie/subject/%d' % id) data.append(r) print (i, time.time() - time_start) print ('data:', len(data))
程序里用了 time.time() 来计算抓取花费的时间。运行一遍,大约需要十几秒(根据网络情况会有差异)。
如果我们想用这套代码抓取几万部电影,就算中间不出什么状况,估计也得花上好几个小时。
然而想一下,我们抓一部电影信息的过程是独立,并不依赖于其他电影的结果。因此没必要排好队一部一部地按顺序来。那么有没有什么办法可以同时抓取好几部电影?
答案就是:多线程。
来说一种简单的多线程方法:
python 里有一个 thread 模块,其中提供了一个函数:
start_new_thread(function, args[, kwargs])
function 是开发者定义的线程函数,
args 是传递给线程函数的参数,必须是tuple类型,
kwargs 是可选参数。
调用 start_new_thread 之后,会创建一个新的线程,来执行 function 函数。而代码原本的主线程将继续往下执行,不再等待 function 的返回。通常情况,线程在 function 执行完毕后结束。
改写一下前面的代码,将抓取的部分放在一个函数中:
import urllib3, time, _thread def get_content(i): id = 1764796 + i http = urllib3.PoolManager() r = http.request('url','https://api.douban.com/v2/movie/subject/%d' % id) data.append(r) 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,)) input('press ENTER to exit...\n')
因为主线程不在等待函数返回结果,所以在代码最后,增加了 raw_input,避免程序提前退出。
从输出结果可以看出:
在程序刚开始运行时,已经发送所有请求
收到的请求并不是按发送顺序,先收到就先显示
总共用时两秒多
data 里同样记录了所有30条结果
所以,对于这种耗时长,但又独立的任务,使用多线程可以大大提高运行效率。但在代码层面,可能额外需要做一些处理,保证结果正确。如上例中,如果需要电影信息按 id 排列,就要另行排序。
多线程通常会用在网络收发数据、文件读写、用户交互等待之类的操作上,以避免程序阻塞,提升用户体验或提高执行效率。
多线程的实现方法不止这一种。另外多线程也会带来一些单线程程序中不会出现的问题。这里只是简单地开个头。
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/182676.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...