Python(5)正则表达式

前言

正则表达式的步骤

  1. 确定模式有几个子模式
  2. 各部分的字符分类是什么
  3. 子模式怎么重复
  4. 是否有外部位置的限制
  5. 是否有内部制约关系

正则表达式本质上是一门语言,它不从属与Python!Python只是将他人写好的正则引擎集成到了语言内部,大多数编程语言都是这么干的!
正则表达式诞生的时间很长,应用非常广泛,是业界公认的字符串匹配工具。虽然有不同版本的内部引擎,但基本通用,也就是说,你在Python内写的正则表达式,可以移植到Linux的shell,Java语言等任何支持正则的场景中去。
正则表达式默认从左往右匹配。
正则表达式默认是贪婪模式
正则表达式默认在匹配到了内容后,则终止匹配,不继续匹配。
对同一个问题,编写的正则表达式不是唯一的

匹配模式: flags=xxx

缩写全称含义
AASCIIASCII字符模式
IIGNORECASE不区分大小写
LLOCALE本地化识别local-aware匹配
MMULTILINE多行匹配,每一行匹配一次,影响^ $
SDOTALL使 . 字符能够匹配包括 \n 换行符的所有字符,针对多行匹配
XVERBOSE该标志通过给予你更灵活的格式以便你将正则表达式写得更易于理解(emm 有点点废话)
UUNICODE根据Unicode字符集解析字符。这个标志影响 \w, \W, \b, \B

史上最全ASCII码对照表0-255(%d)
Unicode字符代码
字符集与编码系列:Unicode字符集

反斜杠:\

