如何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 相关文章推荐
python2.7删除文件夹和删除文件代码实例
Dec 18 Python
忘记ftp密码使用python ftplib库暴力破解密码的方法示例
Jan 22 Python
简单的Python抓taobao图片爬虫
Oct 26 Python
Python实现的简单发送邮件脚本分享
Nov 07 Python
使用Python编写vim插件的简单示例
Apr 17 Python
Python中设置变量访问权限的方法
Apr 27 Python
Python遍历文件夹和读写文件的实现方法
May 10 Python
Python+树莓派+YOLO打造一款人工智能照相机
Jan 02 Python
python模拟表单提交登录图书馆
Apr 27 Python
Python的matplotlib绘图如何修改背景颜色的实现
Jul 16 Python
用Python批量把文件复制到另一个文件夹的实现方法
Aug 16 Python
Python调用Windows命令打印文件
Feb 07 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
解析如何屏蔽php中的phpinfo()函数
2013/06/06 PHP
PHP上传图片类显示缩略图功能
2016/06/30 PHP
Javascript的一种模块模式
2010/09/08 Javascript
详细介绍jQuery.outerWidth() 函数具体用法
2015/07/20 Javascript
jquery实现鼠标滑过显示二级下拉菜单效果
2015/08/24 Javascript
分类解析jQuery选择器
2016/11/23 Javascript
ES2015 Symbol 一种绝不重复的值
2016/12/25 Javascript
js中作用域的实例解析
2017/03/16 Javascript
详解webpack打包nodejs项目(前端代码)
2018/09/19 NodeJs
详解Vue SSR( Vue2 + Koa2 + Webpack4)配置指南
2018/11/13 Javascript
axios+Vue实现上传文件显示进度功能
2019/04/14 Javascript
vue的keep-alive中使用EventBus的方法
2019/04/23 Javascript
Vue.js中provide/inject实现响应式数据更新的方法示例
2019/10/16 Javascript
在vue中使用axios实现post方式获取二进制流下载文件(实例代码)
2019/12/16 Javascript
详解微信小程序工程化探索之webpack实战
2020/04/20 Javascript
Vue elementui字体图标显示问题解决方案
2020/08/18 Javascript
解决antd 下拉框 input [defaultValue] 的值的问题
2020/10/31 Javascript
Ant Design Vue table中列超长显示...并加提示语的实例
2020/10/31 Javascript
vue组件是如何解析及渲染的?
2021/01/13 Vue.js
[00:44]华丽开场!DOTA2勇士令状带来全新对阵画面
2019/05/15 DOTA
Python实现多线程下载文件的代码实例
2014/06/01 Python
python网络编程学习笔记(五):socket的一些补充
2014/06/09 Python
python使用urllib2实现发送带cookie的请求
2015/04/28 Python
python找出完数的方法
2018/11/12 Python
学会迭代器设计模式,帮你大幅提升python性能
2021/01/03 Python
欧洲高端品牌直销店:Fashionesta
2016/08/31 全球购物
中国双语服务优势的在线购票及活动平台:247tickets
2018/10/26 全球购物
Armor Lux法国官方网站:水手服装、成衣和内衣
2020/05/26 全球购物
计算机网络工程专业职业生涯规划书
2014/03/10 职场文书
买房协议书
2014/04/11 职场文书
学校校庆演讲稿
2014/05/22 职场文书
班级体育活动总结
2014/07/05 职场文书
中国梦团日活动总结
2014/07/07 职场文书
详解Js模块化的作用原理和方案
2021/04/29 Javascript
Java循环队列与非循环队列的区别总结
2021/06/22 Java/Android
IDEA2021.2配置docker如何将springboot项目打成镜像一键发布部署
2021/09/25 Java/Android