如何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实现从百度API获取天气的方法
Mar 11 Python
python按照多个字符对字符串进行分割的方法
Mar 17 Python
Python functools模块学习总结
May 09 Python
Python虚拟环境Virtualenv使用教程
May 18 Python
python基础教程之分支、循环简单用法
Jun 16 Python
Pandas:Series和DataFrame删除指定轴上数据的方法
Nov 10 Python
解决Python一行输出不显示的问题
Dec 03 Python
selenium在执行phantomjs的API并获取执行结果的方法
Dec 17 Python
python分割一个文本为多个文本的方法
Jul 22 Python
Django1.11配合uni-app发起微信支付的实现
Oct 12 Python
python-视频分帧&amp;多帧合成视频实例
Dec 10 Python
OpenCV 图像梯度的实现方法
Jul 25 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中限制ip段访问、禁止ip提交表单的代码分享
2014/08/22 PHP
thinkPHP连接sqlite3数据库的实现方法(附Thinkphp代码生成器下载)
2016/05/27 PHP
PHP中散列密码的安全性分析
2019/07/26 PHP
基于jQuery的弹出消息插件 DivAlert之旅(一)
2010/04/01 Javascript
javascript下数值型比较难点说明
2010/06/07 Javascript
JS保留小数点(四舍五入、四舍六入)实现思路及实例
2013/04/25 Javascript
IE8下String的Trim()方法失效的解决方法
2013/11/08 Javascript
jQuery中contents()方法用法实例
2015/01/08 Javascript
Windows系统下Node.js的简单入门教程
2015/06/23 Javascript
js实现的页面矩阵图形变换特效
2016/01/26 Javascript
理解javascript模块化
2016/03/28 Javascript
jquery中取消和绑定hover事件的实现代码
2016/06/02 Javascript
node.js缺少mysql模块运行报错的解决方法
2016/11/13 Javascript
神级程序员JavaScript300行代码搞定汉字转拼音
2017/05/20 Javascript
30分钟快速入门掌握ES6/ES2015的核心内容(上)
2018/04/18 Javascript
如何获取TypeScript的声明文件.d.ts
2018/05/01 Javascript
详解Vue CLI3配置解析之css.extract
2018/09/14 Javascript
小程序云开发获取不到数据库记录的解决方法
2019/05/18 Javascript
JS回调函数 callback的理解与使用案例分析
2019/09/09 Javascript
Vue-cli3多页面配置详解
2020/03/22 Javascript
[50:11]2018DOTA2亚洲邀请赛 4.7总决赛 LGD vs Mineski 第三场
2018/04/09 DOTA
Python写入CSV文件的方法
2015/07/08 Python
Python中字符串格式化str.format的详细介绍
2017/02/17 Python
Python爬虫实战:分析《战狼2》豆瓣影评
2018/03/26 Python
SkinCeuticals官网:美国药妆品牌
2018/04/19 全球购物
幼师专业毕业生自荐信
2013/09/29 职场文书
工作中个人的自我评价
2013/12/31 职场文书
大学毕业自我鉴定范文
2014/02/03 职场文书
军训自我鉴定200字
2014/02/13 职场文书
领导干部作风建设剖析材料
2014/10/11 职场文书
离职证明范本
2015/06/12 职场文书
学生病假条怎么写
2015/08/17 职场文书
导游词之嵊泗列岛
2019/10/30 职场文书
原生JS中应该禁止出现的写法
2021/05/05 Javascript
MySQL时间盲注的五种延时方法实现
2021/05/18 MySQL
《英雄联盟》2022日蚀、月蚀皮肤演示 黑潮亚索曝光
2022/04/13 其他游戏