python高级_day5

python正则表达式

1 标准库模块re

   python3中使用re模块支持正则表达式(Regular Expression),需要定义一个用于匹配的模式(pattern)字符串,以及一个要匹配的字符串(string)。

  • 简单匹配
import re
m = re.match('My','My name is zhangsan')
print(m.group())
print(m.start(),m.end())
print(m.span())

My
0 2
(0, 2)

   其中,My是正则表达式模式,最简单的,只匹配字符My本身。而My name is zhangsan是想要检查的字符串,re.match()函数用于查看字符串是不是以正则模式开头。
   如果你仅仅是做一次简单的文本匹配/搜索操作的话,可以直接使用 re 模块级别的函数,比如re.match。如果你打算做大量的匹配和搜索操作的话,最好先编译正则表达式,然后再重复使用它

  • 复杂匹配
import re

p = re.compile('[a-z]+')  # [a-z]+ 是正则模式,表示1个或多个小写字母
print(p)

if p.match('hello123'):# p是预编译后的正则模式,它也有match等方法,只是参数不同,不需要再传入正则模式。判断字符串'hello123'是否以1个或多个小写字母开头
    print('yes')
else:
    print('no')
    
if p.match('123hi'):# 重用预编译过的正则模式
    print('yes')
else:
    print('no')

re.compile('[a-z]+')
yes
no

   模块级别的函数会将最近编译过的模式缓存起来,因此并不会消耗太多的性能, 但是如果使用预编译模式的话,你将会减少查找和一些额外的处理损耗。

1.1 使用match()从字符串开头开始匹配

   可以使用模块级别的re.match()或预编译模式的p.match(),如果字符串是以正则表达式开头,则表明匹配成功,返回匹配到的对象,比如<_sre.SRE_Match object; span=(0, 2), match='My'>,如果匹配失败,返回None

import re

m1 = re.match('zhangsan', 'zhangsan is a handsome boy.')  # 模块级的match方法
print(m1)  # 匹配成功,返回Match对象
print(m1.group())  # Match对象有group()、start()、end()、span()等方法

m2 = re.match('mayun', 'zhangsan is a handsome boy.')  # 匹配失败
print(m2)  # 返回None

p = re.compile('zhangsan')  # 预编译正则模式也是可以的
p.match('zhangsan is a handsome boy.')  # 调用预编译正则模式的match方法


zhangsan
None

1.2 使用search()寻找首次匹配

   如果字符串中有多个地方与正则表达式匹配的话,search()方法返回第一次匹配到的结果:

import re

s = 'I wish I may, I wish I might have a dish of fish tonight.'
print(re.search('wish', s))
print(re.search('wish', s).span())


(2, 6)

1.3 使用findall()或finditer()寻找所有匹配

   前面两个函数都是查找到一个匹配后就停止,如果要查找字符串中所有的匹配项,可以使用findall()和finditer()

  • findall():会搜索文本并以列表形式返回所有的匹配
  • finditer():以迭代方式返回匹配
import re

text = 'Today is 11/27/2012. PyCon starts 3/13/2013.'

# 以列表的方式返回
p = re.compile('\d+/\d+/\d+')
print(p.findall(text))

# 以迭代的方式返回        
iters = p.finditer(text)
print(iters)


for m in iters:
    print(m)    

['11/27/2012', '3/13/2013']


1.4 使用split()按匹配切分

   字符串的str.split()方法只适应于非常简单的字符串分割情形, 它并不允许有多个分隔符或者是分隔符周围不确定的空格。 当你需要更加灵活的切割字符串的时候,最好使用 re.split() 方法

import re

line = 'asdf fjdk;   afed,  fjek,asdf,   foo'
print(re.split(r'[;,\s]\s*', line))  # 正则模式表示 ;或,或空白字符且它们的后面再跟0个或多个空白字符

['asdf', 'fjdk', 'afed', 'fjek', 'asdf', 'foo']

1.5 使用sub()替换匹配

  • 对于简单的字面模式,直接使用字符串的str.replace()方法即可;对于复杂的模式,请使用re模块中的sub()
import re

text = 'Today is 11/27/2012. PyCon starts 3/13/2013.'
print(re.sub('(\d+)/(\d+)/(\d+)', r'\3-\1-\2', text))#sub()`函数中的第一个参数是被匹配的模式,第二个参数是替换模式。反斜杠数字比如`\3`指向前面模式的第3个捕获组,此时要加`r`指定为原始字符串,否则会被Python自动转义为`\x03

