什么是模块?
常见的场景:一个模块就是一个包含了python定义和声明的文件,文件名就是模块名字加上.py的后缀。
但其实import加载的模块分为四个通用类别:
- 使用python编写的代码(.py文件)
- 已经被编译为共享库或DLL的C或C++扩展
- 包好一组模块的包
- 使用C编写并链接到python解释器的内置模块
为何要使用模块?
如果你退出python解释器然后重新进入,那么你之前定义的函数或者变量都将丢失,因此我们通常将程序写到文件中以便永久保存下来,需要时就通过python test.py方式去执行,此时test.py被称为脚本script。
随着程序的发展,功能越来越多,为了方便管理,我们通常将程序分成一个个的文件,这样做程序的结构更加清晰,方便管理,这时候我们不仅仅可以把这些文件当作脚本去执行,还可以把它们当作模块来导入到其他的模块中,实现了功能的重复利用。
模块的导入和使用
模块的导入应该在程序最开始的地方。
常用模块
collections模块
在内置数据类型(dict,list,set,tuple)的基础上,collections模块还提供了几个额外的数据类型:counter,deque,defaultdict,namedtuple,OrderedDict等
- namedtuple: 生成可以使用名字来访问元素内容的tuple
- deque: 双端队列,可以快速的从另外一侧追加和推出对象
- Counter: 计数器,主要用来计数
- OrderedDict: 有序字典
- defaultdict: 带有默认值的字典
namedtuple
我们知道tuple
可以表示不变集合,例如,一个点的二维坐标就可以表示成:
>>> p = (1, 2)
但是,看到(1, 2),很难看出这个tuple是用来表示一个坐标的。
这时,namedtuple
就派上了用场:
>>> from collections import namedtuple>>> Point = namedtuple('Point', ['x', 'y'])>>> p = Point(1, 2)>>> p.x1>>> p.y2
类似的,如果要用坐标和半径表示一个圆,也可以用namedtuple
定义:
#namedtuple('名称', [属性list]):Circle = namedtuple('Circle', ['x', 'y', 'r'])
deque
使用list存储数据时,按索引访问元素很快,但是插入和删除元素就很慢了,因为list是线性存储,数据量大的时候,插入和删除效率很低。
deque是为了高效实现插入和删除操作的双向列表,适合用于队列和栈:
>>> from collections import deque>>> q = deque(['a', 'b', 'c'])>>> q.append('x')>>> q.appendleft('y')>>> qdeque(['y', 'a', 'b', 'c', 'x'])
deque除了实现list的append()
和pop()
外,还支持appendleft()
和popleft()
,这样就可以非常高效地往头部添加或删除元素。
OrderedDict
使用dict时,Key是无序的。在对dict做迭代时,我们无法确定Key的顺序。
如果要保持Key的顺序,可以用OrderedDict
:
>>> from collections import OrderedDict>>> d = dict([('a', 1), ('b', 2), ('c', 3)])>>> d # dict的Key是无序的{'a': 1, 'c': 3, 'b': 2}>>> od = OrderedDict([('a', 1), ('b', 2), ('c', 3)])>>> od # OrderedDict的Key是有序的OrderedDict([('a', 1), ('b', 2), ('c', 3)])
注意,OrderedDict
的Key会按照插入的顺序排列,不是Key本身排序:
>>> od = OrderedDict()>>> od['z'] = 1>>> od['y'] = 2>>> od['x'] = 3>>> od.keys() # 按照插入的Key的顺序返回['z', 'y', 'x']
defaultdict
有如下值集合 [
11
,
22
,
33
,
44
,
55
,
66
,
77
,
88
,
99
,
90.
..],将所有大于
66
的值保存至字典的第一个key中,将小于
66
的值保存至第二个key的值中。
即: {
'k1'
: 大于
66
,
'k2'
: 小于
66
}
values = [11, 22, 33,44,55,66,77,88,99,90]my_dict = {}for value in values: if value>66: if my_dict.has_key('k1'): my_dict['k1'].append(value) else: my_dict['k1'] = [value] else: if my_dict.has_key('k2'): my_dict['k2'].append(value) else: my_dict['k2'] = [value]
from collections import defaultdictvalues = [11, 22, 33,44,55,66,77,88,99,90]my_dict = defaultdict(list)for value in values: if value>66: my_dict['k1'].append(value) else: my_dict['k2'].append(value)
使用dict
时,如果引用的Key不存在,就会抛出KeyError
。如果希望key不存在时,返回一个默认值,就可以用defaultdict
:
>>> from collections import defaultdict>>> dd = defaultdict(lambda: 'N/A')>>> dd['key1'] = 'abc'>>> dd['key1'] # key1存在'abc'>>> dd['key2'] # key2不存在,返回默认值'N/A'
Counter
Counter类的目的是用来跟踪值出现的次数。它是一个无序的容器类型,以字典的键值对形式存储,其中元素作为key,其计数作为value。计数值可以是任意的Interger(包括0和负数)。Counter类和其他语言的bags或multisets很相似。
c = Counter('abcdeabcdabcaba')print c输出:Counter({'a': 5, 'b': 4, 'c': 3, 'd': 2, 'e': 1})
时间模块
和时间有关系的,我们就要用到时间模块,在使用模块之前,应该先导入这个模块
#常用方法time.sleep(sec)#推迟指定的时间后运行,单位为秒time.time()#获取当前的时间戳
表示时间的三种方法
在python中,通常有这三种方式来表示时间:
- 字符串时间(format_string)————格式化时间:给人看的
- 时间戳时间(tinestamp)—————-float时间:给计算机看的
- 结构化时间(struct_time)—————元组:计算用的
(1)时间戳(timestamp)
通常来说,时间戳表示的是从1970年1月1日00:00:00开始按秒计算的偏移量,我们运行type(time.time()),返回的是float类型
(2)格式化的时间字符串(format_string)
%y 两位数的年份表示(00-99)%Y 四位数的年份表示(000-9999)%m 月份(01-12)%d 月内中的一天(0-31)%H 24小时制小时数(0-23)%I 12小时制小时数(01-12)%M 分钟数(00=59)%S 秒(00-59)%a 本地简化星期名称%A 本地完整星期名称%b 本地简化的月份名称%B 本地完整的月份名称%c 本地相应的日期表示和时间表示%j 年内的一天(001-366)%p 本地A.M.或P.M.的等价符%U 一年中的星期数(00-53)星期天为星期的开始%w 星期(0-6),星期天为星期的开始%W 一年中的星期数(00-53)星期一为星期的开始%x 本地相应的日期表示%X 本地相应的时间表示%Z 当前时区的名称%% %号本身
(3)结构化时间(struct_time)
struct_time元组共有9个元素:(年,月,日,时,分,秒,周几,一年中第几天,是否是夏令时)
索引(Index) | 属性(Attribute) | 值(Values) |
---|---|---|
0 | tm_year(年) | 比如2011 |
1 | tm_mon(月) | 1 - 12 |
2 | tm_mday(日) | 1 - 31 |
3 | tm_hour(时) | 0 - 23 |
4 | tm_min(分) | 0 - 59 |
5 | tm_sec(秒) | 0 - 60 |
6 | tm_wday(weekday) | 0 - 6(0表示周一) |
7 | tm_yday(一年中的第几天) | 1 - 366 |
8 | tm_isdst(是否是夏令时) | 默认为0 |
首先,我们导入time模块,来认识一下python中表示时间的几种格式:
#导入时间模块import time#时间戳print(time.time())#输出#1535631214.1895318#格式化时间(时间字符串)print(time.strftime('%Y-%m-%d %H:%M:%S'))print(time.strftime('%Y-%m-%d-%H-%M-%S'))#输出#2018-08-30 20:13:342018-08-30-20-13-34#时间元组:localtime将一个时间戳转换为当前时区的struct_timeprint(time.localtime())#输出#time.struct_time(tm_year=2018, tm_mon=8, tm_mday=30, tm_hour=20, tm_min=13, tm_sec=34, tm_wday=3, tm_yday=242, tm_isdst=0)
几种格式之间的转换
#时间戳-->结构化时间#time.gmtime(时间戳) #UTC时间,与英国伦敦当地时间一致#time.localtime(时间戳) #当地时间。例如我们现在在北京执行这个方法:与UTC时间相差8小时,UTC时间+8小时 = 北京时间 >>>time.gmtime(1500000000)time.struct_time(tm_year=2017, tm_mon=7, tm_mday=14, tm_hour=2, tm_min=40, tm_sec=0, tm_wday=4, tm_yday=195, tm_isdst=0)>>>time.localtime(1500000000)time.struct_time(tm_year=2017, tm_mon=7, tm_mday=14, tm_hour=10, tm_min=40, tm_sec=0, tm_wday=4, tm_yday=195, tm_isdst=0)#结构化时间-->时间戳 #time.mktime(结构化时间)>>>time_tuple = time.localtime(1500000000)>>>time.mktime(time_tuple)1500000000.0
#结构化时间-->字符串时间#time.strftime("格式定义","结构化时间") 结构化时间参数若不传,则显示当前时间>>>time.strftime("%Y-%m-%d %X")'2017-07-24 14:55:36'>>>time.strftime("%Y-%m-%d",time.localtime(1500000000))'2017-07-14'#字符串时间-->结构化时间#time.strptime(时间字符串,字符串对应格式)>>>time.strptime("2017-03-16","%Y-%m-%d")time.struct_time(tm_year=2017, tm_mon=3, tm_mday=16, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=3, tm_yday=75, tm_isdst=-1)>>>time.strptime("07/24/2017","%m/%d/%Y")time.struct_time(tm_year=2017, tm_mon=7, tm_mday=24, tm_hour=0, tm_min=0, tm_sec=0, tm_wday=0, tm_yday=205, tm_isdst=-1)
#结构化时间 --> %a %b %d %H:%M:%S %Y串#time.asctime(结构化时间) 如果不传参数,直接返回当前时间的格式化串>>>time.asctime(time.localtime(1500000000))'Fri Jul 14 10:40:00 2017'>>>time.asctime()'Mon Jul 24 15:18:33 2017'#时间戳 --> %a %b %d %H:%M:%S %Y串#time.ctime(时间戳) 如果不传参数,直接返回当前时间的格式化串>>>time.ctime()'Mon Jul 24 15:19:07 2017'>>>time.ctime(1500000000)'Fri Jul 14 10:40:00 2017'
import timetrue_time=time.mktime(time.strptime('2017-09-11 08:30:00','%Y-%m-%d %H:%M:%S'))time_now=time.mktime(time.strptime('2017-09-12 11:00:00','%Y-%m-%d %H:%M:%S'))sub_time=time_now-true_timestruct_time=time.gmtime(sub_time)print('过去了%d年%d月%d天%d小时%d分钟%d秒'%(struct_time.tm_year-1970,struct_time.tm_mon-1,struct_time.tm_mday-1,struct_time.tm_hour,struct_time.tm_min,struct_time.tm_sec))#注意最后得到的时间差元组,年要减去1970,月要减1,因为实际0是1月,日也要减1,因为0也是一个月的第一天,
random模块
import randomprint(random.random())#输出随机的一个大于0小于1的小数#输出:0.5012863321867318print(random.uniform(0,10))#输出随机的一个大于0小于10的小数,范围自己设定#输出:6.748984382495321print(random.randint(1,5))#输出所及的一个大于等于1小于等于5的整数,范围自己定#输出:1print(random.randrange(1,10,2))#输出大于等于1小于等于10的奇数#输出:3print(random.choice([1,'23',[2,4]]))#随机选择一个返回#输出[2,4]print(random.sample([1,'23',[2,4]],2))#随机选择给定个数返回,返回个数由第二个参数决定#输出['23',1]iter=[1,2,3,4,5,6,7,8,9]random.shuffle(iter)#随机打乱列表顺序print(iter)#在原列表的基础上打乱,不创建新列表#输出[6, 9, 4, 8, 3, 5, 1, 7, 2]
练习:生成随机验证码
import randomdef identifing_code(): codelist=[] for i in range(5): alpha=chr(random.randint(65,90)) number=str(random.randint(0,9)) codelist.append(random.choice([alpha,number])) code=''.join(codelist) return codeprint(identifing_code())
os模块
os模块是与操作系统交互的一个接口
os.makedirs('dirname1/dirname2') 可生成多层递归目录os.removedirs('dirname1') 若目录为空,则删除,并递归到上一级目录,如若也为空,则删除,依此类推os.mkdir('dirname') 生成单级目录;相当于shell中mkdir dirnameos.rmdir('dirname') 删除单级空目录,若目录不为空则无法删除,报错;相当于shell中rmdir dirnameos.listdir('dirname') 列出指定目录下的所有文件和子目录,包括隐藏文件,并以列表方式打印os.remove() 删除一个文件os.rename("oldname","newname") 重命名文件/目录os.stat('path/filename') 获取文件/目录信息os.system("bash command") 运行shell命令,直接显示os.popen("bash command).read() 运行shell命令,获取执行结果os.getcwd() 获取当前工作目录,即当前python脚本工作的目录路径os.chdir("dirname") 改变当前脚本工作目录;相当于shell下cdos.pathos.path.abspath(path) 返回path规范化的绝对路径os.path.split(path) 将path分割成目录和文件名二元组返回 os.path.dirname(path) 返回path的目录。其实就是os.path.split(path)的第一个元素 os.path.basename(path) 返回path最后的文件名。如何path以/或\结尾,那么就会返回空值。即os.path.split(path)的第二个元素os.path.exists(path) 如果path存在,返回True;如果path不存在,返回Falseos.path.isabs(path) 如果path是绝对路径,返回Trueos.path.isfile(path) 如果path是一个存在的文件,返回True。否则返回Falseos.path.isdir(path) 如果path是一个存在的目录,则返回True。否则返回Falseos.path.join(path1[, path2[, ...]]) 将多个路径组合后返回,第一个绝对路径之前的参数将被忽略os.path.getatime(path) 返回path所指向的文件或者目录的最后访问时间os.path.getmtime(path) 返回path所指向的文件或者目录的最后修改时间os.path.getsize(path) 返回path的大小
注意:os.stat('path/filename') 获取文件/目录信息 的结构说明
stat 结构:st_mode: inode 保护模式st_ino: inode 节点号。st_dev: inode 驻留的设备。st_nlink: inode 的链接数。st_uid: 所有者的用户ID。st_gid: 所有者的组ID。st_size: 普通文件以字节为单位的大小;包含等待某些特殊文件的数据。st_atime: 上次访问的时间。st_mtime: 最后一次修改的时间。st_ctime: 由操作系统报告的"ctime"。在某些系统上(如Unix)是最新的元数据更改的时间,在其它系统上(如Windows)是创建时间(详细信息参见平台的文档)。
os.sep 输出操作系统特定的路径分隔符,win下为"\\",Linux下为"/"os.linesep 输出当前平台使用的行终止符,win下为"\r\n",Linux下为"\n"os.pathsep 输出用于分割文件路径的字符串 win下为;,Linux下为:os.name 输出字符串指示当前使用平台。win->'nt'; Linux->'posix'
sys模块
sys模块是与python解释器交互的一个接口
sys.argv 命令行参数List,第一个元素是程序本身路径sys.exit(n) 退出程序,正常退出时exit(0),错误退出sys.exit(1)sys.version 获取Python解释程序的版本信息sys.path 返回模块的搜索路径,初始化时使用PYTHONPATH环境变量的值sys.platform 返回操作系统平台名称
import systry: sys.exit(1)except SystemExit as e: print(e)
序列化模块
什么叫序列化——将原本的字典,列表等内容转换成一个字符串的过程就叫做序列化
为什么要有序列化模块
比如,我们在python代码中计算的一个数据需要给另外一段程序使用,那我们怎么给?
现在我们能想到的办法就是存储在文件里,然后另一个python程序再从文件里读出来。
但是我们知道对于文件来说是没有字典这个概念的,所以我们只能将数据转换成字典放到文件中。
你一定会问,将字典转换成一个字符串很简单,就是str(dic)就可以办到了,为什么我们还要学习序列化模块呢?
没错,序列化的过程就是 从dic转化成str(dic)的过程,现在你可以通过str(dic),将一个名为dic的字典转换成一个字符串,但是你要怎么把一个字符串转换成字典呢?
有的人一定已经想到了eval()方法,如果我们将一个字符串类型的字典str_dic传给eval,就会得到一个返回的字典类型了。
eval()函数十分强大,但是eval是做什么的?官方demo解释为:将字符串str当成有效的表达式来求值并且返回计算结果
但是,强大的函数有代价,安全性是其最大的缺点。
想象一下,如果我们从文件中读出的不是一份数据结构,而是一句‘删除文件’类似的破坏性语句,那么后果实在不堪设想。
而使用eval就要承担这个风险
所以,不推荐使用eval方法来进行反序列化操作(将str转化成python中的数据结构)
序列化的目的
- 以某种存储形式使自定义对象持久化
- 将对象从一个地方传递到另一个地方
- 使程序更具维护性
json
- 通用的序列化格式,各种语言都能用
- 只有很少一部分数据类型能够通过json转化成字符串
pickle
- 所有的python中的数据类型都可以转化成字符串形式
- pickle序列化的内容只有python能理解
- 且部分反序列化依赖python代码
shelve
- 序列化句柄
- 使用句柄直接操作,非常方便
json
json模块提供了四个功能:dumps,loads,dump,load
import json#能被json序列化的数据类型只有数字,字符串,字典,列表,元组#其中元组被序列化后成为一个列表,在反序列化后输出一个列表#json dumps序列化方法 loads反序列化方法dic={ 'a':1,'b':2}print(dic,type(dic))#{'b': 2, 'a': 1}#dumpsstr_d=json.dumps(dic)#字典被序列化print(str_d,type(str_d))#{"b": 2, "a": 1} #loadsdic_d=json.loads(str_d)#字符串被反序列化print(dic_d,type(dic_d))#{'b': 2, 'a': 1} #dumpdic={1:'a',2:'b'}f=open('file',mode='w',encoding='utf-8')json.dump(dic,f)#就收一个文件句柄,将字典序列化后写入文件f.close()#loadwith open('file',encoding='utf-8') as f1: ret=json.load(f1)#接收一个文件句柄,将文件中的字典反序列化后返回 print(ret)
可见dumps和loads是在将数据类型序列化反序列化后存储在内存中,而dump和load将数据类型序列化反序列化后写入写出文件
#若被序列化的数据中出现中文,如:li=['中国','america']with open('file',mode='w',encoding='utf-8') as f: json.dump(li,f)#当它被序列化写入文件后,文件中显示的是["\u4e2d\u56fd", "america"],可见中文变成了十六进制数,但这并不影响它的反序列化,看下面:with open('file',mode='r',encoding='utf-8') as f1: ret=json.load(f1) print(ret)#输出的依然是['中国','america']#如果想在文件中看到中文,就必须将dump方法的ensure_ascii参数设置为Falsewith open('file',mode='w',encoding='utf-8') as f: json.dump(li,f,ensure_ascii=False)#这样我们打开文件显示的就是['中国','america']
Serialize obj to a JSON formatted str.(字符串表示的json对象) Skipkeys:默认值是False,如果dict的keys内的数据不是python的基本类型(str,unicode,int,long,float,bool,None),设置为False时,就会报TypeError的错误。此时设置成True,则会跳过这类key ensure_ascii:,当它为True的时候,所有非ASCII码字符显示为\uXXXX序列,只需在dump时将ensure_ascii设置为False即可,此时存入json的中文即可正常显示。) If check_circular is false, then the circular reference check for container types will be skipped and a circular reference will result in an OverflowError (or worse). If allow_nan is false, then it will be a ValueError to serialize out of range float values (nan, inf, -inf) in strict compliance of the JSON specification, instead of using the JavaScript equivalents (NaN, Infinity, -Infinity). indent:应该是一个非负的整型,如果是0就是顶格分行显示,如果为空就是一行最紧凑显示,否则会换行且按照indent的数值显示前面的空白分行显示,这样打印出来的json数据也叫pretty-printed json separators:分隔符,实际上是(item_separator, dict_separator)的一个元组,默认的就是(‘,’,’:’);这表示dictionary内keys之间用“,”隔开,而KEY和value之间用“:”隔开。 default(obj) is a function that should return a serializable version of obj or raise TypeError. The default simply raises TypeError. sort_keys:将数据根据keys的值进行排序。 To use a custom JSONEncoder subclass (e.g. one that overrides the .default() method to serialize additional types), specify it with the cls kwarg; otherwise JSONEncoder is used.
pickle
用于序列化的两个模块pickle和json
- json,用于字符串和部分python数据类型间进行转换
- pickle,用于python特有的类型和python的数据类型间进行转换
pickle模块也提供和json模块相同的四个方法,dumps,loads,dump,load,(不仅可以序列化字典,列表。。。。。可以把python中任意的数据类型都序列化)
import pickles={ 'a','b','c','d','e'}ret=pickle.dumps(s)#集合也可以被pickle序列化print(ret)#输出b'\x80\x03cbuiltins\nset\nq\x00]q\x01(X\x01\x00\x00\x00cq\x02X\x01\x00\x00\x00bq\x03X\x01\x00\x00\x00aq\x04X\x01\x00\x00\x00dq\x05X\x01\x00\x00\x00eq\x06e\x85q\x07Rq\x08.' ret_set=pickle.loads(ret)#反序列化print(ret_set)s1={ 'a','b'}f=open('file','wb')#将集合序列化后写进文件,注意写入模式必须为wbpickle.dump(s1,f)#和json一样,dump方法第一个参数为被序列化的对象,第二个参数为文件句柄f.close()f1=open('file',mode='rb')#反序列化注意读取模式也必须为rbresult=pickle.load(f1)f1.close()print(result)
这时候机智的你又要说了,既然pickle如此强大,为什么还要学json呢?
这里我们要说明一下,json是一种所有的语言都可以识别的数据结构,如果我们将一个字典或者序列化成了一个json存在文件里,那么java代码或者js代码都可以拿来用,但是如果我们用pickle进行序列化,其他语言就不能读懂这是什么了。所以,如果你序列化的内容是列表或者字典,我们非常推荐你使用json模块,但是如果出于某种原因你不得不序列化其他的数据类型,而未来你还会用python对这个数据进行反序列化的话,那么就可以使用pickle。
shelve
shelve也是python提供给我们的序列化工具,比pickle用起来更简单简单一些。
shelve只提供给我们一个open方法,是用key来访问的,使用起来和字典类似。
import shelvef=shelve.open('shelve_file')f['key']={ 'a':1,'b':2,'c':3}#直接对文件句柄操作,就可以存入数据f.close()#执行完以上操作会生成三个文件,但是文件内容是看不懂的 f1=shelve.open('shelve_file')existing=f1['key']#取出数据的时候也只需要用‘key’获取即可,但是如果key不存在就会报错f1.close()print(existing)
re模块
假如现在用python写一段代码,类似:
phone_number=input('please input your phone number')
你怎么判断这个phone_number是合法的呢?
根据手机号码一共11位并且只以12,14,15,18开头的数字这些特点,我们用python写了如下代码:
while True: phone_number = input('please input your phone number : ') if len(phone_number) == 11 \ and phone_number.isdigit()\ and (phone_number.startswith('13') \ or phone_number.startswith('14') \ or phone_number.startswith('15') \ or phone_number.startswith('18')): print('是合法的手机号码') else: print('不是合法的手机号码')
这时你的写法,现在展示一下我的写法:
import rephone_number = input('please input your phone number : ')if re.match('^(13|14|15|18)[0-9]{9}$',phone_number): print('是合法的手机号码')else: print('不是合法的手机号码')
对比上面的两种写法,明显第二种比较方便。
所以今天我们要学习python里的re模块和正则表达式。正则表达式不仅在python领域,在整个编程届都占有举足轻重的地位。
正则表达式本身也和python没有什么关系,就是匹配字符串内容的一种规则。
官方定义:正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。
正则表达式
一说规则我已经知道你很晕了,现在就让我们先来看一些实际的应用。在线测试工具
首先你要知道的是,谈到正则,就只和字符串相关了。在我给你提供的工具中,你输入的每一个字都是一个字符串。其次,如果在一个位置的值,不会出现什么变化,那么是不需要规则的。
比如你要用’1‘去匹配’1‘,或者用’2‘去匹配’2‘,直接就可以匹配上。这连python的字符串操作都可以轻松做到,那么在之后我们更多要考虑的是在同一位置上可以出现的字符串的范围
字符组 : [字符组]在同一个位置可能出现的各种字符组成了一个字符组,在正则表达式中用[]表示字符分为很多类,比如数字、字母、标点等等。假如你现在要求一个位置"只能出现一个数字",那么这个位置上的字符只能是0、1、2...9这10个数之一。
正则 | 待匹配字符 | 匹配 结果 | 说明 |
[0123456789] | 8 | True | 在一个字符组里枚举合法的所有字符,字符组里的任意一个字符 和"待匹配字符"相同都视为可以匹配 |
[0123456789] | a | False | 由于字符组中没有"a"字符,所以不能匹配 |
[0-9] | 7 | True | 也可以用-表示范围,[0-9]就和[0123456789]是一个意思 |
[a-z] | s | True | 同样的如果要匹配所有的小写字母,直接用[a-z]就可以表示 |
[A-Z] | B | True | [A-Z]就表示所有的大写字母 |
[0-9a-fA-F] | e | True | 可以匹配数字,大小写形式的a~f,用来验证十六进制字符 |
字符:
元字符 | 匹配内容 |
. | 匹配除换行符以外的任意字符 |
\w | 匹配字母或数字或下划线 |
\s | 匹配任意的空白符 |
\d | 匹配数字 |
\n | 匹配一个换行符 |
\t | 匹配一个制表符 |
\b | 匹配一个单词的结尾 |
^ | 匹配字符串的开始 |
$ | 匹配字符串的结尾 |
\W | 匹配非字母或数字或下划线 |
\D | 匹配非数字 |
\S | 匹配非空白符 |
a|b | 匹配字符a或字符b |
() | 匹配括号内的表达式,也表示一个组 |
[...] | 匹配字符组中的字符 |
[^...] | 匹配除了字符组中字符的所有字符 |
量词:
量词 | 用法说明 |
* | 重复零次或更多次 |
+ | 重复一次或更多次 |
? | 重复零次或一次 |
{n} | 重复n次 |
{n,} | 重复n次或更多次 |
{n,m} | 重复n到m次 |
. ^ $
正则 | 待匹配字符 | 匹配结果 | 说明 |
海. | 海燕海娇海东 | 海燕海娇海东 | 匹配所有"海."的字符 |
^海. | 海燕海娇海东 | 海燕 | 只从开头匹配"海." |
海.$ | 海燕海娇海东 | 海东 | 只匹配结尾的"海.$" |
* + ? { }
正则 | 待匹配字符 | 匹配结果 | 说明 |
李.? | 李杰和李莲英和李二棍子 | 李杰 李莲李二 | ?表示重复零次或一次,即只匹配"李"后面一个任意字符 |
李.* | 李杰和李莲英和李二棍子 | 李杰和李莲英和李二棍子 | *表示重复零次或多次,即匹配"李"后面0或多个任意字符 |
李.+ | 李杰和李莲英和李二棍子 | 李杰和李莲英和李二棍子 | +表示重复一次或多次,即只匹配"李"后面1个或多个任意字符 |
李.{1,2} | 李杰和李莲英和李二棍子 | 李杰和 李莲英李二棍 | {1,2}匹配1到2次任意字符 |
注意,前面的*,+,?等都是贪婪匹配,也就是尽可能匹配,后面加?使其变成惰性匹配
正则 | 待匹配字符 | 匹配结果 | 说明 |
李.*? | 李杰和李莲英和李二棍子 | 李李李 | 惰性匹配 |
字符集 [ ] [^ ]
正则 | 待匹配字符 | 匹配结果 | 说明 |
李[杰莲英二棍子]* | 李杰和李莲英和李二棍子 | 李杰 李莲英李二棍子 | 表示匹配"李"字后面[杰莲英二棍子]的字符任意次 |
李[^和]* | 李杰和李莲英和李二棍子 | 李杰 李莲英李二棍子 | 表示匹配一个不是"和"的字符任意次 |
[\d] | 456bdha3 | 4 563 | 表示匹配任意一个数字,匹配到4个结果 |
[\d]+ | 456bdha3 | 456 3 | 表示匹配任意个数字,匹配到2个结果 |
分组()或 | [^]
身份证号码是一个长度位15或18个数字字符组成的字符串,如果是15位则全部由数字组成,首位不能为0,如果是18位,则前17位全部是数字,末位可能是数字或x,下面我们尝试用正则来表示:
正则 | 待匹配字符 | 匹配结果 | 说明 |
^[1-9]\d{13,16}[0-9x]$ | 110101198001017032 | 110101198001017032 | 表示可以匹配一个正确的身份证号 |
^[1-9]\d{13,16}[0-9x]$ | 1101011980010170 | 1101011980010170 | 表示也可以匹配这串数字,但这并不是一个正确的身份证号码,它是一个16位的数字 |
^[1-9]\d{14}(\d{2}[0-9x])?$ | 1101011980010170 | False | 现在不会匹配错误的身份证号了 ()表示分组,将\d{2}[0-9x]分成一组,就可以整体约束他们出现的次数为0-1次 |
^([1-9]\d{16}[0-9x]|[1-9]\d{14})$ | 110105199812067023 | 110105199812067023 | 表示先匹配[1-9]\d{16}[0-9x]如果没有匹配上就匹配[1-9]\d{14} |
转义符 \
在正则表达式中,有很多有特殊意义的元字符,比如\d和\s等,如果要在正则中匹配正常的''\d''而不是‘数字’,就需要对''\''进行转移,变成''\\''
在python中,无论是正则表达式还是待匹配的内容,都是以字符串的形式出现的,在字符串中\也有特殊的含义,本身还需要转义,所以如果破配一次‘\d’,字符串中要写成‘\\d’,那么python里就要写成‘\\\\d’,这样就太麻烦了,这个时候我们就用到了r'\d'这个概念,此时的正则是r'\\d'就可以了。
正则 | 待匹配字符 | 匹配结果 | 说明 |
\d | \d | False | 因为在正则表达式中\是有特殊意义的字符,所以要匹配\d本身,用表达式\d无法匹配 |
\\d | \d | True | 转义\之后变成\\,即可匹配 |
"\\\\d" | '\\d' | True | 如果在python中,字符串中的'\'也需要转义,所以每一个字符串'\'又需要转义一次 |
r'\\d' | r'\d' | True | 在字符串之前加r,让整个字符串不转义 |
贪婪匹配和非贪婪匹配
正则 | 待匹配字符 | 匹配结果 | 说明 |
<.*> | <script>...<script> | <script>...<script> | 默认为贪婪匹配模式,会匹配尽量长的字符串 |
<.*?> | <script>...<script> | <script> <script> | 加上?为将贪婪匹配模式转为非贪婪匹配模式,会匹配尽量短的字符串 |
几个常用的非贪婪匹配模式
*? 重复任意次,但尽可能少重复+? 重复1次或更多次,但尽可能少重复?? 重复0次或1次,但尽可能少重复{n,m}? 重复n到m次,但尽可能少重复{n,}? 重复n次以上,但尽可能少重复
.*?的用法
. 是任意字符* 是取 0 至 无限长度? 是非贪婪模式。何在一起就是 取尽量少的任意字符,一般不会这么单独写,他大多用在:.*?x就是取前面任意长度的字符,直到一个x出现
re模块下的常用方法
import re#findallret=re.findall('[a-z]+','eva yuan egon')#返回所有满足匹配条件的结果,放在列表中print(ret)#searchret=re.search('a','eva egon yuan')#从前往后匹配,匹配到一个就返回,返回的是结果的一个对象,需要调用group()才能拿到结果#如果没有匹配成功,就会会返回None,调用group()会报错if ret: print(ret.group()) #所以需要用if判断一下#matchret=re.match('a','eva egon yuan')#match是从头开始匹配,如果正则规则从头开始可以匹配上,就返回一个变量。#匹配的内容需要用group()才能显示#如果没匹配上,就返回None,调用group会报错if ret: print(ret.group())#splitret=re.split('[ab]','abcd')# 先按'a'分割得到''和'bcd',再按'b'分割得到''和'cd'print(ret)#['','','cd']#subret=re.sub('\d','H','eva3egon4tyuan5')#将所有数字替换成‘H’print(ret)#subnret=re.subn('\d','H','eva3egon4yuan5')#返回的是一个元组,包括替换的结果,和替换的次数print(ret)#compileobj=re.compile('\d{3}')#将正则表达式编译成为一个正则表达式对象,规则要匹配的是3个数字ret=obj.findall('abc123efg')#正则表达式对象调用search,参数为带匹配的字符串print(ret)#结果123#finditerret=re.finditer('\d','ds3sy4784a')#finditer返回的是一个存放匹配结果的迭代器print(ret)#print(ret.__next__().group())#查看第一个结果print(ret.__next__().group())#查看第二个结果for i in ret: print(i.group())#查看剩余的结果
注意:
findall的优先级查询:
import reret = re.findall('www.(baidu|oldboy).com', 'www.oldboy.com')print(ret) # ['oldboy'] 这是因为findall会优先把匹配结果组里内容返回,如果想要匹配结果,取消权限即可ret = re.findall('www.(?:baidu|oldboy).com', 'www.oldboy.com')print(ret) # ['www.oldboy.com']#再括号最前面加上?:就取消了分组优先
split的优先级查询:
ret=re.split("\d+","eva3egon4yuan")print(ret) #结果 : ['eva', 'egon', 'yuan']ret=re.split("(\d+)","eva3egon4yuan")print(ret) #结果 : ['eva', '3', 'egon', '4', 'yuan']#在匹配部分加上()之后所切出的结果是不同的,#没有()的没有保留所匹配的项,但是有()的却能够保留了匹配的项,#这个在某些需要保留匹配部分的使用过程是非常重要的
分组优先机制只有findall有,search和match都没有,我们再来看个与括号有关的
ret=re.search('^[1-9](\d{14})(\d{2}[0-9x])?$','330682199609235934')print(ret.group(1))print(ret.group(2))#再search方法中给正则表达式里的部分加上括号,用group方法输出时就可以用数字来决定想要输出哪一部分#输出:30682199609235934
练习题:
匹配标签
import reret = re.search("<(?P\w+)>\w+ "," hello
")#还可以在分组中利用?的形式给分组起名字#获取的匹配结果可以直接用group('名字')拿到对应的值print(ret.group('tag_name')) #结果 :h1print(ret.group()) #结果 : hello
ret = re.search(r"<(\w+)>\w+ ","hello
")#如果不给组起名字,也可以用\序号来找到对应的组,表示要找的内容和前面的组内容一致#获取的匹配结果可以直接用group(序号)拿到对应的值print(ret.group(1))print(ret.group()) #结果 :hello
匹配整数
import reret=re.findall(r"\d+","1-2*(60+(-40.35/5)-(-4*3))")print(ret) #['1', '2', '60', '40', '35', '5', '4', '3']ret=re.findall(r'\d+\.\d+|(\d+)','1-2*(60+(-40.35/5)-(-4*3))')#利用findall的括号分组优先,‘|’左边匹配到了小数但是因为右边的括号,匹配到的小数不会被显示出来,所以会显示一个空print(ret)#['1', '2', '60', '', '5', '4', '3']ret.remove("")print(ret) #['1', '2', '60', '5', '4', '3']