Python如何用正则表达式实现tokenizer
Admin 2022-09-15 群英技术资讯 335 次浏览
在这篇文章中我们来了解一下“Python如何用正则表达式实现tokenizer”,一些朋友可能会遇到这方面的问题,对此在下文小编向大家来讲解一下,内容详细,易于理解,希望大家阅读完这篇能有收获哦,有需要的朋友就往下看吧!
分词(tokenization)任务是Python字符串处理中最为常见任务了。我们这里讲解用正则表达式构建简单的表达式分词器(tokenizer),它能够将表达式字符串从左到右解析为标记(tokens)流。
给定如下的表达式字符串:
text = 'foo = 12 + 5 * 6'
我们想要将其转换为下列以序列对呈现的分词结果:
tokens = [('NAME', 'foo'), ('EQ', '='), ('NUM', '12'), ('PLUS', '+'),\ ('NUM', '5'), ('TIMES', '*'), ('NUM', '6')]
要完成这样的分词操作,我们首先需要定义出所有可能的标记模式(所谓模式(pattern),为用来描述或者匹配/系列匹配某个句法规则的字符串,这里我们用正则表达式来做为模式),注意此处要包括空格whitespace,否则字符串中出现任何模式中没有的字符后,扫描就会停止。因为我们还需要给标记以NAME、EQ等名称,我们采用正则表达式中的命名捕获组来实现。
import re NAME = r'(?P<NAME>[a-zA-Z_][a-zA-Z_0-9]*)' # 这里?P<NAME>表示模式名称,()表示一个正则表达式捕获组,合在一起即一个命名捕获组 EQ = r'(?P<EQ>=)' NUM = r'(?P<NUM>\d+)' #\d表示匹配数字,+表示任意数量 PLUS = r'(?P<PLUS>\+)' #需要用\转义 TIMES = r'(?P<TIMES>\*)' #需要用\转义 WS = r'(?P<WS>\s+)' #\s表示匹配空格, +表示任意数量 master_pat = re.compile("|".join([NAME, EQ, NUM, PLUS, TIMES, WS])) # | 用于选择多个模式,表示"或"
接下来我们用模式对象中的scanner()
方法来完成分词操作,该方法创建一个扫描对象:
scanner = master_pat.scanner(text)
然后可以用match()
方法获取单次匹配结果,一次匹配一个模式:
scanner = master_pat.scanner(text) m = scanner.match() print(m.lastgroup, m.group()) # NAME foo m = scanner.match() print(m.lastgroup, m.group()) # WS
当然这样一次一次调用过于麻烦,我们可以使用迭代器来批量调用,并将单次迭代结果以具名元组形式存储
Token = namedtuple('Token', ['type', 'value']) def generate_tokens(pat, text): scanner = pat.scanner(text) for m in iter(scanner.match, None): #scanner.match做为迭代器每次调用的方法, #None为哨兵的默认值,表示迭代到None停止 yield Token(m.lastgroup, m.group()) for tok in generate_tokens(master_pat, "foo = 42"): print(tok)
最终显示表达式串"foo = 12 + 5 * 6"
的tokens流为:
Token(type='NAME', value='foo') Token(type='WS', value=' ') Token(type='EQ', value='=') Token(type='WS', value=' ') Token(type='NUM', value='12') Token(type='WS', value=' ') Token(type='PLUS', value='+') Token(type='WS', value=' ') Token(type='NUM', value='5') Token(type='WS', value=' ') Token(type='TIMES', value='*') Token(type='WS', value=' ') Token(type='NUM', value='6')
接下来我们想要过滤掉空格标记,使用生成器表达式即可:
tokens = (tok for tok in generate_tokens(master_pat, "foo = 12 + 5 * 6") if tok.type != 'WS') for tok in tokens: print(tok)
可以看到空格被成功过滤:
Token(type='NAME', value='foo') Token(type='EQ', value='=') Token(type='NUM', value='12') Token(type='PLUS', value='+') Token(type='NUM', value='5') Token(type='TIMES', value='*') Token(type='NUM', value='6')
tokens在正则表达式(即"|".join([NAME, EQ, NUM, PLUS, TIMES, WS])
)中顺序也非常重要。因为在进行匹配时,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)
可以看到被print
实际上成了另一个模式的子串,导致另一个模式的匹配出现了问题:
# Token(type='PRINT', value='print') # Token(type='NAME', value='er')
更高级的语法分词,建议采用像PyParsing或PLY这样的包。特别地,对于英文自然语言文章的分词,一般被集成到各类NLP的包中(一般分为按空格拆分、处理前后缀、去掉停用词三步骤)。对于中文自然语言处理分词也有丰富的工具(比如jieba
分词工具包)。
[1] Martelli A, Ravenscroft A, Ascher D. Python cookbook[M]. " O'Reilly Media, Inc.", 2015. 数学是符号的艺术,音乐是上界的语言。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:mmqy2019@163.com进行举报,并提供相关证据,查实之后,将立刻删除涉嫌侵权内容。
猜你喜欢
这篇文章主要介绍了Python字符串中如何去除数字之间的逗号,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
这篇文章主要介绍了通过Pygame模块实现监听鼠标的功能,文章的示例代码讲解详细,对我们的学习或工作有一定的价值,感兴趣的小伙伴可以了解一下
开发中对数组、列表去重是非常常见的需求,对一个list中的id进行去重,有下面几种方法,前面两种方法不能保证顺序, 后面两种方法可以保持
CGI 程序可以是 Python 脚本,PERL 脚本,SHELL 脚本,C 或者 C++ 程序等。CGI 目前由 NCSA 维护,NCSA 定义 CGI 如下: CGI(Common Gateway Interface),通用网关接口,它是一段程序,运行在服务器上如:HTTP服务器,提供同客户端 HTML 页面的接口。
logging模块是Python内置的标准模块,主要用于输出脚本运行日志,可以设置输出日志的等级、日志保存路径等。
成为群英会员,开启智能安全云计算之旅
立即注册Copyright © QY Network Company Ltd. All Rights Reserved. 2003-2020 群英 版权所有
增值电信经营许可证 : B1.B2-20140078 粤ICP备09006778号 域名注册商资质 粤 D3.1-20240008