如何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 相关文章推荐
ptyhon实现sitemap生成示例
Mar 30 Python
Python迭代用法实例教程
Sep 08 Python
Python运用于数据分析的简单教程
Mar 27 Python
实例讲解Python的函数闭包使用中应注意的问题
Jun 20 Python
python文件名和文件路径操作实例
Sep 29 Python
Windows 8.1 64bit下搭建 Scrapy 0.22 环境
Nov 18 Python
Python当中的array数组对象实例详解
Jun 12 Python
python中while和for的区别总结
Jun 28 Python
Django-rest-framework中过滤器的定制实例
Apr 01 Python
python主要用于哪些方向
Jul 05 Python
Ubuntu20.04环境安装tensorflow2的方法步骤
Jan 29 Python
手把手教你使用TensorFlow2实现RNN
Jul 15 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/09/22 PHP
表单提交错误后返回内容消失问题的解决方法(PHP网站)
2015/10/20 PHP
PHP多线程模拟实现秒杀抢单
2018/02/07 PHP
php设计模式之观察者模式实例详解【星际争霸游戏案例】
2020/03/30 PHP
JavaScript 格式字符串的应用
2010/03/29 Javascript
Fastest way to build an HTML string(拼装html字符串的最快方法)
2011/08/20 Javascript
js原型继承的两种方法对比介绍
2014/03/30 Javascript
什么是 AngularJS?AngularJS简介
2014/12/06 Javascript
JavaScript中document对象使用详解
2015/01/06 Javascript
JS仿淘宝实现的简单滑动门效果代码
2015/10/14 Javascript
学习使用AngularJS文件上传控件
2016/02/16 Javascript
JS检测移动端横竖屏的代码
2016/05/30 Javascript
Node.js检测端口(port)是否被占用的简单示例
2016/09/29 Javascript
JS基于面向对象实现的选项卡效果示例
2016/12/20 Javascript
Vue数据驱动模拟实现3
2017/01/11 Javascript
vue实现手机号码抽奖上下滚动动画示例
2017/10/18 Javascript
JS实现为动态创建的元素添加事件操作示例
2018/03/17 Javascript
记一次webapck4 配置文件无效的解决历程
2018/09/19 Javascript
浅析微信小程序modal弹窗关闭默认会执行cancel问题
2019/10/14 Javascript
基于Vue CSR的微前端实现方案实践
2020/05/27 Javascript
详解python 字符串和日期之间转换 StringAndDate
2017/05/04 Python
Python批量更改文件名的实现方法
2017/10/29 Python
python交互式图形编程实例(一)
2017/11/17 Python
tensorflow识别自己手写数字
2018/03/14 Python
python 读文件,然后转化为矩阵的实例
2018/04/23 Python
numpy库与pandas库axis=0,axis= 1轴的用法详解
2019/05/27 Python
手把手教你安装Windows版本的Tensorflow
2020/03/26 Python
python保留格式汇总各部门excel内容的实现思路
2020/06/01 Python
大女孩胸罩:Big Girls Bras
2016/12/15 全球购物
西安启天科技有限公司网络工程师面试题笔试题
2016/06/12 面试题
创业计划书的内容步骤和要领
2014/01/04 职场文书
幼儿园中班下学期评语
2014/04/18 职场文书
材料专业大学毕业生自荐书
2014/07/02 职场文书
运动会搞笑广播稿
2014/10/14 职场文书
禁毒主题班会教案
2015/08/14 职场文书
MySQL8.0.18配置多主一从
2021/06/21 MySQL