Weibw's World Weibw's World
首页
  • HTML
  • Python

    • Python基础知识
    • Python CookBook第三版
    • Flask
  • MySQL

    • MySQL基础知识
    • MySQL调优
    • MySQL面试题
算法
  • FineReport
  • Kettle
  • Git
  • 微信公众号文章
  • 优秀博客文章
  • 其他
收藏夹
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Weibw

一个没有梦想的咸鱼
首页
  • HTML
  • Python

    • Python基础知识
    • Python CookBook第三版
    • Flask
  • MySQL

    • MySQL基础知识
    • MySQL调优
    • MySQL面试题
算法
  • FineReport
  • Kettle
  • Git
  • 微信公众号文章
  • 优秀博客文章
  • 其他
收藏夹
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 《Flask》

  • 《Python Cookbook》第三版

    • 第一章:数据结构与算法

    • 第二章:字符串和文本

      • 使用多个界定符分割字符串
      • 字符串开头或结尾匹配
      • 用 Shell 通配符匹配字符串
      • 字符串匹配和搜索
      • 字符串搜索和替换
      • 字符串忽略大小写的搜索替换
      • 最短匹配模式
      • 多行匹配模式
      • 将 Unicode 文本标准化
      • 在正则式中使用 Unicode
      • 删除字符串中不需要的字符
      • 审查清理文本字符串
      • 字符串对齐
      • 合并拼接字符串
      • 字符串中插入变量
      • 以指定列宽格式化字符串
      • 在字符串中处理html和xml
      • 字符串令牌解析
        • 实现一个简单的递归下降分析器
        • 字节字符串上的字符串操作
      • 第三章:数字日期和时间

      • 第四章:迭代器与生成器

      • 第五章:文件与IO

      • 第六章:数据编码和处理

      • 第七章:函数

      • 第八章:类与对象

      • 第九章:元编程

      • 第十章:模块与包

      • 第十一章:网络与Web编程

      • 第十二章:并发编程

      • 第十三章:脚本编程与系统管理

      • 第十四章:测试、调试和异常

      • 第十五章:C语言扩展

    • Python基础

    • Python
    • 《Python Cookbook》第三版
    • 第二章:字符串和文本
    weibw
    2022-01-05

    字符串令牌解析

    # 问题

    你有一个字符串,想从左至右将其解析为一个令牌流。

    # 解决方案

    假如你有下面这样一个文本字符串:

    text = 'foo = 23 + 42 * 10'
    
    1

    为了令牌化字符串,你不仅需要匹配模式,还得指定模式的类型。比如,你可能想将字符串像下面这样转换为序列对:

    tokens = [('NAME', 'foo'), ('EQ','='), ('NUM', '23'), ('PLUS','+'),
    		('NUM', '42'), ('TIMES', '*'), ('NUM', '10')]
    
    1
    2

    为了执行这样的切分,第一步就是像下面这样利用命名捕获组的正则表达式来定义所有可能的令牌,包括空格:

    import re
    NAME = r'(?P<NAME>[a-zA-Z_][a-zA-Z_0-9]*)'
    NUM = r'(?P<NUM>\d+)'
    PLUS = r'(?P<PLUS>\+)'
    TIMES = r'(?P<TIMES>\*)'
    EQ = r'(?P<EQ>=)'
    WS = r'(?P<WS>\s+)'
    master_pat = re.compile('|'.join([NAME, NUM, PLUS, TIMES, EQ, WS]))
    
    1
    2
    3
    4
    5
    6
    7
    8

    在上面的模式中,?P<TOKENNAME> 用于给一个模式命名,供后面使用。

    下一步,为了令牌化,使用模式对象很少被人知道的 scanner() 方法。这个方法会创建一个 scanner 对象,在这个对象上不断的调用 match() 方法会一步步的扫描目标文本,每步一个匹配。下面是演示一个 scanner 对象如何工作的交互式例子:

    >>> scanner = master_pat.scanner('foo = 42')
    >>> scanner.match()
    <_sre.SRE_Match object at 0x100677738>
    >>> _.lastgroup, _.group()
    ('NAME', 'foo')
    >>> scanner.match()
    <_sre.SRE_Match object at 0x100677738>
    >>> _.lastgroup, _.group()
    ('WS', ' ')
    >>> scanner.match()
    <_sre.SRE_Match object at 0x100677738>
    >>> _.lastgroup, _.group()
    ('EQ', '=')
    >>> scanner.match()
    <_sre.SRE_Match object at 0x100677738>
    >>> _.lastgroup, _.group()
    ('WS', ' ')
    >>> scanner.match()
    <_sre.SRE_Match object at 0x100677738>
    >>> _.lastgroup, _.group()
    ('NUM', '42')
    >>> scanner.match()
    >>>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23

    实际使用这种技术的时候,可以很容易的像下面这样将上述代码打包到一个生成器中:

    def generate_tokens(pat, text):
    Token = namedtuple('Token', ['type', 'value'])
    scanner = pat.scanner(text)
    for m in iter(scanner.match, None):
    yield Token(m.lastgroup, m.group())
    # Example use
    for tok in generate_tokens(master_pat, 'foo = 42'):
    print(tok)
    # Produces output
    # Token(type='NAME', value='foo')
    # Token(type='WS', value=' ')
    # Token(type='EQ', value='=')
    # Token(type='WS', value=' ')
    # Token(type='NUM', value='42')
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    如果你想过滤令牌流,你可以定义更多的生成器函数或者使用一个生成器表达式。比如,下面演示怎样过滤所有的空白令牌:

    tokens = (tok for tok in generate_tokens(master_pat, text) if tok.type != 'WS')
    for tok in tokens:
    	print(tok)
    
    1
    2
    3

    # 讨论

    通常来讲令牌化是很多高级文本解析与处理的第一步。为了使用上面的扫描方法,你需要记住这里一些重要的几点。第一点就是你必须确认你使用正则表达式指定了所有输入中可能出现的文本序列。如果有任何不可匹配的文本出现了,扫描就会直接停止。这也是为什么上面例子中必须指定空白字符令牌的原因。

    令牌的顺序也是有影响的。re 模块会按照指定好的顺序去做匹配。因此,如果一个模式恰好是另一个更长模式的子字符串,那么你需要确定长模式写在前面。比如:

    LT = r'(?P<LT><)'
    LE = r'(?P<LE><=)'
    EQ = r'(?P<EQ>=)'
    master_pat = re.compile('|'.join([LE, LT, EQ])) # Correct
    # master_pat = re.compile('|'.join([LT, LE, EQ])) # Incorrect
    
    1
    2
    3
    4
    5

    第二个模式是错的,因为它会将文本 <= 匹配为令牌 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)
    # Outputs :
    # Token(type='PRINT', value='print')
    # Token(type='NAME', value='er')
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    关于更高阶的令牌化技术,你可能需要查看 PyParsing (opens new window) 或者 PLY (opens new window) 包。一个调用

    PLY 的例子在下一节会有演示。

    编辑 (opens new window)
    上次更新: 2023/10/13, 17:39:25
    在字符串中处理html和xml
    实现一个简单的递归下降分析器

    ← 在字符串中处理html和xml 实现一个简单的递归下降分析器→

    最近更新
    01
    牛客网非技术快速入门SQL练习题
    03-08
    02
    其他日常SQL题
    03-07
    03
    用户与权限管理
    03-05
    更多文章>
    Theme by Vdoing | Copyright © 2021-2023 | Weibw | 辽ICP备18015889号
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式