python 字符串令牌解析

问题

你有一个字符串,想从左至右将其解析为一个令牌流。

解决方案

假如你有下面这样一个文本字符串:

1
text = 'foo = 23 + 42 * 10'

为了令牌化字符串,你不仅需要匹配模式,还得指定模式的类型。 比如,你可能想将字符串像下面这样转换为序列对:

1
2
tokens = [('NAME', 'foo'), ('EQ','='), ('NUM', '23'), ('PLUS','+'),
('NUM', '42'), ('TIMES', '*'), ('NUM', '10')]

为了执行这样的切分,第一步就是像下面这样利用命名捕获组的正则表达式来定义所有可能的令牌,包括空格:

1
2
3
4
5
6
7
8
9
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() 方法会一步步的扫描目标文本,每步一个匹配。 下面是演示一个 scanner 对象如何工作的交互式例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
>>> import re

>>> text = 'foo = 23 + 42 * 10'
>>> 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+)'

>>> pattern = re.compile('|'.join([NAME, NUM, PLUS, TIMES, EQ, WS]))
>>> pattern
re.compile('(?P<NAME>[a-zA-Z_][a-zA-Z_0-9]*)|(?P<NUM>\\d+)|(?P<PLUS>\\+)|(?P<TIMES>\\*)|(?P<EQ>=)|(?P<WS>\\s+)')

>>> scanner = pattern.scanner(text)
>>> while 1:
try:
_ = scanner.match()
print((_.lastgroup, _.group()))
except:
break

('NAME', 'foo')
('WS', ' ')
('EQ', '=')
('WS', ' ')
('NUM', '23')
('WS', ' ')
('PLUS', '+')
('WS', ' ')
('NUM', '42')
('WS', ' ')
('TIMES', '*')
('WS', ' ')
('NUM', '10')

关于更高阶的令牌化技术,你可能需要查看 PyParsing 或者 PLY 包。

-------------本文结束感谢您的阅读-------------