笨辦法學(xué) Python · 續(xù) 練習(xí) 32:掃描器

練習(xí) 32:掃描器

原文:Exercise 32: Scanners

譯者:飛龍

協(xié)議:CC BY-NC-SA 4.0

自豪地采用谷歌翻譯

我的第一本書在練習(xí) 48 中非常偶然涉及到了掃描器斋否,但現(xiàn)在我們將會(huì)更加正式杯缺。我將解釋掃描文本背后的概念,它與正則表達(dá)式有關(guān)舔庶,以及如何為一小段 Python 代碼創(chuàng)建一個(gè)小型掃描器躺屁。

我們以下面的 Python 代碼為例來開始討論:

def hello(x, y):
    print(x + y)

hello(10, 20)

你已經(jīng)在 Python 上練習(xí)了一段時(shí)間了沸毁,所以你的大腦最有可能很快閱讀這個(gè)代碼殿漠,但是你真的明白了嗎?當(dāng)我(或別人)教你 Python 時(shí)卤妒,我讓你記得所有的“符號”甥绿。def()字符是每一個(gè)符號叠必,但是 Python 需要一種可靠的、一致的方法來處理它們妹窖。Python 還需要能夠讀取hello纬朝,理解它是一個(gè)什么東西的“名稱”,然后知道def hello(x, y)hello(10, 20)之間的區(qū)別骄呼。怎么實(shí)現(xiàn)它呢共苛?

執(zhí)行此操作的第一步是,掃描文本并查找“記號”(Token)蜓萄。在掃描階段隅茎,像 Python 這樣的語言不會(huì)首先關(guān)心什么是符號(def),什么是名稱(hello)嫉沽。它將簡單地辟犀,嘗試將輸入語言轉(zhuǎn)換為的文本模式串,成為“記號”绸硕。它通過應(yīng)用一系列正則表達(dá)式來做到這一點(diǎn)堂竟,這些正則表達(dá)式“匹配” Python 理解的每個(gè)可能的輸入。練習(xí) 31 中玻佩,你會(huì)記得一個(gè)正則表達(dá)式是一種方式出嘹,告訴 Python 要匹配或接受什么字符序列。所有 Python 解釋器都使用許多正則表達(dá)式咬崔,來匹配它理解的每個(gè)記號税稼。

如果你看看上面的代碼,你可以編寫一組正則表達(dá)式來處理它垮斯。def需要一個(gè)簡單的正則表達(dá)式郎仆,只是“def”。對于()+:,字符你需要更多的正則表達(dá)式兜蠕。然后扰肌,你還剩下如何處理printhello牺氨,1020狡耻。

一旦你確定了上述代碼示例中的所有符號墩剖,你需要命名它們猴凹。你不能僅僅通過它們的正則表達(dá)式來引用它們,因?yàn)椴檎倚实拖铝朐恚擦钊死Щ蠼荐I院竽銜?huì)發(fā)現(xiàn),為每個(gè)符號提供自己的名字(或數(shù)字)可以簡化解析爷绘,但現(xiàn)在讓我們?yōu)檫@些正則表達(dá)式設(shè)計(jì)一些名稱书劝。我可以說def只是DEF进倍,那么()+:,可以是LPAREN RPAREN PLUS COLON COMMA。之后购对,我可以將用于helloprint之類的單詞正則表達(dá)式稱為NAME猾昆。通過這樣做,我想出了一種方法骡苞,將原始文本流轉(zhuǎn)換成一個(gè)單個(gè)數(shù)字(或名稱)記號的流垂蜗,來在后期使用。

Python 也很棘手解幽,因?yàn)樗枰粋€(gè)前導(dǎo)空白的正則表達(dá)式贴见,來處理代碼塊的縮進(jìn)和壓縮。現(xiàn)在躲株,讓我們使用一個(gè)相當(dāng)笨的^\s+片部,然后假裝它也捕捉到行的開頭使用了多少個(gè)空白。

最終你會(huì)擁有一組正則表達(dá)式霜定,可以處理上面的代碼档悠,它可能看起來像這樣:

正則表達(dá)式 記號
def DEF
[a-zA-Z_][a-zA-Z0-9_]* NAME
[0-9]+ INTEGER
\( LPAREN
\) RPAREN
\+ PLUS
: COLON
, COMMA
^\s+ INDENT

掃描器的任務(wù)是使用這些正則表達(dá)式,并將輸入文本分解成識別符號的流望浩。如果我這樣對示例代碼這么做站粟,我可以產(chǎn)生:

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

研究此轉(zhuǎn)換,匹配掃描器輸出的每一行曾雕,并使用表中的正則表達(dá)式將其與上述 Python 代碼進(jìn)行比較奴烙。你會(huì)看到這只是選取輸入文本,將每個(gè)正則表達(dá)式匹配到記錄名稱剖张,然后保存所需的任何信息切诀,如hello或數(shù)字10

微小的 Python 掃描器

我編寫了一個(gè)非常小的 Python 掃描器搔弄,演示了這個(gè)非常小的 Python 語言:

import re

code = [
"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], end
    return None, start, None

script = []

for line in code:
    i = 0
    while i < len(line):
        token, string, end = match(i, line)
        assert token, "Failed to match line %s" % string
        if token:
            i += end
            script.append((token, string, i, end))

print(script)

當(dāng)你運(yùn)行這個(gè)腳本時(shí)幅虑,你會(huì)得到一個(gè)tupleslist,它是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)]

