Python利用re模块实现简易分词(tokenization)
作者:orion-orion 发布时间:2021-06-12 20:07:59
一个简单的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
分词工具包)。
来源:https://www.cnblogs.com/orion-orion/p/16206925.html


猜你喜欢
- 目录1.简介2.如何解决3.虚线框类代码4.测试UI界面如下图所示5.拖动时的效果图如下所示1.简介看到很多才学QT的人都会问为啥无边框拖动
- 如何在一个广告旗帜里轮番显示时间长度不一的不同广告?好了,下面就是Ad Rotator组件完整的应用例子:adrot.asp<html
- 模块导入的规范模块是类或函数的集合,用于实现某个功能。模块的导入和Java 中包的导入的概念很相似都使用import语句。在Python中,
- 前言:数据库是大多数 Web 应用的基础设施,只要想把数据存储下来,就离不开数据库,下面将一起学习一下如何给 Flask 应用添加数据库支持
- 1. 标签{% 标签 %}1.1 for循环标签<ul><!-- 可迭代对象都可以用循环 --><!-- 循环
- 天下武功,唯快不破。编程也不例外,你的代码跑的快,你能快速找出代码慢的原因,你的码功就高。安装pip install pyinstrumen
- wxPython是Python语言的一套优秀的GUI图形库。允许Python程序员很方便的创建完整的、功能键全的GUI用户界面。 wxPyt
- 正则表达式的使用想要学习 Python 爬虫 , 首先需要了解一下正则表达式的使用,下面我们就来看看如何使用。. 的使用这个时候的点就相当于
- 在JavaScript中,数组本质上是一种特殊的对象,它的类型值会返回 object。如果我们需要比较两个数组是否相等,不能像比较基本类型(
- SELECT TABLE_SCHEMA,TABLE_NAMEFROM information_schema.`COLUMNS`WHERE C
- 最近开始研究Python的并行开发技术,包括多线程,多进程,协程等。逐步整理了网上的一些资料,今天整理了一下greenlet相关的资料。并发
- Go 单元测试工具测试分为4个层次单元测试:对代码进行测试集成测试:对一个服务的接口测试端到端测试(链路测试):从一个链路的入口输入测试用例
- 最简单的CGO程序//cgo.gopackage mainimport "C"func main(){ &nb
- 一个很不错的效果,用键盘的箭头键控制一个漂亮的箭头!也许能给你启发,做出一款有趣的小游戏呢!运行代码框<html xmlns:v=&q
- 正常使用了go 1.8一段时间没有发现异常,为了发布便捷,以及后期引入plug-in,开始将大项目分解。涉及到通过vendor引入私有库保存
- SQL Server 2008已经发布,我们可以看到它在各方面都有了显著的进步,这些让人侧目之处和失望之处都有可能极大地影响大型企业的采购意
- A.截取从字符串左边开始N个字符 Declare @S1 varchar(100) Select @S1='http://www.x
- 如果想设置相同的初值和想要的长度>>> a=[None]*4>>> print(a)[None, Non
- PDO::getAttributePDO::getAttribute — 取回一个数据库连接的属性(PHP 5 >= 5.1.0, P
- 为什么是Python先来聊聊为什么做数据分析一定要用Python或R语言。编程语言这么多种,Java, PHP都很成熟,但是为什么在最近热火