'Today is 2012-11-27. PyCon starts 2013-3-13.'

  • 对于更加复杂的替换,可以传递一个替换回调函数来代替。一个替换回调函数的参数是一个Match对象,也就是match()或者find()返回的对象。使用group()方法来提取特定的匹配部分。回调函数最后返回替换字符串。
import re
from calendar import month_abbr

def change_date(m):
    mon_name = month_abbr[int(m.group(1))]
    return '{} {} {}'.format(m.group(2), mon_name, m.group(3))

text = 'Today is 11/27/2012. PyCon starts 3/13/2013.'
p = re.compile(r'(\d+)/(\d+)/(\d+)')
print(p.sub(change_date, text))

'Today is 27 Nov 2012. PyCon starts 13 Mar 2013.'

  • 如果除了替换后的结果外,你还想知道有多少替换发生了,可以使用re.subn()。
text = 'Today is 11/27/2012. PyCon starts 3/13/2013.'
p = re.compile(r'(\d+)/(\d+)/(\d+)')
newtext, n = p.subn(r'\3-\1-\2', text)

print(newtext)
print(n)

Today is 2012-11-27. PyCon starts 2013-3-13.
2

2 正则表达式语法

2.1 基本模式

语法 说明 模式范例 匹配
普通字符 普通的文本值代表自身,用于匹配非特殊字符 ab ab
. 匹配除换行符\n以外的任意一个字符。如果要匹配多行文本,可以指定re.DOTALL标志位 ab. 匹配abc或abC,不匹配ab,因为b后面一定要有一个字符
\ 转义字符,比如要匹配点号.本身,需要转义\.,如果不转义,.将有上一行所示的特殊含义 ab. 匹配ab.com,不匹配abc
[] 匹配中括号内的一个字符 1. 中括号内的字符可以全部列出,如[abc]表示匹配字符a或b或c 2. 也可以使用-表示范围,如[a-z]表示匹配所以小写字母中的任意一个字符 3. 本文后续要介绍的如*、+等特殊字符在中括号内将失去特殊含义,如[+()]表示匹配字符或+或(或) 4. 本文后续要介绍的特殊字符集如\d、\w等也可以放入此中括号内,继续保持特殊含义 5. 如果中括号内的字符序列前面有一个^,表示不匹配中括号内的任何一个字符,如[^0-9]表示不匹配数字,a[^0-9]c不匹配a1c,但会匹配abc 6. 要匹配字符],可以转义它,或者把它放在中括号内的首位,如a[()[\]{}]c或a[]()[{}]c都可以匹配到a]c a[0-9]b a1b或a2b

2.2 特殊字符集

语法 说明 模式范例 匹配
\d 匹配任意一个数字字符,等价于[0-9] a\db a1b
\D 匹配任意一个非数字字符,等价于[^0-9] a\Db aAb
\s 匹配任意一个空白字符,等价于[ \t\n\r\f\v] a\sb a b
\S 匹配任意一个非空白字符,等价于[^ \t\n\r\f\v] a\Sb aAb
\w 匹配任意一个 alphanumeric character,等价于[a-zA-Z0-9_] a\wb azb或aZb或a1b或a_b
\W 匹配任意一个 non-alphanumeric character,等价于[^a-zA-Z0-9_] a\Wb a-b或a b

   这些字符集也可以放入[]中,比如a[\d\s]b表示匹配字符a和字符b,且中间有一个数字字符或空白字符,所以它会匹配a1b或a b等
   python的string模块中预先定义了一些可供我们测试用的字符串常量。我们将使用其中 的printable字符串,它包含 100 个可打印的 ASCII 字符,包括大小写字母、数字、空格 符以及标点符号

import string

printable = string.printable

print(len(printable))

print(printable[:50])
print(printable[50:])

100
0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMN
OPQRSTUVWXYZ!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~ \t\n\r\x0b\x0c

# 查找printable中的数字
print(re.findall('\d', printable))
# 查找printable中的数字,字符,下划线
print(re.findall('\w', printable), end='')
# 查找printable中的空格符
re.findall('\s', printable)

