如何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 相关文章推荐
教你如何在Django 1.6中正确使用 Signal
Jun 22 Python
python实现的重启关机程序实例
Aug 21 Python
python中私有函数调用方法解密
Apr 29 Python
python实现一个简单的并查集的示例代码
Mar 19 Python
python3+PyQt5重新实现自定义数据拖放处理
Apr 19 Python
详解Django中间件执行顺序
Jul 16 Python
理想高通滤波实现Python opencv示例
Jan 30 Python
深入了解Python iter() 方法的用法
Jul 11 Python
Django模板Templates使用方法详解
Jul 19 Python
python 浅谈serial与stm32通信的编码问题
Dec 18 Python
python为Django项目上的每个应用程序创建不同的自定义404页面(最佳答案)
Mar 09 Python
Python 数据可视化神器Pyecharts绘制图像练习
Feb 28 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载入页面时编码的方法
2014/07/29 PHP
php实现的单一入口应用程序实例分析
2015/09/23 PHP
如何在旧的PHP系统中使用PHP 5.3之后的库
2015/12/02 PHP
PHP中substr函数字符串截取用法分析
2016/01/07 PHP
PHP性能分析工具xhprof的安装使用与注意事项
2017/12/19 PHP
PHP依赖注入原理与用法分析
2018/08/21 PHP
PHP PDOStatement::rowCount讲解
2019/02/01 PHP
在laravel中使用with实现动态添加where条件
2019/10/10 PHP
使用户点击后退按钮使效三行代码
2007/07/07 Javascript
DOM下的节点属性和操作小结
2009/05/14 Javascript
js修改input的type属性及浏览器兼容问题探讨与解决
2013/01/23 Javascript
引用 js在IE与FF之间的区别详细解析
2013/11/20 Javascript
jQuery响应鼠标事件并隐藏与显示input默认值
2014/08/24 Javascript
解决jquery版本冲突的有效方法
2014/09/02 Javascript
Javascript基础教程之数据类型 (数值 Number)
2015/01/18 Javascript
IE下支持文本框和密码框placeholder效果的JQuery插件分享
2015/01/31 Javascript
jquery append 动态添加的元素事件on 不起作用的解决方案
2015/07/30 Javascript
深入解析Backbone.js框架的依赖库Underscore.js的作用
2016/05/07 Javascript
jQGrid Table操作列中点击【操作】按钮弹出按钮层的实现代码
2016/12/05 Javascript
javascript实现秒表计时器的制作方法
2017/02/16 Javascript
Vue常用指令详解分析
2018/08/19 Javascript
vue中的router-view组件的使用教程
2018/10/23 Javascript
vue-cli 目录结构详细讲解总结
2019/01/15 Javascript
云服务器部署Node.js项目的方法步骤(小白系列)
2020/03/23 Javascript
jQuery实现动态向上滚动
2020/12/21 jQuery
[02:54]DOTA2英雄基础教程 撼地者
2014/01/14 DOTA
Python中的exec、eval使用实例
2014/09/23 Python
学习python可以干什么
2019/02/26 Python
Python后台开发Django的教程详解(启动)
2019/04/08 Python
Python配置pip国内镜像源的实现
2020/08/20 Python
中西医专业毕业生职业规划书
2014/02/24 职场文书
竞选班长的演讲稿
2014/04/24 职场文书
市政工程技术专业自荐书
2014/07/06 职场文书
中班上学期个人总结
2015/02/12 职场文书
团队拓展训练感想
2015/08/07 职场文书
thinkphp 获取控制器及控制器方法
2021/04/16 PHP