Python技法之如何用re模块实现简易tokenizer
作者:orion-orion 发布时间:2021-08-04 17:03:52
一个简单的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.
总结
来源:https://www.cnblogs.com/orion-orion/p/16206925.html
猜你喜欢
- 本文实例为大家分享了js浏览器倒计时跳转页面效果,供大家参考,具体内容如下效果图:<!DOCTYPE html><html
- k-means算法思想较简单,说的通俗易懂点就是物以类聚,花了一点时间在python中实现k-means算法,k-means算法有本身的缺点
- 在使用数据库的时候,难免要在使用过程中进行删除的操作,如果是使用int类型的字段,令其自增长,这是个最简单的办法,但是后果会有些不是你想要的
- 在SQL Server 2005中,它的另外一个强大的新特点是数据库快照。数据库快照是一个数据库的只读副本,它是数据库所有数据的映射,由快照
- 1. 换源,sohu的相当好用。 1.1备份CentOS-Base.repo cd /etc/yum.repos.d/ cp CentOS-
- 读《论语》,子张十九,子夏曰:博学而笃志,切问而近思,仁在其中矣。 博学:架构需要广度,要尽量多学习各方面的知识。笃志:除了广度,架构师还需
- <% '#######以下是一个类文件,下面的注解是调用类的方法####################
- 二进制转字符串入口参数:字节流函数返回:字符串Code By:Madpolice利用 ADODB.Stream 对象,速度比原来
- 本文实例分析了php5.4传引用时报错问题。分享给大家供大家参考,具体如下:php5.3系列版本以及以前版本,传引用没有什么问题,升级到ph
- Opera所属:挪威Opera Software ASA公司渲染引擎:自家的PrestoOpera起初是一款挪威Oper
- XML有很多相关的技术,将这些技术结合起来,才能充分发挥XML的强大功能。这些技术包括:Xlink与Xpointer(设置XML的超链接)、
- 最近帮伙计做了一个从网页抓取股票信息并把相应信息存入MySQL中的程序。使用环境:Python 2.5 for WindowsMySQLdb
- 阅读上一篇:javascript面向对象编程(二) [Interface,Class.implement 接口及实现]接口规定了一些方法,如
- 比较简单的一个页面,类似csdn论坛在ns下的左边列表 描述: 1. 数据名:tree.mdb 表名:tree 表结构:id(自编)&nbs
- $array=explode(separator,$string); $string=implode(glue,$array);使用和理解这
- 本文介绍了一些JavaScript常用到得表单验证函数,方便大家使用。 判断是否为整数,是则返回true,否则返回falsefun
- 前言今天我们一起来聊聊DataFrame中的索引。上一篇文章当中我们介绍了DataFrame数据结构当中一些常用的索引的使用方法,比如ilo
- 1. 吊顶下拉菜单的键盘可用性改进无障碍访问貌似最近比较火,大家都在聊,其中一块就是键盘的可访问性。我们在首页上作了些调整,让用户可以通过键
- 复制代码CREATE FUNCTION fGetStrBySplit ( @Source VARCHAR(max), @Index INT,
- 前段时日微软(Microsoft)正式发布了.NET Core 2.0,在很多开发社区中反响不错。但还是有一些开发者发出了疑问,.NET C