print(re.match(r"\d\wabc",str)
print(re.match(“\d\wabc”,str)
原字符串\\\\---->表示\\---->正则表达式中表示
很复杂!很恼火!
如果需要匹配 “D:\abc\s\1.txt",可以写成:
print(re.search(r"\w:\\w+\\w+\\w+.\w+",“sadaD:\abc\s\1.txt”))

python提供原生字符串的功能, 很好地解决了这个问题,这个例子中的正则表达式可以使用r"\“表示。同样,匹配一个数字的”\d"可以直接写成r"\d"。有了原生字符串,你再也不用担心是不是漏写了反斜杠,写出来的表达式也更直观。

分组

把正则表达式分成几组,这样就可以重复某个部分

分组
正则匹配
(…)捕获一个组
(?P…)捕获一个组,命名name
(?:…)不捕获组
\Y匹配第Y个匹配的组,可以数左括号的个数
(?P=name)匹配名为name的组
(?#…)注释

一、re模块常用方法

引用博客的传送门

方法描述返回值
compile(pattern[,flags])根据包含正则表达式的字符串创建模式对象re对象
search(pattern,string[,flags])在字符串中查找第一个匹配到的对象或者none
match(pattern,string[,flags])在字符串中开始处匹配返回re对象或none
split(pattern,string[,maxsplit=0,flags])分割字符串返回分割后的字符串列表list
findall(pattern,string,flags)字符串中所有匹配项返回字符串列表
sub(pat,repl,string[,count=0,flags])repl替换所有的pat返回替换后的string

1.编译compile(pattern[, flags])

compile一个re对象 ,提高效率!
返回的re对象就可以正常调用re模块中的噶种方法!
根据包含正则表达式的字符串创建模式对象
当你需要多次匹配string时候,比如需要for的时候,就可以提前compile

f=re.compile(r"\d{8}--\d{4}--\d{4}")
for i in str:
	f.match(i)

2.匹配match(pattern,string[,flags])

在字符串的开始位置进行匹配,不成功就返回None,这个我个人认为在代码编辑中不常用该方法。
** group()方法**
group(0) – 输出原字符串;; group(i) --从pattern左边数第i个左括号中匹配的字符

根据返回的object <_sre.SRE_Match object; span=(0, 3), match=‘abc’>
span是匹配到的字符在字符串中的位置下标 [左下标,右下标)

f=re.match(r"abc",str)
f.group()
f.start()
f.end()
f.span()

3.匹配search(pattern,str[,flags])

文本内查找(和match的唯一区别),饭回匹配的第一个字符内容,如果匹配字符中包含分组,可以用group()方法。

4.匹配findall(pattern,str[,flags])

三大巨头之一,与前两个的区别就是:这个全文搜索所有满足条件的字符串,返回匹配到的字符串的列表。如果是分组查找,返回的就是以组为元素的列表。如果是没有匹配的,返回的是[]空列表,前两个返回的是None。
没有group()方法!!!

5.分割split(pattern,str[maxsplit=0,flags])

分割功能强大,可以按照特定的正则表达式进行分割。返回的是不包含分割字符的列表,也有方法可以返回分割字符。

print(re.split(r"--|[\+\-\*]","2020--12--39+49人"))

['2020', '12', '39', '49人']

maxsplit=最大分割次数,从左到右进行匹配

print(re.split(r"--|[\+\-\*]","2020--12--39-49人-32男+17女",maxsplit=3)) #限制分割次数3次
['2020', '12', '39', '49人-32男+17女']

返回包括分割字符的列表。比如上述例子,需要返回 ‘2020’ ‘–’ ’ 12’ ‘–’…,可以通过神奇的添加小括号()

print(re.split(r"(--|[\+\-\*])","2020--12--39-49人-32男+17女",maxsplit=3)) # !!!!!这里添加了小括号
['2020', '--', '12', '--', '39', '-', '49人-32男+17女']

6.替换sub(pat,repl,str,count=0,flags=0)

pat:被替换的字符
repl:替换的新字符
用repl替换pat
count:指替换次数

print(re.sub(r"i",r"I","i love you but fuvky your ***"))

sub()又一个高级功能----分组引用
\1 就是从左往右数左括号的个数

origin = "Hello,world!"
r = re.sub(r"((world))(.)",r"<em>\1<em><em>\3<em><em>\2<em>", origin)   # 注意括号和\1的作用!
# \1 : ((world))----world  \2(world)----world  \3----!
print(r)

Hello,<em>world<em><em>!<em><em>world<em>

其实现机制是首先在正则表达式里用括号建立了一个分组,然后在要替换进去的字符串里用“\1”引用了这个分组匹配到的内容。

二、匹配练习

原文章链接

习题集一

习题1:判断是否匹配成功,并输出对应匹配信息

if re.match(r"123","123sc"):
    print("匹配成功")
else: print("匹配失败")

习题2: 找出一个字符串中是否有连续的5个数字

str="applr 123999328 hjdfsfh"
str2="diasohdi123uinhxv32r32"
print(re.search(r"\d{5}",str).group())

print(re.search(r"\d{5}",str2).group()) 
# AttributeError: 'NoneType' object has no attribute 'group'

习题3:出一个字符串中的连续5个数字,要求数字前后必须是非数字


print(re.search(r"(\D\d{5}\D)|(^\d{5}\D)|(\D\d{5}$)|(^\d{5}$)","12567").group()) # 匹配成功
print(re.search(r"[^\d]\d{5}[^\d]","12567").group()) # 匹配失败,因为[^\d]是指非数字的字符,不能涵盖只有数字的字符串 

习题4:统计一个文件中单词的数量

with open("1.txt",'r',encoding='utf-8',errors='ignore') as f:
    file=f.read() #  type of file  is string
    # \b 匹配一个单词边界,也就是单词和空格之间的位置,符号本身不匹配任何字符
    print(len(re.findall(r"\b[a-zA-Z]+\b",file)))

习题5:把a1b23c4d非字符内容拼成一个字符串

# 方法一:split()
a=re.split(r"[^\d]","a1b23c4d")
for i in a:
    print("".join(i),end="")

print("\n\n")

# 方法二:findall()
print("".join(re.findall(r"\d","123adewad1fes3hbu4345bh")))

习题6:取最后一个字母

# 取最后一个字母
print(re.search(r".+([a-zA-Z])","dsafbhqbfcuSJHAS Hab12cd").group(1))

习题7:找出一个字符串中的所有数字

str="a1cd33dd99kddd"
# 找出一个字符串中的所有数字
print(re.findall(r"\d+",str))

# 看了答案,题主竟然compile了!!!秀
pattern=re.compile(r"\d+")
pattern.findall(str)

# 越来越复杂 秀
pat=re.compile(r"\d")
for i in str:
    print(pat.search(i))
 

 

习题8:把一个字符串中的所有字母找出并拼成一个字符串

# 把一个字符串中的所有字母找出并拼成一个字符串
for i in str:
    print("".join(re.findall(r"[a-zA-Z]",i)),end="")

print("\n")

# split()
f=re.split(r"[\d]",str)
print("".join(f))

 

习题9:输出句子中的所有单词

s = "I am a boy! you are a girl!"

print(re.findall(r"[a-zA-Z]+",s))

['I', 'am', 'a', 'boy', 'you', 'are', 'a', 'girl']

习题集二

1、 只匹配包含字母和数字的行
import re

s=“because\n12sd 34er 56\ndf e4 54434”

print(re.findall(r"\w+",s,re.M))

2、写一个正则表达式,使其能同时识别下面所有的字符串:‘bat’,‘bit’, ‘but’, ‘hat’, ‘hit’, 'hut‘

s1 = ''' 'bat', 'bit', 'but', 'hat', 'hit', 'hut','yat','har','hot' '''
print(re.findall(r"\'[bh][aiu]t\'",s1))
# [b|h][a|i|u] 和 [bh][aiu]的含义是一样的

6、匹配所有合法的python标识符
import re

s=“awoeur awier !@# @#4_-asdf3KaTeX parse error: Expected group after '^' at position 1: ^̲&()+?><dfg\n$”

print(re.findall(r".*",s,re.DOTALL))

7、提取每行中完整的年月日和时间字段

SS="我出生时间为1990-01-01 00:00:00,今天时间为2019-04-20 12:20:00"
print(re.findall(r"(\d{4}-\d{2}-\d{2} ([01][0-9]|[2][0-3]):([0-5][0-9]):([0-5][0-9]))",SS))

for i in SS.split(r","):
    result=re.search(r"\d{4}-\d{2}-\d{2} ([01][0-9]|[2][0-3]):([0-5][0-9]):([0-5][0-9])",i)
    if result:
        print(result.group())
    else:
        continue
8、将每行中的电子邮件地址替换为你自己的电子邮件地址
# coding:utf-8
import re
s="""
1234867@qq.com
lihuali@sdcion.com
"""
s1="guolingping@sdcion.com"
for i in s.split("\n"):
    result=re.search(r"[\w]+@[\w]+.com",i)
    if result:
        s=s.replace(result.group(),s1)
    else:
        continue
print(s)

 
 

10、使用正则提取出字符串中的单词
# coding:utf-8
import re
s="I am a boy, my is 19 year!"
print(" ".join(re.findall(r"\b[a-zA-Z]+\b",s)))

 

11、使用正则表达式匹配合法的邮件地址:
国际域名格式如下:

域名由各国文字的特定字符集、英文字母、数字及“-(即连字符或减号)任意组合而成, 但开头及结尾均不能含有“-”,“-”不能连续出现。域名中字母不分大小写。域名最长可达60个字节(包括后缀.com、.net、.org等)import re
s="lisi_1234@qq.org"
result=re.match(r"^[\w]([a-z0-9]*[-_]?[a-z0-9]+)*@([a-z0-9]*[-_]?[a-z0-9]+)+[\.][a-z]{2,3}([\.][a-z]{2})?$",s)
if result:
    print(result.group())

 

12、提取字符串中合法的超链接地址比如:s = '<a href="http://www.gloryroad.cn">光荣之路官网</a>'要求,给出的正则表达式能兼顾所有链接地址。
import re
s='<a href="http://www.gloryroad.cn">光荣之路官网</a>'
print(re.search(r'\w+://[w]{3}.\w+.\w{2,3}',s).group())

 

13、统计文件中单词个数
# coding:utf-8
import re
s="I am a boy, my is 19 year!"
result=re.findall(r"\b[a-zA-Z]+\b",s)
print(result)
print("单词个数为:",len(result))

 

14、写一个函数,其中用正则验证密码的强度
import re
"""
密码长度大于或等于8位数
强:字母+数字+特殊字符
中:字母+数字,字母+特殊字符,数字+特殊字符
弱:纯数字,纯字母,纯特殊字符
"""

def checklen(pwd):
    if len(pwd)>=8:
        return True
    else:
        return False

def is_strong_pwd(pwd):
    pattern=re.compile(r"^(?![a-zA-z]+$)(?!\d+$)(?![!@#$%^&*]+$)(?![a-zA-z\d]+$)(?![a-zA-z!@#$%^&*]+$)(?![\d!@#$%^&*]+$)[a-zA-Z\d!@#$%^&*]+$")
    result=pattern.findall(pwd)
    if result:
        return True
    else:
        return False

def is_inter_pwd(pwd):
    pattern=re.compile(r"^(?![a-zA-z]+$)(?!\d+$)(?![!@#$%^&*]+$)[a-zA-Z\d!@#$%^&*]+$")
    result=pattern.findall(pwd)
    if result:
        return True
    else:
        return False

def is_weak_pwd(pwd):
    pattern=re.compile(r"^(?:\d+|[a-zA-Z]+|[!@#$%^&*]+)$")
    result=pattern.findall(pwd)
    if result:
        return True
    else:
        return False

def checkpassword(pwd):

    #判断密码长度是否合法
    lenOK=checklen(pwd)

    #判断是否强:字母+数字+特殊字符
    strongOK=is_strong_pwd(pwd)

    #判断是否中:字母+数字,字母+特殊字符,数字+特殊字符
    interOK=is_inter_pwd(pwd)

    #判断是否弱:纯数字,纯字母,纯特殊字符
    weakOK=is_weak_pwd(pwd)

    print(lenOK)
    print(strongOK)
    print(interOK)
    print(weakOK)
    if lenOK:
        if strongOK:
            print("密码的强度为强的!")
        elif interOK:
            print("密码的强度为中的!")
        elif weakOK:
            print("密码的强度为弱的,建议修改!")
    else:
        print("密码长度不合格!")

checkpassword("Helloworld#123")

结果为:

True

True

True

False

密码的强度为强的!

15、匹配ip的正则表达式:
r'^(([1-9]|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.){3}([1-9]|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])$'

# coding:utf-8
import re
s='172.16.23.189'
s1='11.2.123.1'
s2='255.255.255.255'
s2='0.0.0.0'
pattern=re.compile(r'^(([1-9]|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.){3}([1-9]|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])$')
print(pattern.match(s).group())



import re

------------------------------------------------------
'''
# findall(pattern, string, flags=0)
# 它的返回值是一个匹配到的字符串的列表。这个列表没有group()方法,
# 没有start、end、span,更不是一个匹配对象,仅仅是个列表!没有匹配的就返回空列表


# split(pattern, string, maxsplit=0, flags=0)
# 字符串的split()和re模块的split()都能分割字符串
# maxsplit最大分割次数
# 返回列表,可保留分割字符
'''
------------------------------------------------------
st=re.split(r"[\+\#\$\/]","1+2+3+4\3#2313kk/kk")
sr=re.split(r"([\+\#\$\/])","1+2+3+4\3#2313kk/kk") # 加上小括号 就可以保存被匹配到的分隔符
print(st,"\n\n",sr)

# sub(pattern, repl, string, count=0, flags=0)
# 类似于字符串的replace()方法
print(re.sub(r"i","I","i like u")) # 把I替换掉i

# "分组引用" 非常强大的功能
origin="hello world  it is your world"
r=re.sub(r"(world)",r"<em>\1<em>",origin)
print(r)

# flag匹配模式
# re.I 不区分大小写
# re.M 多行匹配

st=re.split(r"[\+\#\$\/K]","1+2+3+4\3#2313kkff/kk",flags=re.I)
sr=re.split(r"([\+\#\$\/K])","1+2+3+4\3#2313kk/kk",flags=re.I) # 加上小括号 就可以保存被匹配到的分隔符
print(st,"\n\n",sr)
------------------------------------------------------
'''re 的分组功能
已经匹配到的内容里面再筛选出需要的内容,相当于二次过滤。
实现分组靠圆括号(),
获取分组的内容靠的是group()、groups()和groupdict()方法, 

'''
------------------------------------------------------
origin = "hasdfi123123safd"
# 不分组时的情况
r = re.match("(h\w+)(?P<id2>\d\w+)", origin)
# (?P<name>\d)中?P<name>是个正则表达式的特殊语法,表示给这个小组取了个叫“name”的名字,?P<xxxx>是固定写法。
# 在匹配的时候是拿整体的表达式去匹配的,而不是拿小组去匹配的。

print(r.group())         # 获取匹配到的整体结果
print(r.groups())        # 获取模型中匹配到的分组结果元组 ("","")
print(r.groupdict())     # 获取模型中匹配到的分组中所有key的字典 {"name":" "}
print("\n"*8)

---------------------------------------------------------------------
''' search()方法 分组'''

origin = "sdfi1ha23123safd123"      # 注意这里对匹配对象做了下调整
# 有分组
r = re.search("h(\w+).*(?P<name>\d)$", origin)
print(r.group())  
# 第几个左括号就是第几个分组,例如(1(2,(3)),(4)),
# 0表示表达式本身,不参加数左括号的动作。
# 所以根据上面两句话我们可以知道group括号里面的数字代表的是组 和列表中的标识不一样
print(r.group(0))  
print(r.group(1))  
print(r.group(2))
print(r.groups())  
print(r.groupdict()) 


print("\n"*8)
origin = "has something have do"
# 一个分组  元组组成的列表!
r = re.findall("(h(\w+))", origin)
print(r,"\n"*8) 

origin = "hasabcd something haveabcd do"
# 有分组
r = re.sub("h(\w+)", "haha",origin)
print(r)
# sub()没有分组的概念!这是因为sub()方法是用正则表达式整体去匹配,然后又整体的去替换,分不分组对它没有意义。这里一定要注意了!不!!他有分组的概念,巧妙利用正则表达式里面的反向应用。
print("\n"*4)
origin = "has abcd something abcd do"
# 有一个分组
r = re.split("(abcd)", origin)
print(r)
r2 = re.split("(a(bc)d)", origin)
print(r2)
r3 = re.split("(a(bc)d)", origin)
print(r3)