re模块和字符串处理

40次阅读

共计 10321 个字符,预计需要花费 26 分钟才能阅读完成。

re 模块和字符串处理

对于简单的匹配,通常有 str.find(),str.endswith(), str.startswith()

>>>text = 'abcd'
>>> text.startswith('a')
True
>>> text.startswith('b')
False
>>> text.endswith('d')
True
>>> text.find('c')
2
>>> text[2]
'c'

对于比较复杂的匹配就需要用到正则表达式和 re 模块了

>>> text1 = '4/24/2019'
>>> text2 = '2019.4.24'
>>> import re
>>> re.match(r'\d+/\d+/\d+', text1) 
<re.Match object; span=(0, 9), match='4/24/2019'>  #匹配成功
>>> re.match(r'\d+/\d+/\d+', text2)  #匹配失败,没有返回 Mathch 对象
#如果想用同一个模式匹配多个字符串,那么把模式字符串预编译为模式对象
>>> c = re.compile(r'\d+/\d+/\d+')
>>> c
re.compile('\\d+/\\d+/\\d+')
>>> c.match(text1)  #用编译好的模式匹配字符串
<re.Match object; span=(0, 9), match='4/24/2019'>
#match 总是从字符串开始处匹配,想查找字符串与模式匹配的所有位置用 findall
>>> text = 'Today is 11/27/2012. PyCon starts 3/13/2013.'
>>> c.findall(text)  #如果用 match 就会匹配失败,因为它总是从开始第一个字母对比匹配
['11/27/2012', '3/13/2013']

在定义正则式的时候,通常会用括号去捕获分组

>>> c = re.compile(r'(\d+)/(\d+)/(\d+)')
>>> m = c.match('4/24/2019')
>>> m.group() #等于 group(0)
'4/24/2019'
>>> m.group(0) #捕获全部分组
'4/24/2019'
>>> m.group(1) #捕获第一个分组
'4'
>>> m.groups()  #捕获所有分组,返回一个元组
('4', '24', '2019')
>>> text = 'Today is 11/27/2012. PyCon starts 3/13/2013.'
>>> f = c.findall(text)  #返回一个所有位置匹配成功的列表
>>> f
[('11', '27', '2012'), ('3', '13', '2013')]
>>> for month, day, year in c.findall(text):
        print('{}-{}-{}'.format(year, month, day))
    
2012-11-27
2013-3-13
#finditer()返回一个可迭代对象,每个元素都为一个 Match 对象
>>> c.finditer(text)
<callable_iterator object at 0x0000018954FDE6A0>
>>> for i in c.finditer(text):
...        print(i)
<re.Match object; span=(9, 19), match='11/27/2012'>
<re.Match object; span=(34, 43), match='3/13/2013'>

>>> for i in c.finditer(text):
    print(i.group(3))    
2012
2013

>>> for i in c.finditer(text):
    print(i.groups())    
('11', '27', '2012')
('3', '13', '2013')

match()方法仅会匹配字符串开始处,能匹配到就返回匹配到的部分,如果想以正则表达式精确匹配整个字符串那么就要在正则表达式结尾加 $。

>>> c
re.compile('(\\d+)/(\\d+)/(\\d+)')
>>> m = c.match('11/27/2012abcdef') #此时把开头匹配到的返回了
>>> m
<re.Match object; span=(0, 10), match='11/27/2012'>
>>> m.group(0)
'11/27/2012'
>>> c = re.compile(r'(\d+)/(\d+)/(\d+)$') #以 $ 结尾时只有匹配整个字符串才会匹配成功
>>> m = c.match('11/27/2012abcdef')
>>> m #没有值

但是需要注意的是,如果你打算做大量的匹配和搜索操作的话,最好先编译正则表
达式,然后再重复使用它。模块级别的函数会将最近编译过的模式缓存起来,因此并
不会消耗太多的性能,但是如果使用预编译模式的话,你将会减少查找和一些额外的
处理损耗 **

对于简单的字符串替换,一般用 replace()即可

>>> t = 'yes, no'
>>> t.replace('yes', 'no')
'no, no'

对于复杂的模式。就需要用到 re 模块的 sub()函数

