如何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遍历目录的4种方法实例介绍
Apr 13 Python
改进Django中的表单的简单方法
Jul 17 Python
实例Python处理XML文件的方法
Aug 31 Python
python3.5 + PyQt5 +Eric6 实现的一个计算器代码
Mar 11 Python
Python开发的实用计算器完整实例
May 10 Python
Python实现的单向循环链表功能示例
Nov 10 Python
python去掉空白行的多种实现代码
Mar 19 Python
基于DATAFRAME中元素的读取与修改方法
Jun 08 Python
python实现登录密码重置简易操作代码
Aug 14 Python
pandas将多个dataframe以多个sheet的形式保存到一个excel文件中
Oct 10 Python
Python安装并操作redis实现流程详解
Oct 13 Python
关于python中readlines函数的参数hint的相关知识总结
Jun 24 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执行速度全攻略
2006/10/09 PHP
php 将bmp图片转为jpg等其他任意格式的图片
2009/06/29 PHP
XML的代替者----JSON
2007/07/21 Javascript
QUnit jQuery的TDD框架
2010/11/04 Javascript
纯JS实现根据CSS的class选择DOM
2014/03/22 Javascript
javascript实现按回车键切换焦点
2015/02/09 Javascript
运行Node.js的IIS扩展iisnode安装配置笔记
2015/03/02 Javascript
浅析JS操作DOM的一些常用方法
2016/05/13 Javascript
JS取数字小数点后两位或n位的简单方法
2016/10/24 Javascript
jquery.validate[.unobtrusive]和Bootstrap实现tooltip错误提示问题分析
2016/10/30 Javascript
JavaScript 保护变量不被随意修改的实现代码
2017/09/27 Javascript
jQuery使用动画队列自定义动画操作示例
2018/06/16 jQuery
echarts整合多个类似option的方法实例
2018/07/10 Javascript
前端axios下载excel文件(二进制)的处理方法
2018/07/31 Javascript
创建echart多个联动的示例代码
2018/11/23 Javascript
vue+springboot图片上传和显示的示例代码
2020/02/14 Javascript
JavaScript, select标签元素左右移动功能实现
2020/05/14 Javascript
python判断端口是否打开的实现代码
2013/02/10 Python
Python Tkinter简单布局实例教程
2014/09/03 Python
简单说明Python中的装饰器的用法
2015/04/24 Python
python安装教程 Pycharm安装详细教程
2017/05/02 Python
Django中url的反向查询的方法
2018/03/14 Python
基于DataFrame筛选数据与loc的用法详解
2018/05/18 Python
python打造爬虫代理池过程解析
2019/08/15 Python
Python 网络编程之UDP发送接收数据功能示例【基于socket套接字】
2019/10/11 Python
python中Ansible模块的Playbook的具体使用
2020/05/28 Python
个人作风剖析材料
2014/02/02 职场文书
2014年大学生四年规划书范文
2014/04/03 职场文书
老公保证书范文
2014/04/29 职场文书
产品推广策划方案
2014/05/10 职场文书
中学生运动会新闻稿
2014/09/24 职场文书
解除施工合同协议书
2014/10/17 职场文书
银行服务理念口号
2015/12/25 职场文书
创业计划书之蛋糕店
2019/08/29 职场文书
PyTorch梯度裁剪避免训练loss nan的操作
2021/05/24 Python
Python初学者必备的文件读写指南
2021/06/23 Python