['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '_']
[' ', '\t', '\n', '\r', '\x0b', '\x0c']

注意:\d和\w不仅会匹配ASCII字符,还可以匹配Unicode字符

s = 'abc' + '-/*' + '\u00ea' + '\u0115' + '\u0030' + '\u0031'

print(re.findall('\w', s))
print(re.findall('\d',s))

['a', 'b', 'c', 'ê', 'ĕ', '0', '1']
['0', '1']

2.3 数量

说明: 表格中的 prev 表示 1. 单个字符如b 2. 或者复杂的表达式如(abc)或(.|\n)(这是分组的功能,见2.5)

语法 说明 模式示例 匹配
prev* 匹配0个或多个 prev,尽可能多地匹配,贪婪模式,等价于{0,} ab* a或ab或abb或abbb,注意是匹配字符a后面跟0个或多个字符b
prev*? 匹配0个或多个 prev,尽可能少地匹配,非贪婪模式 ab*? a,非贪婪模式下匹配0个字符b
prev+ 匹配1个或多个 prev,尽可能多地匹配,贪婪模式,等价于{1,} ab+ ab或abb或abbb
prev+? 匹配1个或多个 prev,尽可能少地匹配,非贪婪模式 ab+? ab,非贪婪模式下匹配1个字符b
prev? 匹配0个或1个 prev,尽可能多地匹配,贪婪模式,等价于{0,1} ab? a或ab
prev?? 匹配0个或1个 prev,尽可能少地匹配,非贪婪模式 ab?? a,非贪婪模式下匹配0个字符b
prev{m} 匹配m个连续的 prev a{3} aaa
prev{m,n} 匹配m到n个连续的 prev ,尽可能多地匹配,贪婪模式。n可选,如果不指定,则表示m到无穷多个连续的 prev a{3,5} aaa或aaaa或aaaaa
prev{m,n}? 匹配m到n个连续的 prev ,尽可能少地匹配,非贪婪模式 a{3,5}? aaa

   可以在*或+或?的后面再添加一个?,此时表示非贪婪模式匹配,python中的正则表达式默认是贪婪模式匹配,它会在满足整个表达式要求的前提下,尽可能多地去匹配字符

2.4 边界

语法 说明 模式示例 匹配
^prev 匹配以 prev 开头的字符串(托字符)。多行文本中,默认^只会匹配第一行的开头位置,如果设置了re.MULTILINE标志位,则^也会匹配换行符之后的开头位置 ^ab abcd
prev$ 匹配以 prev 结尾的字符串。多行文本中,默认$只会匹配最后一行的结尾位置,如果设置了re.MULTILINE标志位,则$也会匹配换行符之前的结尾位置 ab$ 只匹配ab。如果是.*ab$则会匹配123ab,否则使用ab\Z
\b 单词边界。Matches the entire string, but only at the beginning or end of a word. A word is defined as a sequence of word characters. Note that formally, \b is defined as the boundary between a \w and a \W character (or vice versa), or between \w and the beginning/end of the string. 注意: \b在Python中默认会被转义为\x08表示退格,需要将整个正则表达式指定为原始字符串(在前面加个r),即r'\bfoo\b' r'\bfoo\b' 请使用re.findall()测试 匹配foo或foo.或(foo)或bar foo baz,但不匹配foobar或foo3
\B 非单词边界。Matches the entire string, but only when it is not at the beginning or end of a word. \B is just the opposite of \b. py\B 匹配python或py3或py2,但不匹配py或py.或py!
\A Matches only at the start of the string. \Aab abcde
\Z Matches only at the end of the string. ab\Z 123ab

2.5 分组

语法 说明 模式示例 匹配
(expr) 将小括号内的表达式作为一个分组,后面可以接表示数量的特殊字符。每个分组的编号从1开始递增,后续可以使用\1、\2这样引用分组匹配到的内容 a(bc)?d ad或abcd,不匹配abcbcd
expr1 │expr2 匹配 expr1 或 expr2 ,a│b│c等价于[abc],都表示匹配字符a或b或c。从左至右,如果匹配了某个表达式,则跳过后续表达式。expr1 │ expr2表示作用于整个待匹配的字符串,而(expr1│expr2)加入小括号内表示分组,不是整个字符串 p(i│u)g pig或pug
\1 引用编号为1的分组匹配到的字符串,同理\2表示引用第2个分组。注意: 此时为了不让python自动将\1转义为\x01,需要将整个正则表达式指定为原始字符串(在前面加个r),即r'a(\d)b\1c' r'a(\d)b\1c' a3b3c
(?Pexpr) 类似于(expr),同时给分组指定了一个别名NAME,注意是大写的字母P r'a(?P\d)b\1c' a3b3c
(?P=NAME) 引用别名为NAME的分组,当然也可以继续使用编号的形式引用分组如\1,此时要加r指定为原始字符串 a(?P\d)b(?P=quote)c 或 r'a(?P\d)b\1c' a3b3c
  • 引用命名的分组
       当使用match()或search()时,所有的匹配会以m.group()的形式返回到对象m中。如果你用括号将某一模式包裹起来, 括号中模式匹配得到的结果归入自己的分组group(无名称)中,而调用 m.groups() 可以得到包含这些匹配的元组
import re
s = 'I wish I may, I wish I might have a dish of fish tonight.'
m = re.search(r'(. dish\b).*(\bfish)', s)
print(m.group())
print(m.groups())
print(m.group(0))
print(m.group(1))
print(m.group(2))

a dish of fish
('a dish', 'fish')
a dish of fish
a dish
fish

给分组指定别名:

import re
s = 'I wish I may, I wish I might have a dish of fish tonight.'
m2 = re.search(r'(?P. dish\b).*(?P\bfish)', s)
print(m2.group())
print(m2.groups())
print(m2.group(0))
print(m2.group('DISH'))
print(m2.group('FISH'))
print(m2.group(1))
print(m2.group(2))

a dish of fish
('a dish', 'fish')
a dish of fish
a dish
fish
a dish
fish

2.6 扩展语法

   python3中的正则表达式,以(?开头的都是一些扩展语法,比如上面学过的(?Pexpr)和(?P=NAME),还有一些常用的

语法 说明 模式示例 匹配
(?:expr) 非捕获组。如果分组后续要被\1这样的形式引用,使用(expr),这叫捕获组,后面可以接表示数量的特殊字符。如果不想分组被引用,就使用(?:expr),这叫非捕获组,后面可以接表示数量的特殊字符 a(?:\d)+bc,此时不支持引用分组,像r'a(?:\d)+b\1c'这样的属于语法错误 a333bc
prev(?=next) 如果后面为 next,则返回 prev ab(?=\d) 如果ab后面紧跟一个数字,则匹配,比如ab3,返回ab
prev(?!next) 如果后面不是 next,则返回 prev ab(?!\d) 如果ab后面不是紧跟一个数字,则匹配,比如abc,返回ab
(?<=prev)next 如果前面为 prev,则返回 next (?<=\d)ab 如果ab前面是一个数字,则匹配,比如1ab,返回ab
(? 如果前面不是 prev,则返回 next (? 如果ab前面不是一个数字,则匹配,比如Aab,返回ab
  • 非捕获组:
    比如要使用多个分割符或者是分隔符周围不确定的空格时,来分割一个字符串
import re

line = 'asdf fjdk; afed, fjek,asdf, foo'
print(re.split(r'[;,\s]\s*', line))

['asdf', 'fjdk', 'afed', 'fjek', 'asdf', 'foo']

这里使用了[],如果你想在正则表达式中用括号()包含一个捕获分组,那么被匹配的文本(那些分隔符)也将出现在结果列表中

print(re.split(r'(;|,|\s)\s*', line))

['asdf', ' ', 'fjdk', ';', 'afed', ',', 'fjek', ',', 'asdf', ',', 'foo']

如果你不想保留分割字符串到结果列表中去,但仍然需要使用到括号来分组正则表达式的话, 确保你的分组是非捕获分组,形如(?:...) 。

print(re.split(r'(?:,|;|\s)\s*', line))

['asdf', 'fjdk', 'afed', 'fjek', 'asdf', 'foo']

3 实例

3.1 字符串忽略大小写的搜索替换

   为了在文本操作时忽略大小写,你需要在使用re模块的时候给这些操作提供re.IGNORECASE标志参数。

import re

text = 'UPPER PYTHON, lower python, Mixed Python'

print(re.findall('python', text))
print(re.findall('python', text, flags=re.IGNORECASE))
print(re.sub('python', 'snake', text, flags=re.IGNORECASE))

['python']
['PYTHON', 'python', 'Python']
UPPER snake, lower snake, Mixed snake

   这样不管原字符串中的是大写、小写或首字母大写的python,全替换为小写的snake,如果你想替换字符串能自动跟被匹配字符串的大小写保持一致,需要提供一个辅助函数

import re

text = 'UPPER PYTHON, lower python, Mixed Python'

def matchcase(word):
    def replace(m):
        text = m.group()
        if text.isupper():
            return word.upper()
        elif text.islower():
            return word.lower()
        elif text[0].isupper():
            return word.capitalize()
        else:
            return word
    return replace


print(re.sub('python', matchcase('snake'), text, flags=re.IGNORECASE))

UPPER SNAKE, lower snake, Mixed Snake

matchcase('snake')返回了一个回调函数

3.2 python支持的其它正则表达式的标志位flags

  • re.A: 或re.ASCII,使\w/\W/d/\D/\s/\S/\b/\B只匹配ASCII字符,不匹配Unicode字符。
  • re.I: 或re.IGNORECASE,忽略大小写,[A-Z]会匹配小写字母。
  • re.M: 或re.MULTILINE,多行模式,改变^或$的默认行为。
  • re.S: 或re.DOTALL,Make the.special character match any character at all, including a newline; without this flag, .will match anything except a newline。
  • re.U: 或re.UNICODE,默认使用此标志位,\w/\W/d/\D/\s/\S/\b/\B会匹配Unicode字符,如果指定了re.A标志,则re.U失效。
  • re.X: 或re.VERBOSE,允许整个正则表达式写成多行,忽略空白字符,并可以添加#开头的注释,这样更美观。
import re

a = re.compile(r"""\d +  # the integral part
                    \.    # the decimal point
                    \d *  # some fractional digits""", re.X)

b = re.compile(r"\d+\.\d*")

print(a.match('10.2'))
print(b.match('10.2'))


`可以一次指定多个标志位·

import re

text = '''UPPER PYTHON
lower python
Mixed Python'''

print(re.sub('python$', 'snake', text))  # 三个python都没被替换,$表示只能匹配最后一个python,但是大小写不匹配,它是Python,所以没有被替换
print()
print(re.sub('python$', 'snake', text, flags=re.IGNORECASE))  # 指定了re.I,忽略大小写,最一个Python被替换成了snake
print()
print(re.sub('python$', 'snake', text, flags=re.IGNORECASE|re.MULTILINE))  # 同时指定re.I和re.M,三个都被替换
print()
p = re.compile(r'python$', re.I|re.M)
print(p)  # 预编译后的正则模式除了默认的re.UNICODE标志位外,又添加了re.I和re.M两个标志位
print(p.sub('snake', text))
print()
print(re.sub('(?im)python$', 'snake', text))  # re.I|re.M 等价于 (?im),注意(?im)必须位于整个正则的开头

UPPER PYTHON
lower python
Mixed Python

UPPER PYTHON
lower python
Mixed snake

UPPER snake
lower snake
Mixed snake

re.compile('python$', re.IGNORECASE|re.MULTILINE)
UPPER snake
lower snake
Mixed snake

UPPER snake
lower snake
Mixed snake

3.3 最短匹配模式: 非贪婪

   你正在试着用正则表达式匹配某个文本模式,但是它找到的是模式的最长可能匹配:

import re

p = re.compile(r'"(.*)"')
text1 = 'Computer says "no."'

print(p.findall(text1))


text2 = 'Computer says "no." Phone says "yes."'

print(p.findall(text2))  # 默认是贪婪模式,(.*)会尽可能多的匹配,只要后面有一个"就能满足整个正则,所以匹配到最后一个冒号之前了

['no.']
['no." Phone says "yes.']

   为了修正这个问题,可以在模式中的*操作符后面加上?修饰符

p2 = re.compile(r'"(.*?)"')
pritn(p2.findall(text2))

['no.', 'yes.']

3.4 多行匹配

   你正在试着使用正则表达式去匹配一大块的文本,而你需要跨越多行去匹配,当你用点.去匹配任意字符的时候,忘记了点.不能匹配换行符\n的事实。比如,假设你想试着去匹配C语言分割的注释

import re

text1 = '/* this is a comment */'
text2 = '''/* this is a
 multiline comment */
 '''

p = re.compile(r'/\*(.*?)\*/')

print(p.findall(text1))  # 能正确匹配单行


print(p.findall(text2))  # . 点号不能匹配多行中的换行符,所以整体匹配失败

[' this is a comment ']
[]

   可以使用.|\n匹配所有字符,或者指定re.DOTALL标志位

p2 = re.compile(r'/\*((?:.|\n)*?)\*/')

print(p2.findall(text2))

p3 = re.compile(r'/\*(.*?)\*/', re.DOTALL)

print(p3)
print(p3.findall(text2))

[' this is a\n multiline comment ']
re.compile('/\(.?)\*/', re.DOTALL)
[' this is a\n multiline comment ']

re.MULTILINE

import re

text = 'This is some text -- with punctuation.\nA second line.'
pattern = r'(^\w+)|(\w+\S*$)'
single_line = re.compile(pattern)
multiline = re.compile(pattern, re.MULTILINE)


print('Single Line :')
for match in single_line.findall(text):
    print('  {!r}'.format(match))# 转义字符,只能和format一起使用
print('Multline    :')
for match in multiline.findall(text):
    print('  {!r}'.format(match))

Single Line :
  ('This', '')
  ('', 'line.')
Multline :
  ('This', '')
  ('', 'punctuation.')
  ('A', '')
  ('', 'line.')

你可能感兴趣的:(python高级_day5)