上QQ阅读APP看书,第一时间看更新
2.7 令牌解析
下面介绍将字符串从左至右解析为令牌流。
文本字符串示例如下:
text_v = 'foo = 23 + 42 * 10'
令牌化字符串不仅需要匹配模式,还得指定模式的类型。如将字符串转换为序列对,示例如下:
token_list = [('NAME', 'foo'), ('EQ','='), ('NUM', '23'), ('PLUS','+'), ('NUM', '42'), ('TIMES', '*'), ('NUM', '10')]
为了执行上述切分,第一步就是利用命名捕获组的正则表达式来定义所有可能的令牌,包括空格,代码(token_parser.py)如下:
import re NAME = r'(?P<NAME>[a-zA-Z_][a-zA-Z_0-9]*)' NUM = r'(?P<NUM>\d+)' PLUS = r'(?P<PLUS>\+)' TIMES = r'(?P<TIMES>\*)' EQ = r'(?P<EQ>=)' WS = r'(?P<WS>\s+)' master_pat = re.compile('|'.join([NAME, NUM, PLUS, TIMES, EQ, WS]))
在上面的模式中,?P<TOKENNAME>用于给定一个模式命名,供后面的程序使用。
为了使字符串令牌化,我们使用模式对象中的scanner()方法。该方法会创建一个Scanner对象,在这个对象上不断地调用match()方法扫描目标文本,相关代码(token_parser.py)示例如下:
scanner = master_pat.scanner('foo = 42') match_val = scanner.match() print(match_val) print(match_val.lastgroup, match_val.group()) match_val = scanner.match() print(match_val) print(match_val.lastgroup, match_val.group()) match_val = scanner.match() print(match_val) print(match_val.lastgroup, match_val.group()) match_val = scanner.match() print(match_val) print(match_val.lastgroup, match_val.group()) match_val = scanner.match() print(match_val) print(match_val.lastgroup, match_val.group()) match_val = scanner.match() print(match_val)
执行py文件,输出结果如下:
<re.Match object; span=(0, 3), match='foo'> NAME foo <re.Match object; span=(3, 4), match=' '> WS <re.Match object; span=(4, 5), match='='> EQ = <re.Match object; span=(5, 6), match=' '> WS <re.Match object; span=(6, 8), match='42'> NUM 42 None
实际使用这种技术的时候,我们可以很容易地将上述代码打包到一个生成器中,相关代码(token_parser.py)示例如下:
from collections import namedtuple def generate_tokens(pat, text): Token = namedtuple('Token', ['type', 'value']) pat_scanner = pat.scanner(text) for m in iter(pat_scanner.match, None): yield Token(m.lastgroup, m.group()) for tok in generate_tokens(master_pat, 'foo = 42'): print(tok)
执行py文件,输出结果如下:
Token(type='NAME', value='foo') Token(type='WS', value=' ') Token(type='EQ', value='=') Token(type='WS', value=' ') Token(type='NUM', value='42')
如果想过滤令牌流,可以定义更多的生成器函数或者使用一个生成器表达式,以下示例展示了过滤所有的空白令牌:
token_list = (tok for tok in generate_tokens(master_pat, text_v) if tok.type != 'WS') for tok in token_list: print(tok)
通常来讲,令牌化是很多高级文本解析与处理的第一步。
对于上述扫描方法,我们需要记住一点:必须确认使用正则表达式指定所有输入中可能出现的文本序列。如果有任何不可匹配的文本出现,扫描会直接停止。这也是上面示例中必须指定空白字符令牌的原因。
令牌的顺序对解析结果也是有影响的。re模块会按照指定好的顺序去做匹配。如果一个模式恰好是另一个更长模式的子字符串,那么需要确定长模式写在前面,示例如下:
LT = r'(?P<LT><)' LE = r'(?P<LE><=)' EQ = r'(?P<EQ>=)' master_pat = re.compile('|'.join([LE, LT, EQ])) master_pat = re.compile('|'.join([LT, LE, EQ]))
上述示例中,第二个模式是错的,它会将文本<=匹配为令牌LT紧跟着EQ,而不是单独的令牌LE,这并不是我们想要的结果。
最后,需要留意子字符串形式的模式,示例如下:
PRINT = r'(?P<PRINT>print)' NAME = r'(?P<NAME>[a-zA-Z_][a-zA-Z_0-9]*)' master_pat = re.compile('|'.join([PRINT, NAME])) for tok in generate_tokens(master_pat, 'printer'): print(tok)