2.18 å—符串令牌解æž�¶
问题¶
ä½ æœ‰ä¸€ä¸ªå—符串,想从左至å�³å°†å…¶è§£æž�为一个令牌æµ�。
解决方案¶
å�‡å¦‚ä½ æœ‰ä¸‹é�¢è¿™æ ·ä¸€ä¸ªæ–‡æœ¬å—符串:
text = 'foo = 23 + 42 * 10'
为了令牌化å—ç¬¦ä¸²ï¼Œä½ ä¸�仅需è¦�匹é…�模å¼�,还得指定模å¼�的类型。 æ¯”å¦‚ï¼Œä½ å�¯èƒ½æƒ³å°†å—符串åƒ�下é�¢è¿™æ ·è½¬æ�¢ä¸ºåº�列对:
tokens = [('NAME', 'foo'), ('EQ','='), ('NUM', '23'), ('PLUS','+'),
('NUM', '42'), ('TIMES', '*'), ('NUM', '10')]
ä¸ºäº†æ‰§è¡Œè¿™æ ·çš„åˆ‡åˆ†ï¼Œç¬¬ä¸€æ¥å°±æ˜¯åƒ�下é�¢è¿™æ ·åˆ©ç”¨å‘½å��æ�•获组的æ£åˆ™è¡¨è¾¾å¼�æ�¥å®šä¹‰æ‰€æœ‰å�¯èƒ½çš„ä»¤ç‰Œï¼ŒåŒ…æ‹¬ç©ºæ ¼ï¼š
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 对象如何工作的交互å¼�例å�:
>>> scanner = master_pat.scanner('foo = 42')
>>> scanner.match()
<_sre.SRE_Match object at 0x100677738>
>>> _.lastgroup, _.group()
('NAME', 'foo')
>>> scanner.match()
<_sre.SRE_Match object at 0x100677738>
>>> _.lastgroup, _.group()
('WS', ' ')
>>> scanner.match()
<_sre.SRE_Match object at 0x100677738>
>>> _.lastgroup, _.group()
('EQ', '=')
>>> scanner.match()
<_sre.SRE_Match object at 0x100677738>
>>> _.lastgroup, _.group()
('WS', ' ')
>>> scanner.match()
<_sre.SRE_Match object at 0x100677738>
>>> _.lastgroup, _.group()
('NUM', '42')
>>> scanner.match()
>>>
实际使用这ç§�技术的时候,å�¯ä»¥å¾ˆå®¹æ˜“çš„åƒ�下é�¢è¿™æ ·å°†ä¸Šè¿°ä»£ç �打包到一个生æˆ�器ä¸ï¼š
def generate_tokens(pat, text):
Token = namedtuple('Token', ['type', 'value'])
scanner = pat.scanner(text)
for m in iter(scanner.match, None):
yield Token(m.lastgroup, m.group())
# Example use
for tok in generate_tokens(master_pat, 'foo = 42'):
print(tok)
# Produces output
# Token(type='NAME', value='foo')
# Token(type='WS', value=' ')
# Token(type='EQ', value='=')
# Token(type='WS', value=' ')
# Token(type='NUM', value='42')
å¦‚æžœä½ æƒ³è¿‡æ»¤ä»¤ç‰Œæµ�ï¼Œä½ å�¯ä»¥å®šä¹‰æ›´å¤šçš„生æˆ�器函数或者使用一个生æˆ�器表达å¼�。 比如,下é�¢æ¼”ç¤ºæ€Žæ ·è¿‡æ»¤æ‰€æœ‰çš„ç©ºç™½ä»¤ç‰Œï¼š
tokens = (tok for tok in generate_tokens(master_pat, text)
if tok.type != 'WS')
for tok in tokens:
print(tok)
讨论¶
通常æ�¥è®²ä»¤ç‰ŒåŒ–是很多高级文本解æž�与处ç�†çš„第一æ¥ã€‚ 为了使用上é�¢çš„æ‰«æ��æ–¹æ³•ï¼Œä½ éœ€è¦�è®°ä½�这里一些é‡�è¦�çš„å‡ ç‚¹ã€‚ ç¬¬ä¸€ç‚¹å°±æ˜¯ä½ å¿…é¡»ç¡®è®¤ä½ ä½¿ç”¨æ£åˆ™è¡¨è¾¾å¼�指定了所有输入ä¸å�¯èƒ½å‡ºçŽ°çš„æ–‡æœ¬åº�列。 如果有任何ä¸�å�¯åŒ¹é…�的文本出现了,扫æ��就会直接å�œæ¢ã€‚这也是为什么上é�¢ä¾‹å�ä¸å¿…须指定空白å—ç¬¦ä»¤ç‰Œçš„åŽŸå› ã€‚
令牌的顺�也是有影�的。 re 模�会按照指定好的顺�去�匹�。
å› æ¤ï¼Œå¦‚果一个模å¼�æ�°å¥½æ˜¯å�¦ä¸€ä¸ªæ›´é•¿æ¨¡å¼�çš„å�å—ç¬¦ä¸²ï¼Œé‚£ä¹ˆä½ éœ€è¦�确定长模å¼�写在å‰�é�¢ã€‚比如:
LT = r'(?P<LT><)'
LE = r'(?P<LE><=)'
EQ = r'(?P<EQ>=)'
master_pat = re.compile('|'.join([LE, LT, EQ])) # Correct
# master_pat = re.compile('|'.join([LT, LE, EQ])) # Incorrect
第二个模å¼�æ˜¯é”™çš„ï¼Œå› ä¸ºå®ƒä¼šå°†æ–‡æœ¬<=匹é…�为令牌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)
# Outputs :
# Token(type='PRINT', value='print')
# Token(type='NAME', value='er')
å…³äºŽæ›´é«˜é˜¶çš„ä»¤ç‰ŒåŒ–æŠ€æœ¯ï¼Œä½ å�¯èƒ½éœ€è¦�查看 PyParsing 或者 PLY 包。 一个调用PLY的例å�在下一节会有演示。