如何Python使用re模块实现okenizer


Posted in Python onApril 30, 2022

一个简单的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流

接下来我们想要过滤掉空格标记,使用生成器表达式即可:

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. 数学是符号的艺术,音乐是上界的语言。

到此这篇关于Python使用re模块实现okenizer的文章就介绍到这了!


Tags in this post...

Python 相关文章推荐
Python实现对PPT文件进行截图操作的方法
Apr 28 Python
Python连接MySQL并使用fetchall()方法过滤特殊字符
Mar 13 Python
python实现的正则表达式功能入门教程【经典】
Jun 05 Python
Python实现的科学计算器功能示例
Aug 04 Python
PyCharm在win10的64位系统安装实例
Nov 26 Python
python读取一个目录下所有txt里面的内容方法
Jun 23 Python
Python 查找list中的某个元素的所有的下标方法
Jun 27 Python
CentOS7下python3.7.0安装教程
Jul 30 Python
pandas通过loc生成新的列方法
Nov 28 Python
windows系统中Python多版本与jupyter notebook使用虚拟环境的过程
May 15 Python
Python内置函数locals和globals对比
Apr 28 Python
Pytorch使用shuffle打乱数据的操作
May 20 Python
如何使用python包中的sched事件调度器
Apr 30 #Python
详解OpenCV获取高动态范围(HDR)成像
详解OpenCV曝光融合
python使用shell脚本创建kafka连接器
Apr 29 #Python
python中pycryto实现数据加密
Apr 29 #Python
Python如何快速找到多个字典中的公共键(key)
Apr 29 #Python
Python如何让字典保持有序排列
Apr 29 #Python
You might like
杏林同学录(四)
2006/10/09 PHP
PHP OPP机制和模式简介(抽象类、接口和契约式编程)
2014/06/09 PHP
PHP 芝麻信用接入的注意事项
2016/12/01 PHP
yii2中LinkPager增加总页数和总记录数的实例
2017/08/28 PHP
详解php语言最牛掰的Laravel框架
2017/11/20 PHP
php多进程模拟并发事务产生的问题小结
2018/12/07 PHP
12个非常有创意的JavaScript小游戏
2010/03/18 Javascript
jquery中:input和input的区别分析
2011/07/13 Javascript
如何获取JQUERY AJAX返回的JSON结果集实现代码
2012/12/10 Javascript
jQuery学习之prop和attr的区别示例介绍
2013/11/15 Javascript
jQuery 实现侧边浮动导航菜单效果
2014/12/26 Javascript
JavaScript实现向setTimeout执行代码传递参数的方法
2015/04/16 Javascript
详解javascript事件冒泡
2016/01/09 Javascript
Bootstrap教程JS插件滚动监听学习笔记分享
2016/05/18 Javascript
jQuery 的 ready()的纯js替代方法
2016/11/20 Javascript
jquery代码规范让代码越来越好看
2017/02/03 Javascript
vue计算属性时v-for处理数组时遇到的一个bug问题
2018/01/21 Javascript
JS canvas实现画板和签字板功能
2021/02/23 Javascript
Python文件去除注释的方法
2015/05/25 Python
Python实现给文件添加内容及得到文件信息的方法
2015/05/28 Python
深入解析Python中的变量和赋值运算符
2015/10/12 Python
python3实现TCP协议的简单服务器和客户端案例(分享)
2017/06/14 Python
Django MEDIA的配置及用法详解
2019/07/25 Python
详解Python打包分发工具setuptools
2019/08/05 Python
Python实现名片管理系统
2020/02/14 Python
html5 Canvas画图教程(7)—canvas里画曲线之quadraticCurveTo方法
2013/01/09 HTML / CSS
Nordgreen手表德国官方网站:丹麦极简主义手表
2019/10/31 全球购物
手术室护士自我鉴定
2013/10/14 职场文书
制药工程专业毕业生推荐信
2013/12/24 职场文书
给学校的建议书
2014/03/12 职场文书
安全生产专项整治方案
2014/05/06 职场文书
建议书的格式
2014/05/12 职场文书
基层干部个人对照检查及整改措施
2014/10/28 职场文书
订货会邀请函
2015/01/31 职场文书
公文写作:教你写“建议书”
2019/05/07 职场文书
GO中sync包自由控制并发示例详解
2022/08/05 Golang