如何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-ALLAUTH社交用户系统的安装配置
Nov 18 Python
Python外星人入侵游戏编程完整版
Mar 30 Python
python导出hive数据表的schema实例代码
Jan 22 Python
python实现画圆功能
Jan 25 Python
为什么str(float)在Python 3中比Python 2返回更多的数字
Oct 16 Python
10 分钟快速入门 Python3的教程
Jan 29 Python
pyinstaller打包单个exe后无法执行错误的解决方法
Jun 21 Python
Python数据类型之列表和元组的方法实例详解
Jul 08 Python
python实现图片压缩代码实例
Aug 12 Python
python实现七段数码管和倒计时效果
Nov 23 Python
tensorboard实现同时显示训练曲线和测试曲线
Jan 21 Python
2021年值得向Python开发者推荐的VS Code扩展插件
Jan 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中的内存管理问题
2011/08/31 PHP
PHP5.4中json_encode中文转码的变化小结
2013/01/30 PHP
php中删除字符串中最先出现某个字符的实现代码
2013/02/03 PHP
浅析PHP递归函数返回值使用方法
2013/02/18 PHP
基于PHP开发中的安全防范知识详解
2013/06/06 PHP
解析PHP实现下载文件的两种方法
2013/07/05 PHP
phpstrom使用xdebug配置方法
2013/12/17 PHP
php 计算两个时间相差的天数、小时数、分钟数、秒数详解及实例代码
2016/11/09 PHP
javascript 读取图片文件的大小
2009/06/25 Javascript
innerText和textContent对比及使用介绍
2013/02/27 Javascript
js文本框输入点回车触发确定兼容IE、FF等
2013/11/19 Javascript
Bootstrap菜单按钮及导航实例解析
2016/09/09 Javascript
gulp安装以及打包合并的方法教程
2017/11/19 Javascript
Element Tooltip 文字提示的使用示例
2020/07/26 Javascript
Python实用日期时间处理方法汇总
2015/05/09 Python
Windows下anaconda安装第三方包的方法小结(tensorflow、gensim为例)
2018/04/05 Python
对python中的for循环和range内置函数详解
2018/04/17 Python
Python实现的redis分布式锁功能示例
2018/05/29 Python
python将视频转换为全字符视频
2019/04/26 Python
用python给自己做一款小说阅读器过程详解
2019/07/11 Python
Python tornado上传文件的功能
2020/03/26 Python
keras CNN卷积核可视化,热度图教程
2020/06/22 Python
css3中的calc函数浅析
2018/07/10 HTML / CSS
一款超酷的js+css3实现的3D标签云特效兼容ie7/8/9
2013/11/18 HTML / CSS
浅析rem和em和px vh vw和% 移动端长度单位
2016/04/28 HTML / CSS
实例讲解使用SVG制作loading加载动画的方法
2016/04/05 HTML / CSS
匡威比利时官网:Converse Belgium
2017/04/13 全球购物
选购世界上最好的美妆品:Cult Beauty
2017/11/03 全球购物
Nike澳大利亚官网:Nike.com (AU)
2019/06/03 全球购物
俄罗斯奢侈品牌衣服、鞋子和配饰的在线商店:INTERMODA
2020/07/17 全球购物
儿科主治医生个人求职信
2013/09/23 职场文书
思想品德自我鉴定
2013/10/12 职场文书
法定代表人授权委托书格式
2014/10/14 职场文书
护林员个人总结
2015/03/04 职场文书
小学教代会开幕词
2016/03/04 职场文书
Python+Matplotlib图像上指定坐标的位置添加文本标签与注释
2022/04/11 Python