笨办法学 Python · 续 练习 32:扫描器
练习 32:扫描器
原文:Exercise 32: Scanners
译者:飞龙
协议:CC BY-NC-SA 4.0
自豪地采用谷歌翻译
我的第一本书在练习 48 中非常偶然涉及到了扫描器,但现在我们将会更加正式。我将解释扫描文本背后的概念,它与正则表达式有关,以及如何为一小段 Python 代码创建一个小型扫描器。
我们以下面的 Python 代码为例来开始讨论:
def hello(x, y):print(x + y)hello(10, 20)
你已经在 Python 上练习了一段时间了,所以你的大脑最有可能很快阅读这个代码,但是你真的明白了吗?当我(或别人)教你 Python 时,我让你记得所有的“符号”。def
和()
字符是每一个符号,但是 Python 需要一种可靠的、一致的方法来处理它们。Python 还需要能够读取hello
,理解它是一个什么东西的“名称”,然后知道def hello(x, y)
和hello(10, 20)
之间的区别。怎么实现它呢?
执行此操作的第一步是,扫描文本并查找“记号”(Token)。在扫描阶段,像 Python 这样的语言不会首先关心什么是符号(def
),什么是名称(hello
)。它将简单地,尝试将输入语言转换为的文本模式串,成为“记号”。它通过应用一系列正则表达式来做到这一点,这些正则表达式“匹配” Python 理解的每个可能的输入。练习 31 中,你会记得一个正则表达式是一种方式,告诉 Python 要匹配或接受什么字符序列。所有 Python 解释器都使用许多正则表达式,来匹配它理解的每个记号。
如果你看看上面的代码,你可以编写一组正则表达式来处理它。def
需要一个简单的正则表达式,只是“def”。对于()+:,
字符你需要更多的正则表达式。然后,你还剩下如何处理print
,hello
,10
和20
。
一旦你确定了上述代码示例中的所有符号,你需要命名它们。你不能仅仅通过它们的正则表达式来引用它们,因为查找效率低下,也令人困惑。稍后你会发现,为每个符号提供自己的名字(或数字)可以简化解析,但现在让我们为这些正则表达式设计一些名称。我可以说def
只是DEF
,那么()+:,
可以是LPAREN RPAREN PLUS COLON COMMA
。之后,我可以将用于hello
和print
之类的单词正则表达式称为NAME
。通过这样做,我想出了一种方法,将原始文本流转换成一个单个数字(或名称)记号的流,来在后期使用。
Python 也很棘手,因为它需要一个前导空白的正则表达式,来处理代码块的缩进和压缩。现在,让我们使用一个相当笨的^\s+
,然后假装它也捕捉到行的开头使用了多少个空白。
最终你会拥有一组正则表达式,可以处理上面的代码,它可能看起来像这样:
正则表达式 | 记号 |
---|---|
def
|
DEF
|
[a-zA-Z_][a-zA-Z0-9_]*
|
NAME
|
[0-9]+
|
INTEGER
|
\(
|
LPAREN
|
\)
|
RPAREN
|
\+
|
PLUS
|
:
|
COLON
|
,
|
COMMA
|
^\s+
|
INDENT
|
扫描器的任务是使用这些正则表达式,并将输入文本分解成识别符号的流。如果我这样对示例代码这么做,我可以产生:
DEF NAME(hello) LPAREN NAME(x) COMMA NAME(y) RPAREN COLON
INDENT(4) NAME(print) LPAREN NAME(x) PLUS NAME(y) RPAREN
NAME(hello) RPAREN INTEGER(10) COMMA INTEGER(20) RPAREN
研究此转换,匹配扫描器输出的每一行,并使用表中的正则表达式将其与上述 Python 代码进行比较。你会看到这只是选取输入文本,将每个正则表达式匹配到记录名称,然后保存所需的任何信息,如hello
或数字10
。
微小的 Python 扫描器
我编写了一个非常小的 Python 扫描器,演示了这个非常小的 Python 语言:
import recode = [
"def hello(x, y):",
" print(x + y)",
"hello(10, 20)",
]TOKENS = [
(re.compile(r"^def"), "DEF"),
(re.compile(r"^[a-zA-Z_][a-zA-Z0-9_]*"), "NAME"),
(re.compile(r"^[0-9]+"), "INTEGER"),
(re.compile(r"^\("), "LPAREN"),
(re.compile(r"^\)"), "RPAREN"),
(re.compile(r"^\+"), "PLUS"),
(re.compile(r"^:"), "COLON"),
(re.compile(r"^,"), "COMMA"),
(re.compile(r"^\s+"), "INDENT"),
]def match(i, line):start = line[i:]for regex, token in TOKENS:match = regex.match(start)if match:begin, end = match.span()return token, start[:end], endreturn None, start, Nonescript = []for line in code:i = 0while i < len(line):token, string, end = match(i, line)assert token, "Failed to match line %s" % stringif token:i += endscript.append((token, string, i, end))print(script)
当你运行这个脚本时,你会得到一个tuples
的list
,它是TOKEN
、匹配到的字符串、开头和末尾,像这样:
[('DEF', 'def', 3, 3), ('INDENT', ' ', 4, 1), ('NAME', 'hello', 9, 5),
('LPAREN', '(', 10, 1), ('NAME', 'x', 11, 1), ('COMMA', ',', 12, 1),
('INDENT', ' ', 13, 1), ('NAME', 'y', 14, 1), ('RPAREN', ')', 15, 1),
('COLON', ':', 16, 1), ('INDENT', ' ', 4, 4), ('NAME', 'print', 9, 5),
('LPAREN', '(', 10, 1), ('NAME', 'x', 11, 1), ('INDENT', ' ', 12, 1),
('PLUS', '+', 13, 1), ('INDENT', ' ', 14, 1), ('NAME', 'y', 15, 1),
('RPAREN', ')', 16, 1), ('NAME', 'hello', 5, 5), ('LPAREN', '(', 6, 1),
('INTEGER', '10', 8, 2), ('COMMA', ',', 9, 1), ('INDENT', ' ', 10, 1),
('INTEGER', '20', 12, 2), ('RPAREN', ')', 13, 1)]
这个代码绝对不是你可以创建的最快或最准确的扫描器。这是一个简单的脚本,用于演示扫描器的工作原理。对于进行真正的扫描工作,你将使用一种工具来生成更高效的扫描器。我在深入学习部分介绍。
挑战练习
你的工作是研究这个扫描器示例代码,并将其转换成通用的Scanner
类以便稍后使用。这个Scanner
类的目标是接受一个输入文件,将其扫描为记号的列表,然后允许你按顺序取出记号。API 应具有以下功能:
__init__
使用类似的元组列表(没有
re.compile
)来配置扫描器。
scan
接受一个字符串并执行扫描,创建一个记录列表以便以后使用。你应该保留这个字符串,让人们以后访问。
match
提供可能的记号列表,返回列表中的第一个记号,并将其移除。
peek
提供可能的记号列表,返回列表中的第一个记号,但不将其移除。
push
将记号放回记号流中,以便后续的
peek
或者match
返回它。
你也应该创建通用的Token
类来代替我使用的tuple
。它应该能够跟踪发现的记号,匹配的字符串、原始字符串中匹配位置的开头和末尾。
研究性学习
- 安装
pytest-cov
库,并使用它来测量自动化测试的覆盖率。 - 使用
pytest-cov
的结果来改进自动化测试。
深入学习
创建扫描器的更好方法是,利用以下关于正则表达式的三个事实:
- 正则表达式是有限状态机。
- 你可以将小型有限状态机精确地组合成更大更复杂的有限状态机。
- 匹配许多小型正则表达式的有限状态机组合,操作方式每个正则表达式一样,并且效率更高。
有许多工具使用这个事实来接受扫描器定义,将每个小的正则表达式转换为 FSM,然后将它们组合来产生大段代码,可以可靠地匹配所有记号。这样做的优点是,你可以以滚动方式为这些生成的扫描器提供独立的字符,并使其快速识别记号。它比我这里的方式要好,其中我拼凑字符串,并尝试一系列正则表达式,直到找到一个正则表达式。
研究扫描器的发生器如何工作,并将其与你编写的代码进行比较。
笨办法学 Python · 续 练习 32:扫描器相关推荐
- 笨办法学 Python · 续 中文版
笨办法学 Python · 续 中文版 原书:Learn More Python 3 The Hard Way 译者:飞龙 自豪地采用谷歌翻译 在线阅读 PDF格式 EPUB格式 MOBI格式 代码仓 ...
- 笨办法学 Python · 续 练习 33:解析器
练习 33:解析器 原文:Exercise 33: Parsers 译者:飞龙 协议:CC BY-NC-SA 4.0 自豪地采用谷歌翻译 想象一下,你将获得一个巨大的数字列表,你必须将其输入到电子表格 ...
- 笨办法学 Python · 续 练习 0:起步
练习 0:起步 原文:Exercise 0: The Setup 译者:飞龙 协议:CC BY-NC-SA 4.0 自豪地采用谷歌翻译 你需要设置和配置一些工具来学习此书.有可能你已经有了很多这些东西 ...
- 笨办法学 Python · 续 第二部分:简单的黑魔法
第二部分:简单的黑魔法 原文:Part II: Quick Hacks 译者:飞龙 协议:CC BY-NC-SA 4.0 自豪地采用谷歌翻译 你有最好的想法,你会打动世界!你会成为一个亿万富豪!你的大 ...
- 笨办法学 Python · 续 练习 52:`moreweb`
练习 52:moreweb 原文:Exercise 52: moreweb 译者:飞龙 协议:CC BY-NC-SA 4.0 自豪地采用谷歌翻译 现在,你已经使用 Python http.server ...
- 笨办法学 Python · 续 练习 24:URL 快速路由
练习 24:URL 快速路由 原文:Exercise 24: Fast URL Search 译者:飞龙 协议:CC BY-NC-SA 4.0 自豪地采用谷歌翻译 我们将结束数据结构和算法的部分,并将 ...
- 笨办法学 Python · 续 练习 36:简单的计算器
练习 36:简单的计算器 原文:Exercise 36: Simple Calculator 译者:飞龙 协议:CC BY-NC-SA 4.0 自豪地采用谷歌翻译 这个挑战是创建一个简单的代数计算器, ...
- python描述器 有限状态机_笨办法学 Python · 续 练习 30:有限状态机
练习 30:有限状态机 每当你阅读一本关于解析的书,都有一个可怕的章节,关于有限状态机(FSM).他们对"边"和"节点"进行了详细的分析,每个可能的" ...
- 笨办法学 Python · 续 第三部分:数据结构
第三部分:数据结构 原文:Part III: Data Structures 译者:飞龙 协议:CC BY-NC-SA 4.0 自豪地采用谷歌翻译 你正在以你的方式构建个人流程,它让你以有限的阻碍快速 ...
最新文章
- java weakhashmap用法_Java WeakHashMap指南
- 分页浏览的导航栏Bootstrap和js两种方法
- java io 读取多个对象_Java IO系列(五):读写对象ObjectOutputStream和ObjectInputStream详解...
- mysql allowed packet_修改mysql的max_allowed_packet值
- python利器-[python] bluepy 一款python封装的BLE利器
- Commons IO -- IOUtils
- PMBOK第六版学习笔记
- 简单的朴素贝叶斯算法实现英文文本分类(Python实现)
- SOSO发布国内首家高清街景地图 引领地图换代
- 历史经验之解决vMix22闪退的办法(亲测管用)
- vue使用组件化思想实现一个简单的购物车页面
- html制作不均匀表格,HTML自定义JUnit报告不均匀表格对齐
- ItextAsian中文字体
- python 选手比赛模拟
- 学习记录560@DES密码体系
- 每日新闻丨英伟达发布全球最小边缘AI超级计算机;IBM开发出全球首个金融服务就绪公有云...
- PVE迁移到VMware
- 美国人这样教育小学生(转贴)
- 网络编程01-TCP协议(详)
- ubuntu20.4服务器搭建ftp并连接(阿里云服务器)
热门文章
- (75)FPGA随机函数($random)
- 2025.wireshark工具使用
- gcc生成的汇编和keil生成的汇编_Linux编译工具:gcc入门
- php跳过代码,PHP利用continue实现跳过本次循环中剩余代码的注意点
- 线程池参数如何设置?
- mysql二级缓存redis_redis实现二级缓存
- C51单片机————串行接口
- 【重难点】【Java集合 01】HashMap 和 ConcurrentHashMap
- 使用Freemarker实现网页静态化
- Flask 学习 (二) blueprint 示例