如何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中sets模块的用法实例
Sep 30 Python
使用Python的Flask框架构建大型Web应用程序的结构示例
Jun 04 Python
Python实现从log日志中提取ip的方法【正则提取】
Mar 31 Python
python2.7实现FTP文件下载功能
Apr 15 Python
浅析Python 读取图像文件的性能对比
Mar 07 Python
Django用户认证系统 Web请求中的认证解析
Aug 02 Python
python动态文本进度条的实例代码
Jan 22 Python
Python定义函数实现累计求和操作
May 03 Python
Python多线程正确用法实例解析
May 30 Python
Elasticsearch py客户端库安装及使用方法解析
Sep 14 Python
python中numpy.empty()函数实例讲解
Feb 05 Python
python Tkinter的简单入门教程
Apr 11 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
模拟OICQ的实现思路和核心程序(二)
2006/10/09 PHP
PHP手机号码归属地查询代码(API接口/mysql)
2012/09/04 PHP
php实现SAE上使用storage上传与下载文件的方法
2015/06/29 PHP
PHP7 参数处理机制修改
2021/03/09 PHP
不间断滚动JS打包类,基本可以实现所有的滚动效果,太强了
2007/12/08 Javascript
用js 让图片在 div或dl里 居中,底部对齐
2008/01/21 Javascript
让div层随鼠标移动的实现代码 ie ff
2009/12/18 Javascript
删除select中所有option选项jquery代码
2013/08/12 Javascript
IE6/IE7中JavaScript json提示缺少标识符、字符串或数字问题处理
2014/12/16 Javascript
jQuery中:text选择器用法实例
2015/01/03 Javascript
Javascript中的几种URL编码方法比较
2015/01/23 Javascript
js实现三张图(文)片一起切换的banner焦点图
2015/08/25 Javascript
JQuery 两种方法解决刚创建的元素遍历不到的问题
2016/04/13 Javascript
详解vue的数据binding绑定原理
2017/04/12 Javascript
vue用addRoutes实现动态路由的示例
2017/09/15 Javascript
使用socket.io实现简单聊天室案例
2018/01/02 Javascript
解决Angular.js中使用Swiper插件不能滑动的问题
2018/02/26 Javascript
JS与jQuery实现ListBox上移,下移,左移,右移操作功能示例
2018/05/31 jQuery
关于element-ui的隐藏组件el-scrollbar的使用
2019/05/29 Javascript
深入了解js原型模式
2019/05/30 Javascript
[02:23]完美世界全国高校联赛街访DOTA2第一期
2019/11/28 DOTA
python生成圆形图片的方法
2020/03/25 Python
对python的bytes类型数据split分割切片方法
2018/12/04 Python
python绘制评估优化算法性能的测试函数
2019/06/25 Python
安装python依赖包psycopg2来调用postgresql的操作
2021/01/01 Python
Skyscanner澳大利亚:全球领先的旅游搜索网站
2018/03/24 全球购物
中学生自我鉴定
2014/02/04 职场文书
高中班级口号
2014/06/09 职场文书
村干部群众路线整改措施思想汇报
2014/10/12 职场文书
2014年卫生监督工作总结
2014/12/09 职场文书
2014年社区卫生工作总结
2014/12/18 职场文书
学校三八妇女节活动总结
2015/02/06 职场文书
2015年感恩父亲节演讲稿
2015/03/19 职场文书
社会实践活动总结格式
2015/05/11 职场文书
python中requests库+xpath+lxml简单使用
2021/04/29 Python
vue使用Google Recaptcha验证的实现示例
2021/08/23 Vue.js