>>> text = 'Today is 11/27/2012. PyCon starts 3/13/2013.'
>>> re.sub(r'(\d+)/(\d+)/(\d+)', r'\3-\1\2-', text)
'Today is 2012-1127-. PyCon starts 2013-313-.'
#sub()函数第一个参数是匹配模式,第二个参数是替换模式反斜杠加数字表示匹配捕获的组号
#如果你打算用相同的模式做多次替换考虑先编译它来提升性能比如:>>> import re
>>> datepat = re.compile(r'(\d+)/(\d+)/(\d+)')
>>> datepat.sub(r'\3-\1-\2', text)
'Today is 2012-11-27. PyCon starts 2013-3-13.'

以忽略大小写的方式搜索与替换文本字符串

# 为了在文本操作时忽略大小写,你需要在使用 re 模块的时候给这些操作提供 re.IGNORECASE 标志参数。比如:>>> text = 'UPPER PYTHON, lower python, Mixed Python'
>>> re.findall('python', text, flags=re.IGNORECASE)
['PYTHON', 'python', 'Python']
>>> re.sub('python', 'snake', text, flags=re.IGNORECASE)
'UPPER snake, lower snake, Mixed snake'

非贪婪匹配

# 这个问题一般出现在需要匹配一对分隔符之间的文本的时候 (比如引号包含的字符串)。>>> c = re.compile(r'\"(.*)\"')
>>> c
re.compile('\\"(.*)\\"')
>>> text1 = 'Computer says"no."'
>>> c.findall(text1)
['no.']   #正确匹配
>>> text2 = 'Computer says"no."Phone says"yes."'
>>> c.findall(text2)
['no." Phone says "yes.']  #! 这时应该匹配['no', 'yes']
#在这个例子中,模式 r'\"(.*)\"' 的意图是匹配被双引号包含的文本。但是在正则表达式中 * 操作符是贪婪的,因此匹配操作会查找最长的可能匹配。于是在第二个例子中搜索 text2 的时候返回结果并不是我们想要的。#为了修正这个问题,可以在模式中的 * 操作符后面加上? 修饰符
>>> c = re.compile(r'\"(.*?)\"')
>>> c.findall(text2)
['no.', 'yes.']  #这样就会使匹配变成非贪婪的

多行匹配换行符

# (.)不能匹配换行符
>>> comment = re.compile(r'/\*(.*?)\*/') #/\*,/ 是转义字符,匹配 \*
>>> text1 = '/* this is a comment */'
>>> text2 = '''/* this is a
... multiline comment */
... '''
>>>
>>> comment.findall(text1)
['this is a comment']
>>> comment.findall(text2)  #(.)匹配除了换行的任意字符。如果指定了标签 DOTALL,它将匹配包括换行符的任意字符。此时字符串中间有换行符,匹配失败
[]
#re.compile() 函数接受一个标志参数叫 re.DOTALL,在这里非常有用。它可以让正则表达式中的点 (.) 匹配包括换行符在内的任意字符。比如:>>> comment = re.compile(r'/\*(.*?)\*/', re.DOTALL)
>>> comment.findall(text2)
['this is a\n multiline comment']

字符串对齐:format 函数

# 顺便写下来 format 吧。。>>> format(text, '>20')
'Hello World'
>>> format(text, '<20')
'Hello World'
>>> format(text, '^20')
'Hello World'
>>>
#如果你想指定一个非空格的填充字符,将它写到对齐字符的前面即可
>>> format(text, '=>20s')
'=========Hello World'
>>> format(text, '*^20s')
'****Hello World*****'
#当格式化多个值的时候,这些格式代码也可以被用在 format() 方法中
>>> '{:>10s} {:>10s}'.format('Hello', 'World')
'Hello World'
#format() 函数的一个好处是它不仅适用于字符串。它可以用来格式化任何值,使得它非常的通用
>>> x = 1.2345
>>> format(x, '>10')
'1.2345'
>>> format(x, '^10.2f')
'1.23'

当你想要拼接列表中的字符串和数字时,最有效的方法是生成器

>>> data = ['ACME', 50, 91.1]
>>> ','.join(str(d) for d in data)
'ACME,50,91.1

你想创建一个内嵌变量的字符串,变量被它的值所表示的字符串替换掉

