python每日读02-re模块与正则表达式
例程学习与注释
检查对子
在这个例子里,我们可以使用以下辅助函数来更好的显示匹配对象
def displaymatch(match):
if match is None:
return None
return '<Match: %r, groups=%r>' % (match.group(), match.groups())
假设你在写一个扑克程序,一个玩家的一手牌为五个字符的串,每个字符表示一张牌,“a” 就是 A, “k” K, “q” Q, “j” J, “t” 为 10, “2” 到 “9” 表示2 到 9。
要看给定的字符串是否有效,我们可以按照以下步骤
>>> valid = re.compile(r"^[a2-9tjqk]{5}$")
>>> displaymatch(valid.match("akt5q")) # Valid.
"<Match: 'akt5q', groups=()>"
>>> displaymatch(valid.match("akt5e")) # Invalid.
>>> displaymatch(valid.match("akt")) # Invalid.
>>> displaymatch(valid.match("727ak")) # Valid.
"<Match: '727ak', groups=()>"
最后一手牌,“727ak” ,包含了一个对子,或者两张同样数值的牌。要用正则表达式匹配它,应该使用向后引用如下
>>> pair = re.compile(r".*(.).*\1")
>>> displaymatch(pair.match("717ak")) # Pair of 7s.
"<Match: '717', groups=('7',)>"
>>> displaymatch(pair.match("718ak")) # No pairs.
>>> displaymatch(pair.match("354aa")) # Pair of aces.
"<Match: '354aa', groups=('a',)>"
要找到对子包含的是哪一张牌,应该按照下面的方式使用 group() 方法:
>>> pair.match("717ak").group(1)
'7'
# Error because re.match() returns None, which doesn't have a group() method:
>>> pair.match("718ak").group(1)
Traceback (most recent call last):
File "<pyshell#23>", line 1, in <module>
re.match(r".*(.).*\1", "718ak").group(1)
AttributeError: 'NoneType' object has no attribute 'group'
>>> pair.match("354aa").group(1)
'a'
模拟scanf()
下表提供scanf()格式符和正则表达式大致相同的映射
scanf()格式符 | 正则表达式 |
---|---|
%c | . |
%5c | .{5} |
%d | [-+]?\d+ |
%e, %E, %f, %g | [-+]?(\d+(\.\d*)?|\.\d+)([eE] [-+]?\d+)? |
%i | [-+]?(0[xX][\dA-Fa-f]+|0[0-7]*|\d+) |
%o | [-+]?[0-7]+ |
%s | \S+ |
%u | \d+ |
%x, %X | [-+]?(0[xX])?[\dA-Fa-f]+ |
例子:从文件名和数字提取 字符串
/usr/sbin/sendmail - 0 errors, 4 warnings
#对以上一段文本进行匹配
#scanf()格式化格式如下
%s - %d errors, %d warnings
#等价正则表达式如下
(\s+) - (\d+) errors, (\d+) warnings
'''(\s+) \s匹配所有的Unicode字符,‘+’表示对其前方的正则表达式匹配任意多次
同理(\d+)
'''
建立一个电话本
原文本如下:
>>> text =
"""Ross McFluff: 834.345.1254 155 Elm Street
...
... Ronald Heathmore: 892.345.3428 436 Finley Avenue
... Frank Burger: 925.541.7625 662 South Dogwood Way
...
...
... Heather Albrecht: 548.326.4584 919 Park Place"""
split()可以将字符串拆分成列表,从而便于修改文本的结构,十分有用。
>>> entries = re.split("\n+", text)
>>>#先将文本拆分成为一行一行的字段,而后对每一行就行正则匹配
>>> entries#展示拆分后的效果
['Ross McFluff: 834.345.1254 155 Elm Street',
'Ronald Heathmore: 892.345.3428 436 Finley Avenue',
'Frank Burger: 925.541.7625 662 South Dogwood Way',
'Heather Albrecht: 548.326.4584 919 Park Place']
>>> [re.split(":? ", entry, 4) for entry in entries]
#正则表达式":? "指匹配一个或者0个:后接一个空格的类似格式,即为可以匹配冒号空格,与纯空格两种情况。整句意为:以匹配的到的匹配对象为分割的标志,将整个entry文本分割四次。
[['Ross', 'McFluff', '834.345.1254', '155', 'Elm Street'],
['Ronald', 'Heathmore', '892.345.3428', '436', 'Finley Avenue'],
['Frank', 'Burger', '925.541.7625', '662', 'South Dogwood Way'],
['Heather', 'Albrecht', '548.326.4584', '919', 'Park Place']]
同由此我们就将用户的名字、性氏、电话号码以及房间号分离出来了
文字整理
sub( )可以将字符中匹配到的样式替换为你自己想要的文字格式,以下整个例子就很哈的使用了sub( )。
>>> def repl(m):
... inner_word = list(m.group(2))#生成中间文本列表
... random.shuffle(inner_word)#将中间文本列表打乱
... return m.group(1) + "".join(inner_word) + m.group(3)
#返回一个中间的单词被打乱的字符串
>>> text = "Professor Abdolmalek, please report your absences promptly."
>>> re.sub(r"(\w)(\w+)(\w)", repl, text)
'''匹配单词的首字母,第二个到倒数第二个字母,以及尾字母,生成三个匹配组。分别为‘P’‘refesso’‘r’。将中间的字符串,在此例子中为‘refesso’,传入repl()函数中打乱,将打乱得到的字符串替换原本的中间的字符串(‘refesso’)
'''
'Poefsrosr Aealmlobdk, pslaee reorpt your abnseces plmrptoy.'
>>> re.sub(r"(\w)(\w+)(\w)", repl, text)
'Pofsroser Aodlambelk, plasee reoprt yuor asnebces potlmrpy.'
整体操作
找到所有副词
findall( )将会匹配所有的能够同正则表达式匹配的字符串,而search( )只能够匹配第一个与正则表达式匹配的字符串,可以用作大批量文本的操作。
>>> text = "He was carefully disguised but captured quickly by police."
>>> re.findall(r"\w+ly", text)#这个则是匹配所有以ly结尾的字符串
['carefully', 'quickly']
找到所有副词和位置
finditer( )同findall( )的返回值不同,将会返回一个匹配对象,因此可以对匹配到的结果进行与匹配对象有关的相关操作。可以得到匹配对象在原文本中的位置等其它的详细信息
>>> text = "He was carefully disguised but captured quickly by police."
>>> for m in re.finditer(r"\w+ly", text):#同上匹配含义
... print('%02d-%02d: %s' % (m.start(), m.end(), m.group(0)))
07-16: carefully
40-47: quickly
原始字符记法
(r"text") 保持正则表达式正常。否则,每个正则式里的反斜杠(‘’) 都必须前缀一个反斜杠来转义。比如,下面两行代码功能就是完全一致的
>>> re.match(r"\W(.)\1\W", " ff ")
<re.Match object; span=(0, 4), match=' ff '>
>>> re.match("\\W(.)\\1\\W", " ff ")
<re.Match object; span=(0, 4), match=' ff '>
当需要匹配一个字符反斜杠,它必须在正则表达式中转义。在原始字符串记法,就是 r"\"。否则就必须用 “\\”,来表示同样的意思
>>> re.match(r"\\", r"\\")
<re.Match object; span=(0, 1), match='\\'>
>>> re.match("\\\\", r"\\")
<re.Match object; span=(0, 1), match='\\'>
写一个词法分析器
这个例子的目的是写一个词法器或者词法分析器分析自负床,并且分类为目录组
import collections
import re
Token = collections.namedtuple('Token', ['type', 'value', 'line', 'column'])
def tokenize(code):
keywords = {'IF', 'THEN', 'ENDIF', 'FOR', 'NEXT', 'GOSUB', 'RETURN'}
token_specification = [
('NUMBER', r'\d+(\.\d*)?'), # 整数或十进制数 \d+ 匹配整数或小数的整数部分
# (\.\d*)\.匹配.字符,后借0到任意多位的整数
# ? 最后的?表示(\.\d*)这一部分匹不匹配都行
('ASSIGN', r':='), # Assignment operator
''' 仅仅匹配=运算符'''
('END', r';'), # Statement terminator
'''仅匹配;'''
('ID', r'[A-Za-z]+'), # Identifiers
'''[A-Za-z]匹配一位英文字符,不论大小写
+ 对[A-Za-z]进行1次到任意次的匹配'''
('OP', r'[+\-*/]'), # Arithmetic operators
'''直接匹配+ -(这里用了\来转义)* / 四个符号
'''
('NEWLINE', r'\n'), # Line endings
'''直接匹配\n'''
('SKIP', r'[ \t]+'), # Skip over spaces and tabs
'''[ \t] 匹配空格即" "或者\t
+ 对[A-Za-z]进行1次到任意次的匹配'''
('MISMATCH', r'.'), # Any other character
#'.'匹配除了换行符的任意字符
]
tok_regex = '|'.join('(?P<%s>%s)' % pair for pair in token_specification)#将所有正则表达式用或逻辑连接
#(?P<%s>%s) 是(?P<name>...)的匹配形式,其中token_specification的每个元组的左侧字符串为name,右侧的正则表达式为...
line_num = 1
line_start = 0
for mo in re.finditer(tok_regex, code):
kind = mo.lastgroup
value = mo.group()
column = mo.start() - line_start
if kind == 'NUMBER':
value = float(value) if '.' in value else int(value)
elif kind == 'ID' and value in keywords:
kind = value
elif kind == 'NEWLINE':
line_start = mo.end()
line_num += 1
continue
elif kind == 'SKIP':
continue
elif kind == 'MISMATCH':
raise RuntimeError(f'{value!r} unexpected on line {line_num}')
yield Token(kind, value, line_num, column)
statements = '''
IF quantity THEN
total := total + price * quantity;
tax := price * 0.05;
ENDIF;
'''
for token in tokenize(statements):
print(token)