這個(gè)代碼絕對不是你可以創(chuàng)建的最快或最準(zhǔn)確的掃描器炫刷。這是一個(gè)簡單的腳本擎宝,用于演示掃描器的工作原理。對于進(jìn)行真正的掃描工作浑玛,你將使用一種工具來生成更高效的掃描器绍申。我在深入學(xué)習(xí)部分介紹。

挑戰(zhàn)練習(xí)

你的工作是研究這個(gè)掃描器示例代碼,并將其轉(zhuǎn)換成通用的Scanner類以便稍后使用极阅。這個(gè)Scanner類的目標(biāo)是接受一個(gè)輸入文件胃碾,將其掃描為記號的列表,然后允許你按順序取出記號筋搏。API 應(yīng)具有以下功能:

__init__

使用類似的元組列表(沒有re.compile)來配置掃描器仆百。

scan

接受一個(gè)字符串并執(zhí)行掃描,創(chuàng)建一個(gè)記錄列表以便以后使用奔脐。你應(yīng)該保留這個(gè)字符串儒旬,讓人們以后訪問。

match

提供可能的記號列表帖族,返回列表中的第一個(gè)記號栈源,并將其移除。

peek

提供可能的記號列表竖般,返回列表中的第一個(gè)記號甚垦,但不將其移除。

push

將記號放回記號流中涣雕,以便后續(xù)的peek或者match返回它艰亮。

你也應(yīng)該創(chuàng)建通用的Token類來代替我使用的tuple。它應(yīng)該能夠跟蹤發(fā)現(xiàn)的記號挣郭,匹配的字符串迄埃、原始字符串中匹配位置的開頭和末尾。

研究性學(xué)習(xí)

  • 安裝pytest-cov庫兑障,并使用它來測量自動(dòng)化測試的覆蓋率侄非。
  • 使用pytest-cov的結(jié)果來改進(jìn)自動(dòng)化測試。

深入學(xué)習(xí)

創(chuàng)建掃描器的更好方法是流译,利用以下關(guān)于正則表達(dá)式的三個(gè)事實(shí):

  • 正則表達(dá)式是有限狀態(tài)機(jī)逞怨。
  • 你可以將小型有限狀態(tài)機(jī)精確地組合成更大更復(fù)雜的有限狀態(tài)機(jī)。
  • 匹配許多小型正則表達(dá)式的有限狀態(tài)機(jī)組合福澡,操作方式每個(gè)正則表達(dá)式一樣叠赦,并且效率更高。

有許多工具使用這個(gè)事實(shí)來接受掃描器定義革砸,將每個(gè)小的正則表達(dá)式轉(zhuǎn)換為 FSM除秀,然后將它們組合來產(chǎn)生大段代碼,可以可靠地匹配所有記號算利。這樣做的優(yōu)點(diǎn)是册踩,你可以以滾動(dòng)方式為這些生成的掃描器提供獨(dú)立的字符,并使其快速識別記號笔时。它比我這里的方式要好棍好,其中我拼湊字符串仗岸,并嘗試一系列正則表達(dá)式允耿,直到找到一個(gè)正則表達(dá)式借笙。

研究掃描器的發(fā)生器如何工作,并將其與你編寫的代碼進(jìn)行比較较锡。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末业稼,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子蚂蕴,更是在濱河造成了極大的恐慌低散,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件骡楼,死亡現(xiàn)場離奇詭異熔号,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)鸟整,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進(jìn)店門引镊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人篮条,你說我怎么就攤上這事弟头。” “怎么了涉茧?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵赴恨,是天一觀的道長。 經(jīng)常有香客問我伴栓,道長伦连,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任钳垮,我火速辦了婚禮除师,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘扔枫。我一直安慰自己汛聚,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布短荐。 她就那樣靜靜地躺著倚舀,像睡著了一般。 火紅的嫁衣襯著肌膚如雪忍宋。 梳的紋絲不亂的頭發(fā)上痕貌,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天,我揣著相機(jī)與錄音糠排,去河邊找鬼舵稠。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的哺徊。 我是一名探鬼主播室琢,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼落追!你這毒婦竟也來了盈滴?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤轿钠,失蹤者是張志新(化名)和其女友劉穎巢钓,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體疗垛,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡症汹,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了贷腕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片烈菌。...
    茶點(diǎn)故事閱讀 39,795評論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖花履,靈堂內(nèi)的尸體忽然破棺而出芽世,到底是詐尸還是另有隱情,我是刑警寧澤诡壁,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布济瓢,位于F島的核電站,受9級特大地震影響妹卿,放射性物質(zhì)發(fā)生泄漏旺矾。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一夺克、第九天 我趴在偏房一處隱蔽的房頂上張望箕宙。 院中可真熱鬧,春花似錦铺纽、人聲如沸柬帕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽陷寝。三九已至,卻和暖如春其馏,著一層夾襖步出監(jiān)牢的瞬間凤跑,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工叛复, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留仔引,地道東北人扔仓。 一個(gè)月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像咖耘,于是被迫代替她去往敵國和親翘簇。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內(nèi)容