# 一般情况下可以用 format 来实现
>>> s = '{name} has {n} messages.'
>>> s.format(name='Guido', n=37)
'Guido has 37 messages.'
#如果要被替换的变量能在变量域中找到,那么你可以结合使用 format map()和 vars()
>>> name = 'yang'
>>> n = 21
>>> s.format_map(vars())
'yang has 21 messages.'
  • .
    (点) 在默认模式,匹配除了换行的任意字符。如果指定了标签 DOTALL,它将匹配包括换行符的任意字符。
  • ^
    (插入符号) 匹配字符串的开头,并且在 MULTILINE 模式也匹配换行后的首个符号。
  • $
    匹配字符串尾或者换行符的前一个字符,在 MULTILINE 模式匹配换行符的前一个字符。foo 匹配 ‘foo’ 和 ‘foobar’ , 但正则 foo$ 只匹配 ‘foo’。更有趣的是,在 ‘foo1\nfoo2\n’ 搜索 foo.$,通常匹配 ‘foo2’,但在 MULTILINE 模式,可以匹配到 ‘foo1’;在 ‘foon’ 搜索 $ 会找到两个空串:一个在换行前,一个在字符串最后。
  • *
    对它前面的正则式匹配 0 到任意次重复,尽量多的匹配字符串。ab* 会匹配 ‘a’,‘ab’,或者 ‘a’后面跟随任意个 ‘b’。
  • +
    对它前面的正则式匹配 1 到任意次重复。ab+ 会匹配 ‘a’ 后面跟随 1 个以上到任意个 ‘b’,它不会匹配 ‘a’。
  • ?
    对它前面的正则式匹配 0 到 1 次重复。ab? 会匹配 ‘a’ 或者 ‘ab’。
  • *?, +?, ??
    ‘, ‘+’,和 ‘?’ 修饰符都是 贪婪的;它们在字符串进行尽可能多的匹配。有时候并不需要这种行为。如果正则式 <.> 希望找到 ‘ b <c>’,它将会匹配整个字符串,而不仅是 ”。在修饰符之后添加 ? 将使样式以 非贪婪方式或者 :dfn: 最小 方式进行匹配;尽量 少 的字符将会被匹配。使用正则式 <.*?> 将会仅仅匹配 ”。
  • “{m}”
    对其之前的正则式指定匹配 m 个重复;少于 m 的话就会导致匹配失败。比如,a{6} 将匹配 6 个 ‘a’ , 但是不能是 5 个。
  • “{m, n}”
    对正则式进行 m 到 n 次匹配,在 m 和 n 之间取尽量多。比如,a{3,5} 将匹配 3 到 5 个 ‘a’。忽略 m 意为指定下界为 0,忽略 n 指定上界为无限次。比如 a{4,}b 将匹配 ‘aaaab’ 或者 1000 个 ‘a’ 尾随一个 ‘b’,但不能匹配 ‘aaab’。逗号不能省略,否则无法辨别修饰符应该忽略哪个边界。
  • {m,n}?
    前一个修饰符的非贪婪模式,只匹配尽量少的字符次数。比如,对于 ‘aaaaaa’,a{3,5} 匹配 5 个 ‘a’,而 a{3,5}? 只匹配 3 个 ‘a’。
  • \
    转义特殊字符(允许你匹配 ‘*’, ‘?’, 或者此类其他),或者表示一个特殊序列;特殊序列之后进行讨论。如果你没有使用原始字符串(r’raw’)来表达样式,要牢记 Python 也使用反斜杠作为转义序列;如果转义序列不被 Python 的分析器识别,反斜杠和字符才能出现在字符串中。如果 Python 可以识别这个序列,那么反斜杠就应该重复两次。这将导致理解障碍,所以高度推荐,就算是最简单的表达式,也要使用原始字符串。
  • []
    用于表示一个字符集合。在一个集合中:字符可以单独列出,比如 [amk] 匹配 ‘a’,‘m’,或者 ‘k’。可以表示字符范围,通过用 ‘-‘ 将两个字符连起来。比如 [a-z] 将匹配任何小写 ASCII 字符,0-5 将匹配从 00 到 59 的两位数字,[0-9A-Fa-f] 将匹配任何十六进制数位。如果 – 进行了转义(比如 [a-z])或者它的位置在首位或者末尾(如 [-a] 或 [a-]),它就只表示普通字符 ‘-‘。特殊字符在集合中,失去它的特殊含义。比如 [(+)] 只会匹配这几个文法字符 ‘(‘, ‘+’, ‘‘, or ‘)’。字符类如 w 或者 S (如下定义) 在集合内可以接受,它们可以匹配的字符由 ASCII 或者 LOCALE 模式决定。不在集合范围内的字符可以通过 取反 来进行匹配。如果集合首字符是 ‘^’,所有 不 在集合内的字符将会被匹配,比如 1 将匹配所有字符,除了 ‘5’,2 将匹配所有字符,除了 ‘^’. ^ 如果不在集合首位,就没有特殊含义。在集合内要匹配一个字符 ‘]’,有两种方法,要么就在它之前加上反斜杠,要么就把它放到集合首位。比如,[()[]{}] 和 []()[{}] 都可以匹配括号。Unicode Technical Standard #18 里的嵌套集合和集合操作支持可能在未来添加。这将会改变语法,所以为了帮助这个改变,一个 FutureWarning 将会在有多义的情况里被 raise,包含以下几种情况,集合由 ‘[‘ 开始,或者包含下列字符序列 ‘–‘, ‘&&’, ‘~~’, 和 ‘||’。为了避免警告,需要将它们用反斜杠转义。在 3.7 版更改: 如果一个字符串构建的语义在未来会改变的话,一个 FutureWarning 会 raise。
  • |
    A|B,A 和 B 可以是任意正则表达式,创建一个正则表达式,匹配 A 或者 B. 任意个正则表达式可以用 ‘|’ 连接。它也可以在组合(见下列)内使用。扫描目标字符串时,‘|’ 分隔开的正则样式从左到右进行匹配。当一个样式完全匹配时,这个分支就被接受。意思就是,一旦 A 匹配成功,B 就不再进行匹配,即便它能产生一个更好的匹配。或者说,’|’ 操作符绝不贪婪。如果要匹配 ‘|’ 字符,使用 |,或者把它包含在字符集里,比如 [|].
  • (…)
    (组合),匹配括号内的任意正则表达式,并标识出组合的开始和结尾。匹配完成后,组合的内容可以被获取,并可以在之后用 number 转义序列进行再次匹配,之后进行详细说明。要匹配字符 ‘(‘ 或者 ‘)’, 用 (或), 或者把它们包含在字符集合里: [(], [)].
  • (?…)
    这是个扩展标记法(一个 ‘?’ 跟随 ‘(‘ 并无含义)。‘?’ 后面的第一个字符决定了这个构建采用什么样的语法。这种扩展通常并不创建新的组合;(?P<name>…) 是唯一的例外。以下是目前支持的扩展。
  • (?aiLmsux)
    (‘a’, ‘i’, ‘L’, ‘m’, ‘s’, ‘u’, ‘x’ 中的一个或多个) 这个组合匹配一个空字符串;这些字符对正则表达式设置以下标记 re.A (只匹配 ASCII 字符), re.I (忽略大小写), re.L (语言依赖), re.M (多行模式), re.S (点 dot 匹配全部字符), re.U (Unicode 匹配), and re.X (冗长模式)。(这些标记在 模块内容 中描述) 如果你想将这些标记包含在正则表达式中,这个方法就很有用,免去了在 re.compile() 中传递 flag 参数。标记应该在表达式字符串首位表示。
  • (?:…)
    正则括号的非捕获版本。匹配在括号内的任何正则表达式,但该分组所匹配的子字符串 不能 在执行匹配后被获取或是之后在模式中被引用。
  • (?aiLmsux-imsx:…)
    (‘a’, ‘i’, ‘L’, ‘m’, ‘s’, ‘u’, ‘x’ 中的 0 或者多个,之后可选跟随 ‘-‘ 在后面跟随 ‘i’ , ‘m’ , ‘s’ , ‘x’ 中的一到多个 .) 这些字符为表达式的其中一部分 设置 或者 去除 相应标记 re.A (只匹配 ASCII), re.I (忽略大小写), re.L (语言依赖), re.M (多行), re.S (点匹配所有字符), re.U (Unicode 匹配), and re.X (冗长模式)。(标记描述在 模块内容 .) ‘a’, ‘L’ and ‘u’ 作为内联标记是相互排斥的,所以它们不能结合在一起,或者跟随 ‘-‘。当他们中的某个出现在内联组中,它就覆盖了括号组内的匹配模式。在 Unicode 样式中,(?a:…) 切换为 只匹配 ASCII,(?u:…) 切换为 Unicode 匹配 (默认). 在 byte 样式中 (?L:…) 切换为语言依赖模式,(?a:…) 切换为 只匹配 ASCII (默认)。这种方式只覆盖组合内匹配,括号外的匹配模式不受影响。3.6 新版功能. 在 3.7 版更改: 符号 ‘a’, ‘L’ 和 ‘u’ 同样可以用在一个组合内。
  • (?P<name>…)
    (命名组合)类似正则组合,但是匹配到的子串组在外部是通过定义的 name 来获取的。组合名必须是有效的 Python 标识符,并且每个组合名只能用一个正则表达式定义,只能定义一次。一个符号组合同样是一个数字组合,就像这个组合没有被命名一样。命名组合可以在三种上下文中引用。如果样式是 (?P<quote>[‘”]).*?(?P=quote)(也就是说,匹配单引号或者双引号括起来的字符串):引用组合 “quote” 的上下文 引用方法 在正则式自身内 (?P=quote) (如示) 1 处理匹配对象 m m.group(‘quote’) m.end(‘quote’) (等) 传递到 re.sub() 里的 repl 参数中 g<quote> g<1> 1
  • (?P=name)
    反向引用一个命名组合;它匹配前面那个叫 name 的命名组中匹配到的串同样的字串。
  • (?#…)
    注释;里面的内容会被忽略。
  • (?=…)
    匹配 … 的内容,但是并不消费样式的内容。这个叫做 lookahead assertion。比如,Isaac (?=Asimov) 匹配 ‘Isaac ‘ 只有在后面是 ‘Asimov’ 的时候。
  • (?!…)
    匹配 … 不符合的情况。这个叫 negative lookahead assertion(前视取反)。比如说,Isaac (?!Asimov) 只有后面 不 是 ‘Asimov’ 的时候才匹配 ‘Isaac ‘。
  • (?<=…)
    匹配字符串的当前位置,它的前面匹配 … 的内容到当前位置。这叫:dfn:positive lookbehind assertion(正向后视断定)。(?<=abc)def 会在 ‘abcdef’ 中找到一个匹配,因为后视会往后看 3 个字符并检查是否包含匹配的样式。包含的匹配样式必须是定长的,意思就是 abc 或 a|b 是允许的,但是 a* 和 a{3,4} 不可以。注意以 positive lookbehind assertions 开始的样式,如 (?<=abc)def,并不是从 a 开始搜索,而是从 d 往回看的。你可能更加愿意使用 search() 函数,而不是 match() 函数:>>> import re >>> m = re.search(‘(?<=abc)def’, ‘abcdef’) >>> m.group(0) ‘def’ 这个例子搜索一个跟随在连字符后的单词:>>> m = re.search(r'(?<=-)w+’, ‘spam-egg’) >>> m.group(0) ‘egg’ 在 3.5 版更改: 添加定长组合引用的支持。
  • (?<!…)
    匹配当前位置之前不是 … 的样式。这个叫:dfn:negative lookbehind assertion(后视断定取非)。类似正向后视断定,包含的样式匹配必须是定长的。由 negative lookbehind assertion 开始的样式可以从字符串搜索开始的位置进行匹配。
  • (?(id/name)yes-pattern|no-pattern)
    如果给定的 id 或 name 存在,将会尝试匹配 yes-pattern,否则就尝试匹配 no-pattern,no-pattern 可选,也可以被忽略。比如,(<)?(w+@w+(?:.w+)+)(?(1)>|$) 是一个 email 样式匹配,将匹配 ‘<user@host.com>’ 或 ‘user@host.com’,但不会匹配 ‘<user@host.com’,也不会匹配 ‘user@host.com>’。

由 ” 和一个字符组成的特殊序列在以下列出。如果普通字符不是 ASCII 数位或者 ASCII 字母,那么正则样式将匹配第二个字符。比如,&dollar; 匹配字符 ‘$’.

  • number
    匹配数字代表的组合。每个括号是一个组合,组合从 1 开始编号。比如 (.+) 1 匹配 ‘the the’ 或者 ’55 55′, 但不会匹配 ‘thethe’ (注意组合后面的空格)。这个特殊序列只能用于匹配前面 99 个组合。如果 number 的第一个数位是 0,或者 number 是三个八进制数,它将不会被看作是一个组合,而是八进制的数字值。在 ‘[‘ 和 ‘]’ 字符集合内,任何数字转义都被看作是字符。
  • A
    只匹配字符串开始。
  • b
    匹配空字符串,但只在单词开始或结尾的位置。一个单词被定义为一个单词字符的序列。注意,通常 b 定义为 w 和 W 字符之间,或者 w 和字符串开始 / 结尾的边界,意思就是 r’bfoob’ 匹配 ‘foo’, ‘foo.’, ‘(foo)’, ‘bar foo baz’ 但不匹配 ‘foobar’ 或者 ‘foo3’。默认情况下,Unicode 字母和数字是在 Unicode 样式中使用的,但是可以用 ASCII 标记来更改。如果 LOCALE 标记被设置的话,词的边界是由当前语言区域设置决定的,b 表示退格字符,以便与 Python 字符串文本兼容。
  • B
    匹配空字符串,但 不 能在词的开头或者结尾。意思就是 r’pyB’ 匹配 ‘python’, ‘py3’, ‘py2’, 但不匹配 ‘py’, ‘py.’, 或者 ‘py!’. B 是 b 的取非,所以 Unicode 样式的词语是由 Unicode 字母,数字或下划线构成的,虽然可以用 ASCII 标志来改变。如果使用了 LOCALE 标志,则词的边界由当前语言区域设置。
  • d
    对于 Unicode (str) 样式:匹配任何 Unicode 十进制数(就是在 Unicode 字符目录 [Nd] 里的字符)。这包括了 [0-9],和很多其他的数字字符。如果设置了 ASCII 标志,就只匹配 [0-9]。对于 8 位 (bytes) 样式:匹配任何十进制数,就是 [0-9]。
  • D
    匹配任何非十进制数字的字符。就是 d 取非。如果设置了 ASCII 标志,就相当于 3
  • s
    对于 Unicode (str) 样式:匹配任何 Unicode 空白字符(包括 [tnrfv],还有很多其他字符,比如不同语言排版规则约定的不换行空格)。如果 ASCII 被设置,就只匹配 [tnrfv]。对于 8 位 (bytes) 样式:匹配 ASCII 中的空白字符,就是 [tnrfv]。
  • S
    匹配任何非空白字符。就是 s 取非。如果设置了 ASCII 标志,就相当于 4
  • w
    对于 Unicode (str) 样式:匹配 Unicode 词语的字符,包含了可以构成词语的绝大部分字符,也包括数字和下划线。如果设置了 ASCII 标志,就只匹配 [a-zA-Z0-9_]。对于 8 位 (bytes) 样式:匹配 ASCII 字符中的数字和字母和下划线,就是 [a-zA-Z0-9_]。如果设置了 LOCALE 标记,就匹配当前语言区域的数字和字母和下划线。
  • W
    匹配任何非词语字符。是 w 取非。如果设置了 ASCII 标记,就相当于 5。如果设置了 LOCALE 标志,就匹配当前语言区域的 非 词语字符。
  • Z
    只匹配字符串尾。

绝大部分 Python 的标准转义字符也被正则表达式分析器支持。:

\a      \b      \f      \n
\r      \t      \u      \U
\v      \x      \\

(注意 b 被用于表示词语的边界,它只在字符集合内表示退格,比如 [b]。)

‘u’ 和 ‘U’ 转义序列只在 Unicode 样式中支持。在 bytes 算啊看会显示错误。未知的 ASCII 字符转义序列保留在未来使用,会被当作错误来处理。

八进制转义包含为一个有限形式。如果首位数字是 0,或者有三个八进制数位,那么就认为它是八进制转义。其他的情况,就看作是组引用。对于字符串文本,八进制转义最多有三个数位长。


  1. 5 ↩
  2. ^ ↩
  3. 0-9 ↩
  4. tnrfv ↩
  5. a-zA-Z0-9_ ↩

正文完
 0