将一个字符串分割为多个字段,但是分隔符(还有周围的空格)并不是固定的。
string 对象的 split() 方法只适应于非常简单的字符串分割情形, 它不允许有多个分隔符或者是分隔符周围不确定的空格。 当需要更加灵活的切割字符串的时候,应该使用 re.split()方法:
import re
line = 'asdf fjdk; afed, fjek,asdf, foo'
print(re.split(r'[;,]', line))
# >>> ['asdf fjdk', ' afed', ' fjek', 'asdf', ' foo']
print(re.split(r'[;,\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)) # \s* 表示连续的空格
# >>> ['asdf', 'fjdk', 'afed', 'fjek', 'asdf', 'foo']
函数 re.split() 是非常实用的,因为它允许为分隔符指定多个正则模式。 比如,在上面的例子中,分隔符可以是逗号,分号或者是空格,并且后面紧跟着任意个的空格。 只要这个模式被找到,那么匹配的分隔符两边的实体都会被当成是结果中的元素返回。 返回结果为一个字段列表,这个跟 str.split() 返回值类型是一样的。
当我们需要通过指定的文本模式去检查字符串的开头或者结尾,比如文件名后缀,URL Scheme等等。
检查字符串开头或结尾的一个简单方法是使用 str.startswith() 或者是 str.endswith() 方法。比如:
filename = 'spam.txt'
print(filename.endswith('.txt'))
# >>> True
print(filename.startswith('file:'))
# >>> False
url = 'http://www.python.org'
print(url.startswith('http:'))
# >>>True
如果我们想检查多种匹配可能,只需要将所有的匹配项放入到一个元组中去, 然后传给 startswith() 或者 endswith() 方法:
import os
filenames = os.listdir('.')
print(filenames)
"""
>>> ['dictOperation.py', 'lookupNelement.py', 'MatchStartEnd.py',
'multidict.py', 'preorityQueue.py', 'saveNelements.py', 'SegString.py',
'somefile.txt', 'yield_experiment.py']
"""
print ( [name for name in filenames if name.endswith(('.txt','.ini'))] )
# >>> ['somefile.txt', 'text.ini']
print( any(name.endswith('.py') for name in filenames) )
当我们想匹配或者搜索特定模式的文本的时候该如何处理?
如果想匹配的是字面字符串,那么通常只需要调用基本字符串方法就行, 比如 str.find() , str.endswith() , str.startswith() 或者类似的方法:
text = 'yeah, but no, but yeah, but no, but yeah'
# Exact match
print( text == 'yeah' )
# >>> False
# Match at start or end
print( text.startswith('yeah') )
# >>> True
print( text.endswith('no') )
# >>> False
# Search for the location of the first occurrence
print( text.find('no') )
# >>> 10
对于复杂的匹配需要使用正则表达式和 re 模块。 为了解释正则表达式的基本原理,假设想匹配数字格式的日期字符串比如 11/27/2012 ,我们可以这样做:
text1 = '11/27/2012'
text2 = 'Nov 27, 2012'
import re
# Simple matching: \d+ means match one or more digits
if re.match(r'\d+/\d+/\d+', text1):
print('yes')
else:
print('no')
# >>> yes
if re.match(r'\d+/\d+/\d+', text2):
print('yes')
else:
print('no')
# >>> no
如果我们想使用同一个模式去做多次匹配,那么应该先将模式字符串预编译为模式对象。比如:
text1 = '11/27/2012'
text2 = 'Nov 27, 2012'
import re
datepat = re.compile(r'\d+/\d+/\d+')
if datepat.match(text1):
print('yes')
else:
print('no')
# >>> yes
if datepat.match(text2):
print('yes')
else:
print('no')
# >>> no
match() 总是从字符串开始去匹配,如果你想查找字符串任意部分的模式出现位置, 使用 findall() 方法去代替。比如:
text = 'Today is 11/27/2012. PyCon starts 3/13/2013.'
print( datepat.findall(text) )
# >>> ['11/27/2012', '3/13/2013']
findall() 方法会搜索文本并以列表形式返回所有的匹配。 如果我们想以迭代方式返回匹配,可以使用 finditer() 方法来代替,比如:
datepat_new = re.compile(r'(\d+)/(\d+)/(\d+)') # group
for m in datepat_new .finditer(text):
print(m.groups())
# >>> ('11', '27', '2012')
# ('3', '13', '2013')
核心步骤就是先使用 re.compile() 编译正则表达式字符串, 然后使用 match() , findall() 或者 finditer() 等方法。 当写正则式字符串的时候,相对普遍的做法是使用原始字符串比如 r'(\d+)/(\d+)/(\d+)' 。 这种字符串将不去解析反斜杠,这在正则表达式中是很有用的。
对于简单的字面模式,直接使用 str.replace() 方法即可,比如:
text = 'yeah, but no, but yeah, but no, but yeah'
print( text.replace('yeah', 'yep') )
# >>> yep, but no, but yep, but no, but yep
对于复杂的模式,我们应该使用 re 模块中的 sub() 函数。假设我们想将形式为 11/27/2012 的日期字符串改成 2012-11-27 。示例如下:
text = 'Today is 11/27/2012. PyCon starts 3/13/2013.'
import re
print( re.sub(r'(\d+)/(\d+)/(\d+)', r'\3-\1-\2', text) )
# >>> Today is 2012-11-27. PyCon starts 2013-3-13.
sub() 函数中的第一个参数是被匹配的模式,第二个参数是替换模式。反斜杠数字比如 \3 指向前面模式的捕获组号。
如果我们打算用相同的模式做多次替换,可以考虑先编译re.compile()它来提升性能。比如:
text = 'Today is 11/27/2012. PyCon starts 3/13/2013.'
import re
datepat = re.compile(r'(\d+)/(\d+)/(\d+)')
print( datepat.sub(r'\3-\1-\2', text) )
# >>> Today is 2012-11-27. PyCon starts 2013-3-13.
其实,sub()已经足够强大了,剩下的更难的问题,可能就是自己编写正则化表达式了。
博文主要参考《python3-codebook》.