re- 正則表達操作

這個模塊提供了與 Perl 語言類似的正則表達式匹配操作。

模式和被搜索的字符串既可以是 Unicode 字符串 (str) ,也可以是8位字節串 (bytes)。 但是,Unicode 字符串與8位字節串不能混用:也就是說,你不能用一個字節串模式去匹配 Unicode 字符串,反之亦然;類似地,當進行替換操作時,替換字符串的類型也必須與所用的模式和搜索字符串的類型一致。

解決辦法是對於正則表達式樣式使用 Python 的原始字符串表示法;在帶有 ‘r’ 前綴的字符串字面值中,反斜槓不必做任何特殊處理。 因此 r"\n" 表示包含 ‘’ 和 ‘n’ 兩個字符的字符串,而 “\n” 則表示只包含一個換行符的字符串。 樣式在 Python 代碼中通常都會使用這種原始字符串表示法來表示。

正則表達式語法

一個正則表達式(或RE)指定了一集與之匹配的字符串;模塊內的函數可以讓你檢查某個字符串是否跟給定的正則表達式匹配(或者一個正則表達式是否匹配到一個字符串,這兩種說法含義相同)。

正則表達式可以拼接; 如果 A 和 B 都是正則表達式, 那麼 AB 也是正則表達式。 通常, 如果字符串 p 匹配 A 並且另一個字符串 q 匹配 B, 那麼 pq 可以匹配 AB。除非 A 或者 B 包含低優先級操作,A 和 B 存在邊界條件;或者命名組引用。所以,複雜表達式可以很容易的從這裏描述的簡單源語表達式構建。 瞭解更多正則表達式理論和實現,參考the Friedl book [Frie09] ,或者其他編譯器構建的書籍。

以下是正則表達式格式的簡要說明。更詳細的信息和演示,參考 正則表達式HOWTO。

正則表達式可以包含普通或者特殊字符。絕大部分普通字符,比如 ‘A’, ‘a’, 或者 ‘0’,都是最簡單的正則表達式。它們就匹配自身。你可以拼接普通字符,所以 last 匹配字符串 ‘last’. (在這一節的其他部分,我們將用 this special style 這種方式表示正則表達式,通常不帶引號,要匹配的字符串用 ‘in single quotes’ ,單引號形式。)

有些字符,比如 ‘|’ 或者 ‘(’,屬於特殊字符。 特殊字符既可以表示它的普通含義, 也可以影響它旁邊的正則表達式的解釋。

重複修飾符 (, +, ?, {m,n}, 等) 不能直接嵌套。這樣避免了非貪婪後綴 ? 修飾符,和其他實現中的修飾符產生的多義性。要應用一個內層重複嵌套,可以使用括號。 比如,表達式 (?:a{6}) 匹配6個 ‘a’ 字符重複任意次數。

特殊字符是:
.
(點) 在默認模式,匹配除了換行的任意字符。如果指定了標籤 DOTALL ,它將匹配包括換行符的任意字符。

^
(插入符號) 匹配字符串的開頭, 並且在 MULTILINE 模式也匹配換行後的首個符號。

$
匹配字符串尾或者換行符的前一個字符,在 MULTILINE 模式匹配換行符的前一個字符。 foo 匹配 ‘foo’ 和 ‘foobar’ , 但正則 foo$ 只匹配 ‘foo’。更有趣的是, 在 ‘foo1\nfoo2\n’ 搜索 foo.$ ,通常匹配 ‘foo2’ ,但在 MULTILINE 模式 ,可以匹配到 ‘foo1’ ;在 ‘foo\n’ 搜索 $ 會找到兩個空串:一個在換行前,一個在字符串最後。

*
對它前面的正則式匹配0到任意次重複, 儘量多的匹配字符串。 ab* 會匹配 ‘a’, ‘ab’, 或者 ‘a’後面跟隨任意個‘b’。

+
對它前面的正則式匹配1到任意次重複。 ab+ 會匹配 ‘a’ 後面跟隨1個以上到任意個 ‘b’,它不會匹配 ‘a’。

?
對它前面的正則式匹配0到1次重複。 ab? 會匹配 ‘a’ 或者 ‘ab’。

?, +?, ??
'
’, ‘+’,和 ‘?’ 修飾符都是 貪婪的;它們在字符串進行儘可能多的匹配。有時候並不需要這種行爲。如果正則式 <.> 希望找到 ’ b ',它將會匹配整個字符串,而不僅是 ‘’。在修飾符之後添加 ? 將使樣式以 非貪婪方式或者 :dfn:最小 方式進行匹配; 儘量 少 的字符將會被匹配。 使用正則式 <.?> 將會僅僅匹配 ‘’。

"{m}"
對其之前的正則式指定匹配 m 個重複;少於 m 的話就會導致匹配失敗。比如, a{6} 將匹配6個 ‘a’ , 但是不能是5個。

"{m, n}"
對正則式進行 m 到 n 次匹配,在 m 和 n 之間取儘量多。 比如,a{3,5} 將匹配 3 到 5個 ‘a’。忽略 m 意爲指定下界爲0,忽略 n 指定上界爲無限次。 比如 a{4,}b 將匹配 ‘aaaab’ 或者1000個 ‘a’ 尾隨一個 ‘b’,但不能匹配 ‘aaab’。逗號不能省略,否則無法辨別修飾符應該忽略哪個邊界。

{m,n}?
前一個修飾符的非貪婪模式,只匹配儘量少的字符次數。比如,對於 ‘aaaaaa’, a{3,5} 匹配 5個 ‘a’ ,而 a{3,5}? 只匹配3個 ‘a’。

\
轉義特殊字符(允許你匹配 ‘*’, ‘?’, 或者此類其他),或者表示一個特殊序列;特殊序列之後進行討論。

如果你沒有使用原始字符串( r’raw’ )來表達樣式,要牢記Python也使用反斜槓作爲轉義序列;如果轉義序列不被Python的分析器識別,反斜槓和字符才能出現在字符串中。如果Python可以識別這個序列,那麼反斜槓就應該重複兩次。這將導致理解障礙,所以高度推薦,就算是最簡單的表達式,也要使用原始字符串。

[]

用於表示一個字符集合。在一個集合中:

字符可以單獨列出,比如 [amk] 匹配 ‘a’, ‘m’, 或者 ‘k’。

可以表示字符範圍,通過用 ‘-’ 將兩個字符連起來。比如 [a-z] 將匹配任何小寫ASCII字符, [0-5][0-9] 將匹配從 00 到 59 的兩位數字, [0-9A-Fa-f] 將匹配任何十六進制數位。 如果 - 進行了轉義 (比如 [a-z])或者它的位置在首位或者末尾(如 [-a] 或 [a-]),它就只表示普通字符 ‘-’。

特殊字符在集合中,失去它的特殊含義。比如 [(+)] 只會匹配這幾個文法字符 ‘(’, ‘+’, '’, or ‘)’。

字符類如 \w 或者 \S (如下定義) 在集合內可以接受,它們可以匹配的字符由 ASCII 或者 LOCALE 模式決定。

不在集合範圍內的字符可以通過 取反 來進行匹配。如果集合首字符是 ‘^’ ,所有 不 在集合內的字符將會被匹配,比如 [^5] 將匹配所有字符,除了 ‘5’, [^^] 將匹配所有字符,除了 ‘^’. ^ 如果不在集合首位,就沒有特殊含義。

在集合內要匹配一個字符 ‘]’,有兩種方法,要麼就在它之前加上反斜槓,要麼就把它放到集合首位。比如, [()[]{}] 和 [{}] 都可以匹配括號。

Unicode Technical Standard #18 裏的嵌套集合和集合操作支持可能在未來添加。這將會改變語法,所以爲了幫助這個改變,一個 FutureWarning 將會在有多義的情況裏被 raise,包含以下幾種情況,集合由 ‘[’ 開始,或者包含下列字符序列 ‘–’, ‘&&’, ‘~~’, 和 ‘||’。爲了避免警告,需要將它們用反斜槓轉義。

在 3.7 版更改: 如果一個字符串構建的語義在未來會改變的話,一個 FutureWarning 會 raise 。

|
A|B, A 和 B 可以是任意正則表達式,創建一個正則表達式,匹配 A 或者 B. 任意個正則表達式可以用 ‘|’ 連接。它也可以在組合(見下列)內使用。掃描目標字符串時, ‘|’ 分隔開的正則樣式從左到右進行匹配。當一個樣式完全匹配時,這個分支就被接受。意思就是,一旦 A 匹配成功, B 就不再進行匹配,即便它能產生一個更好的匹配。或者說,’|’ 操作符絕不貪婪。 如果要匹配 ‘|’ 字符,使用 |, 或者把它包含在字符集裏,比如 [|].

(…)
(組合),匹配括號內的任意正則表達式,並標識出組合的開始和結尾。匹配完成後,組合的內容可以被獲取,並可以在之後用 \number 轉義序列進行再次匹配,之後進行詳細說明。要匹配字符 ‘(’ 或者 ‘)’, 用 ( 或 ), 或者把它們包含在字符集合裏: [(], [)].

(?…)
這是個擴展標記法 (一個 ‘?’ 跟隨 ‘(’ 並無含義)。 ‘?’ 後面的第一個字符決定了這個構建採用什麼樣的語法。這種擴展通常並不創建新的組合; (?P…) 是唯一的例外。 以下是目前支持的擴展。

(?aiLmsux)
( ‘a’, ‘i’, ‘L’, ‘m’, ‘s’, ‘u’, ‘x’ 中的一個或多個) 這個組合匹配一個空字符串;這些字符對正則表達式設置以下標記 re.A (只匹配ASCII字符), re.I (忽略大小寫), re.L (語言依賴), re.M (多行模式), re.S (點dot匹配全部字符), re.U (Unicode匹配), and re.X (冗長模式)。 (這些標記在 模塊內容 中描述) 如果你想將這些標記包含在正則表達式中,這個方法就很有用,免去了在 re.compile() 中傳遞 flag 參數。標記應該在表達式字符串首位表示。

(?:…)
正則括號的非捕獲版本。 匹配在括號內的任何正則表達式,但該分組所匹配的子字符串 不能 在執行匹配後被獲取或是之後在模式中被引用。

(?aiLmsux-imsx:…)
(‘a’, ‘i’, ‘L’, ‘m’, ‘s’, ‘u’, ‘x’ 中的0或者多個, 之後可選跟隨 ‘-’ 在後面跟隨 ‘i’ , ‘m’ , ‘s’ , ‘x’ 中的一到多個 .) 這些字符爲表達式的其中一部分 設置 或者 去除 相應標記 re.A (只匹配ASCII), re.I (忽略大小寫), re.L (語言依賴), re.M (多行), re.S (點匹配所有字符), re.U (Unicode匹配), and re.X (冗長模式)。(標記描述在 模塊內容 .)

‘a’, ‘L’ and ‘u’ 作爲內聯標記是相互排斥的, 所以它們不能結合在一起,或者跟隨 ‘-’ 。 當他們中的某個出現在內聯組中,它就覆蓋了括號組內的匹配模式。在Unicode樣式中, (?a:…) 切換爲 只匹配ASCII, (?u:…) 切換爲Unicode匹配 (默認). 在byte樣式中 (?L:…) 切換爲語言依賴模式, (?a:…) 切換爲 只匹配ASCII (默認)。這種方式只覆蓋組合內匹配,括號外的匹配模式不受影響。

在 3.7 版更改: 符號 ‘a’, ‘L’ 和 ‘u’ 同樣可以用在一個組合內。

(?P…)
(命名組合)類似正則組合,但是匹配到的子串組在外部是通過定義的 name 來獲取的。組合名必須是有效的Python標識符,並且每個組合名只能用一個正則表達式定義,只能定義一次。一個符號組合同樣是一個數字組合,就像這個組合沒有被命名一樣。

命名組合可以在三種上下文中引用。如果樣式是 (?P[’"]).*?(?P=quote) (也就是說,匹配單引號或者雙引號括起來的字符串):

引用組合 “quote” 的上下文 引用方法
在正則式自身內 (?P=quote) (如示) - \1
處理匹配對象 m m.group(‘quote’) m.end(‘quote’) (等)
傳遞到 re.sub() 裏的 repl 參數中 \g<quote> - \g<1> - \1

(?P=name)
反向引用一個命名組合;它匹配前面那個叫 name 的命名組中匹配到的串同樣的字串。

(?#…)
註釋;裏面的內容會被忽略。

(?=…)
匹配 … 的內容,但是並不消費樣式的內容。這個叫做 lookahead assertion。比如, Isaac (?=Asimov) 匹配 'Isaac ’ 只有在後面是 ‘Asimov’ 的時候。

(?!…)
匹配 … 不符合的情況。這個叫 negative lookahead assertion (前視取反)。比如說, Isaac (?!Asimov) 只有後面 不 是 ‘Asimov’ 的時候才匹配 'Isaac ’ 。

(?<=…)
匹配字符串的當前位置,它的前面匹配 … 的內容到當前位置。這叫:dfn:positive lookbehind assertion (正向後視斷定)。 (?<=abc)def 會在 ‘abcdef’ 中找到一個匹配,因爲後視會往後看3個字符並檢查是否包含匹配的樣式。包含的匹配樣式必須是定長的,意思就是 abc 或 a|b 是允許的,但是 a* 和 a{3,4} 不可以。注意以 positive lookbehind assertions 開始的樣式,如 (?<=abc)def ,並不是從 a 開始搜索,而是從 d 往回看的。你可能更加願意使用 search() 函數,而不是 match() 函數:

>>> import re
>>> m = re.search('(?<=abc)def', 'abcdef')
>>> m.group(0)
'def'

這個例子搜索一個跟隨在連字符後的單詞:

>>> m = re.search(r'(?<=-)\w+', 'spam-egg')
>>> m.group(0)
'egg'

(?<!…)
匹配當前位置之前不是 … 的樣式。這個叫:dfn:negative lookbehind assertion (後視斷定取非)。類似正向後視斷定,包含的樣式匹配必須是定長的。由 negative lookbehind assertion 開始的樣式可以從字符串搜索開始的位置進行匹配。

(?(id/name)yes-pattern|no-pattern)
如果給定的 id 或 name 存在,將會嘗試匹配 yes-pattern ,否則就嘗試匹配 no-pattern,no-pattern 可選,也可以被忽略。比如, (<)?(\w+@\w+(?:.\w+)+)(?(1)>|$) 是一個email樣式匹配,將匹配 ‘[email protected]’ 或 ‘[email protected]’ ,但不會匹配 ‘<[email protected]’ ,也不會匹配 ‘[email protected]>’。

由 ‘’ 和一個字符組成的特殊序列在以下列出。 如果普通字符不是ASCII數位或者ASCII字母,那麼正則樣式將匹配第二個字符。比如,$ 匹配字符 ‘$’.

\number
匹配數字代表的組合。每個括號是一個組合,組合從1開始編號。比如 (.+) \1 匹配 ‘the the’ 或者 ‘55 55’, 但不會匹配 ‘thethe’ (注意組合後面的空格)。這個特殊序列只能用於匹配前面99個組合。如果 number 的第一個數位是0, 或者 number 是三個八進制數,它將不會被看作是一個組合,而是八進制的數字值。在 ‘[’ 和 ‘]’ 字符集合內,任何數字轉義都被看作是字符。

\A
只匹配字符串開始。

\b
匹配空字符串,但只在單詞開始或結尾的位置。一個單詞被定義爲一個單詞字符的序列。注意,通常 \b 定義爲 \w 和 \W 字符之間,或者 \w 和字符串開始/結尾的邊界, 意思就是 r’\bfoo\b’ 匹配 ‘foo’, ‘foo.’, ‘(foo)’, ‘bar foo baz’ 但不匹配 ‘foobar’ 或者 ‘foo3’。

默認情況下,Unicode字母和數字是在Unicode樣式中使用的,但是可以用 ASCII 標記來更改。如果 LOCALE 標記被設置的話,詞的邊界是由當前語言區域設置決定的,\b 表示退格字符,以便與Python字符串文本兼容。

\B
匹配空字符串,但 不 能在詞的開頭或者結尾。意思就是 r’py\B’ 匹配 ‘python’, ‘py3’, ‘py2’, 但不匹配 ‘py’, ‘py.’, 或者 ‘py!’. \B 是 \b 的取非,所以Unicode樣式的詞語是由Unicode字母,數字或下劃線構成的,雖然可以用 ASCII 標誌來改變。如果使用了 LOCALE 標誌,則詞的邊界由當前語言區域設置。

\d
對於 Unicode (str) 樣式:
匹配任何Unicode十進制數(就是在Unicode字符目錄[Nd]裏的字符)。這包括了 [0-9] ,和很多其他的數字字符。如果設置了 ASCII 標誌,就只匹配 [0-9] 。

對於8位(bytes)樣式:
匹配任何十進制數,就是 [0-9]。

\D
匹配任何非十進制數字的字符。就是 \d 取非。 如果設置了 ASCII 標誌,就相當於 [^0-9] 。

\s
對於 Unicode (str) 樣式:
匹配任何Unicode空白字符(包括 [ \t\n\r\f\v] ,還有很多其他字符,比如不同語言排版規則約定的不換行空格)。如果 ASCII 被設置,就只匹配 [ \t\n\r\f\v] 。

對於8位(bytes)樣式:
匹配ASCII中的空白字符,就是 [ \t\n\r\f\v] 。

\S
匹配任何非空白字符。就是 \s 取非。如果設置了 ASCII 標誌,就相當於 [^ \t\n\r\f\v] 。

\w
對於 Unicode (str) 樣式:
匹配Unicode詞語的字符,包含了可以構成詞語的絕大部分字符,也包括數字和下劃線。如果設置了 ASCII 標誌,就只匹配 [a-zA-Z0-9_] 。

對於8位(bytes)樣式:
匹配ASCII字符中的數字和字母和下劃線,就是 [a-zA-Z0-9_] 。如果設置了 LOCALE 標記,就匹配當前語言區域的數字和字母和下劃線。

\W
Matches any character which is not a word character. This is the opposite of \w. If the ASCII flag is used this becomes the equivalent of [^a-zA-Z0-9_]. If the LOCALE flag is used, matches characters which are neither alphanumeric in the current locale nor the underscore.

\Z
只匹配字符串尾。

絕大部分Python的標準轉義字符也被正則表達式分析器支持。:

\a      \b      \f      \n
\r      \t      \u      \U
\v      \x      \\

(注意 \b 被用於表示詞語的邊界,它只在字符集合內表示退格,比如 [\b] 。)

‘\u’ and ‘\U’ escape sequences are only recognized in Unicode patterns. In bytes patterns they are errors. Unknown escapes of ASCII letters are reserved for future use and treated as errors.

八進制轉義包含爲一個有限形式。如果首位數字是 0, 或者有三個八進制數位,那麼就認爲它是八進制轉義。其他的情況,就看作是組引用。對於字符串文本,八進制轉義最多有三個數位長。

在 3.3 版更改: 增加了 ‘\u’ 和 ‘\U’ 轉義序列。

在 3.6 版更改: 由 ‘’ 和一個ASCII字符組成的未知轉義會被看成錯誤。

模塊內容

模塊定義了幾個函數,常量,和一個例外。有些函數是編譯後的正則表達式方法的簡化版本(少了一些特性)。絕大部分重要的應用,總是會先將正則表達式編譯,之後在進行操作。

在 3.6 版更改: 標誌常量現在是 RegexFlag 類的實例,這個類是 enum.IntFlag 的子類。

re.compile(pattern, flags=0)

將正則表達式的樣式編譯爲一個 正則表達式對象 (正則對象),可以用於匹配,通過這個對象的方法 match(), search() 以及其他如下描述。

這個表達式的行爲可以通過指定 標記 的值來改變。值可以是以下任意變量,可以通過位的OR操作來結合( | 操作符)。

prog = re.compile(pattern)
result = prog.match(string)

等價於

result = re.match(pattern, string)

如果需要多次使用這個正則表達式的話,使用 re.compile() 和保存這個正則對象以便複用,可以讓程序更加高效。

註解 通過 re.compile() 編譯後的樣式,和模塊級的函數會被緩存, 所以少數的正則表達式使用無需考慮編譯的問題。

re.A
re.ASCII

讓 \w, \W, \b, \B, \d, \D, \s 和 \S 只匹配ASCII,而不是Unicode。這隻對Unicode樣式有效,會被byte樣式忽略。相當於前面語法中的內聯標誌 (?a) 。

注意,爲了保持向後兼容, re.U 標記依然存在(還有他的同義 re.UNICODE 和嵌入形式 (?u) ) , 但是這些在 Python 3 是冗餘的,因爲默認字符串已經是Unicode了(並且Unicode匹配不允許byte出現)。

re.DEBUG
顯示編譯時的debug信息,沒有內聯標記。

re.I
re.IGNORECASE

進行忽略大小寫匹配;表達式如 [A-Z] 也會匹配小寫字符。Unicode匹配(比如 Ü 匹配 ü)同樣有用,除非設置了 re.ASCII 標記來禁用非ASCII匹配。當前語言區域不會改變這個標記,除非設置了 re.LOCALE 標記。這個相當於內聯標記 (?i) 。

注意,當設置了 IGNORECASE 標記,搜索Unicode樣式 [a-z] 或 [A-Z] 的結合時,它將會匹配52個ASCII字符和4個額外的非ASCII字符: ‘İ’ (U+0130, 拉丁大寫的 I 帶個點在上面), ‘ı’ (U+0131, 拉丁小寫沒有點的 I ), ‘ſ’ (U+017F, 拉丁小寫長 s) and ‘K’ (U+212A, 開爾文符號).如果使用 ASCII 標記,就只匹配 ‘a’ 到 ‘z’ 和 ‘A’ 到 ‘Z’ 。

re.L
re.LOCALE

由當前語言區域決定 \w, \W, \b, \B 和大小寫敏感匹配。這個標記只能對byte樣式有效。這個標記不推薦使用,因爲語言區域機制很不可靠,它一次只能處理一個 "習慣”,而且只對8位字節有效。Unicode匹配在Python 3 裏默認啓用,並可以處理不同語言。 這個對應內聯標記 (?L) 。

在 3.6 版更改: re.LOCALE 只能用於byte樣式,而且不能和 re.ASCII 一起用。

在 3.7 版更改: 設置了 re.LOCALE 標記的編譯正則對象不再在編譯時依賴語言區域設置。語言區域設置只在匹配的時候影響其結果。

re.M
re.MULTILINE

設置以後,樣式字符 ‘^’ 匹配字符串的開始,和每一行的開始(換行符後面緊跟的符號);樣式字符 ‘' 匹配字符串尾,和每一行的結尾(換行符前面那個符號)。默認情況下,’^’ 匹配字符串頭,'’ 匹配字符串尾。對應內聯標記 (?m) 。

re.S
re.DOTALL

讓 ‘.’ 特殊字符匹配任何字符,包括換行符;如果沒有這個標記,’.’ 就匹配 除了 換行符的其他任意字符。對應內聯標記 (?s) 。

re.X
re.VERBOSE

這個標記允許你編寫更具可讀性更友好的正則表達式。通過分段和添加註釋。空白符號會被忽略,除非在一個字符集合當中或者由反斜槓轉義,或者在 *?, (?: or (?P<…> 分組之內。當一個行內有 # 不在字符集和轉義序列,那麼它之後的所有字符都是註釋。

意思就是下面兩個正則表達式等價地匹配一個十進制數字:

a = re.compile(r"""\d +  # the integral part
                   \.    # the decimal point
                   \d *  # some fractional digits""", re.X)
b = re.compile(r"\d+\.\d*")

對應內聯標記 (?x) 。

re.search(pattern, string, flags=0)
掃描整個 字符串 找到匹配樣式的第一個位置,並返回一個相應的 匹配對象。如果沒有匹配,就返回一個 None ; 注意這和找到一個零長度匹配是不同的。

re.match(pattern, string, flags=0)
如果 string 開始的0或者多個字符匹配到了正則表達式樣式,就返回一個相應的 匹配對象 。 如果沒有匹配,就返回 None ;注意它跟零長度匹配是不同的。

注意即便是 MULTILINE 多行模式, re.match() 也只匹配字符串的開始位置,而不匹配每行開始。

如果你想定位 string 的任何位置,使用 search() 來替代(也可參考 search() vs. match() )

re.fullmatch(pattern, string, flags=0)
如果整個 string 匹配到正則表達式樣式,就返回一個相應的 匹配對象 。 否則就返回一個 None ;注意這跟零長度匹配是不同的。

3.4 新版功能.

re.split(pattern, string, maxsplit=0, flags=0)
用 pattern 分開 string 。 如果在 pattern 中捕獲到括號,那麼所有的組裏的文字也會包含在列表裏。如果 maxsplit 非零, 最多進行 maxsplit 次分隔, 剩下的字符全部返回到列表的最後一個元素。

>>> re.split(r'\W+', 'Words, words, words.')
['Words', 'words', 'words', '']
>>> re.split(r'(\W+)', 'Words, words, words.')
['Words', ', ', 'words', ', ', 'words', '.', '']
>>> re.split(r'\W+', 'Words, words, words.', 1)
['Words', 'words, words.']
>>> re.split('[a-f]+', '0a3B9', flags=re.IGNORECASE)
['0', '3', '9']

如果分隔符裏有捕獲組合,並且匹配到字符串的開始,那麼結果將會以一個空字符串開始。對於結尾也是一樣

>>> re.split(r'(\W+)', '...words, words...')
['', '...', 'words', ', ', 'words', '...', '']

這樣的話,分隔組將會出現在結果列表中同樣的位置。

樣式的空匹配將分開字符串,但只在不相臨的狀況生效。

>>> re.split(r'\b', 'Words, words, words.')
['', 'Words', ', ', 'words', ', ', 'words', '.']
>>> re.split(r'\W*', '...words...')
['', '', 'w', 'o', 'r', 'd', 's', '', '']
>>> re.split(r'(\W*)', '...words...')
['', '...', '', '', 'w', '', 'o', '', 'r', '', 'd', '', 's', '...', '', '', '']

在 3.1 版更改: 增加了可選標記參數。

在 3.7 版更改: 增加了空字符串的樣式分隔。

re.findall(pattern, string, flags=0)
對 string 返回一個不重複的 pattern 的匹配列表, string 從左到右進行掃描,匹配按找到的順序返回。如果樣式裏存在一到多個組,就返回一個組合列表;就是一個元組的列表(如果樣式裏有超過一個組合的話)。空匹配也會包含在結果裏。

在 3.7 版更改: 非空匹配現在可以在前一個空匹配之後出現了。

re.finditer(pattern, string, flags=0)
pattern 在 string 裏所有的非重複匹配,返回爲一個迭代器 iterator 保存了 匹配對象 。 string 從左到右掃描,匹配按順序排列。空匹配也包含在結果裏。

在 3.7 版更改: 非空匹配現在可以在前一個空匹配之後出現了。

re.sub(pattern, repl, string, count=0, flags=0)
返回通過使用 repl 替換在 string 最左邊非重疊出現的 pattern 而獲得的字符串。 如果樣式沒有找到,則不加改變地返回 string。 repl 可以是字符串或函數;如爲字符串,則其中任何反斜槓轉義序列都會被處理。 也就是說,\n 會被轉換爲一個換行符,\r 會被轉換爲一個回車附,依此類推。 未知的 ASCII 字符轉義序列保留在未來使用,會被當作錯誤來處理。 其他未知轉義序列例如 & 會保持原樣。 向後引用像是 \6 會用樣式中第 6 組所匹配到的子字符串來替換。 例如:

>>> re.sub(r'def\s+([a-zA-Z_][a-zA-Z_0-9]*)\s*\(\s*\):',
...        r'static PyObject*\npy_\1(void)\n{',
...        'def myfunc():')
'static PyObject*\npy_myfunc(void)\n{'

如果 repl 是一個函數,那它會對每個非重複的 pattern 的情況調用。這個函數只能有一個 匹配對象 參數,並返回一個替換後的字符串。比如

>>> def dashrepl(matchobj):
...     if matchobj.group(0) == '-': return ' '
...     else: return '-'
>>> re.sub('-{1,2}', dashrepl, 'pro----gram-files')
'pro--gram files'
>>> re.sub(r'\sAND\s', ' & ', 'Baked Beans And Spam', flags=re.IGNORECASE)
'Baked Beans & Spam'

樣式可以是一個字符串或者一個 樣式對象 。

可選參數 count 是要替換的最大次數;count 必須是非負整數。如果忽略這個參數,或者設置爲0,所有的匹配都會被替換。空匹配只在不相臨連續的情況被更替,所以 sub(‘x*’, ‘-’, ‘abxd’) 返回 ‘-a-b–d-’ 。

在字符串類型的 repl 參數裏,如上所述的轉義和向後引用中,\g 會使用命名組合 name,(在 (?P…) 語法中定義) \g 會使用數字組;\g<2> 就是 \2,但它避免了二義性,如 \g<2>0。 \20 就會被解釋爲組20,而不是組2後面跟隨一個字符 ‘0’。向後引用 \g<0> 把 pattern 作爲一整個組進行引用。

在 3.1 版更改: 增加了可選標記參數。

在 3.5 版更改: 不匹配的組合替換爲空字符串。

在 3.6 版更改: pattern 中的未知轉義(由 ‘’ 和一個 ASCII 字符組成)被視爲錯誤。

在 3.7 版更改: repl 中的未知轉義(由 ‘’ 和一個 ASCII 字符組成)被視爲錯誤。

在 3.7 版更改: 樣式中的空匹配相鄰接時會被替換。

re.subn(pattern, repl, string, count=0, flags=0)
行爲與 sub() 相同,但是返回一個元組 (字符串, 替換次數).

在 3.1 版更改: 增加了可選標記參數。

在 3.5 版更改: 不匹配的組合替換爲空字符串。

re.escape(pattern)
轉義 pattern 中的特殊字符。如果你想對任意可能包含正則表達式元字符的文本字符串進行匹配,它就是有用的。比如

>>> print(re.escape('python.exe'))
python\.exe

>>> legal_chars = string.ascii_lowercase + string.digits + "!#$%&'*+-.^_`|~:"
>>> print('[%s]+' % re.escape(legal_chars))
[abcdefghijklmnopqrstuvwxyz0123456789!\#\$%\&'\*\+\-\.\^_`\|\~:]+

>>> operators = ['+', '-', '*', '/', '**']
>>> print('|'.join(map(re.escape, sorted(operators, reverse=True))))
/|\-|\+|\*\*|\*

這個函數不能被用於 sub() 和 subn() 的替換字符串,只有反斜槓應該被轉義。 例如:

>>> digits_re = r'\d+'
>>> sample = '/usr/sbin/sendmail - 0 errors, 12 warnings'
>>> print(re.sub(digits_re, digits_re.replace('\\', r'\\'), sample))
/usr/sbin/sendmail - \d+ errors, \d+ warnings

在 3.3 版更改: ‘_’ 不再被轉義。

在 3.7 版更改: 只有在正則表達式中可以產生特殊含義的字符會被轉義。

re.purge()
清除正則表達式緩存。

exception re.error(msg, pattern=None, pos=None)
raise 一個例外。當傳遞到函數的字符串不是一個有效正則表達式的時候(比如,包含一個不匹配的括號)或者其他錯誤在編譯時或匹配時產生。如果字符串不包含樣式匹配,是不會被視爲錯誤的。錯誤實例有以下附加屬性:

msg
	未格式化的錯誤消息。
pattern
	正則表達式樣式。
pos
	編譯失敗的 pattern 的位置索引(可以是 None )。
lineno
	對應 pos (可以是 None) 的行號。
colno
	對應 pos (可以是 None) 的列號。

正則表達式對象 (正則對象)

編譯後的正則表達式對象支持一下方法和屬性:

Pattern.search(string[, pos[, endpos]])
掃描整個 string 尋找第一個匹配的位置, 並返回一個相應的 匹配對象。如果沒有匹配,就返回 None ;注意它和零長度匹配是不同的。

可選的第二個參數 pos 給出了字符串中開始搜索的位置索引;默認爲 0,它不完全等價於字符串切片; ‘^’ 樣式字符匹配字符串真正的開頭,和換行符後面的第一個字符,但不會匹配索引規定開始的位置。

可選參數 endpos 限定了字符串搜索的結束;它假定字符串長度到 endpos , 所以只有從 pos 到 endpos - 1 的字符會被匹配。如果 endpos 小於 pos,就不會有匹配產生;另外,如果 rx 是一個編譯後的正則對象, rx.search(string, 0, 50) 等價於 rx.search(string[:50], 0)。

>>> pattern = re.compile("d")
>>> pattern.search("dog")     # Match at index 0
<re.Match object; span=(0, 1), match='d'>
>>> pattern.search("dog", 1)  # No match; search doesn't include the "d"

Pattern.match(string[, pos[, endpos]])
如果 string 的 開始位置 能夠找到這個正則樣式的任意個匹配,就返回一個相應的 匹配對象。如果不匹配,就返回 None ;注意它與零長度匹配是不同的。

可選參數 pos 和 endpos 與 search() 含義相同。

>>> pattern = re.compile("o")
>>> pattern.match("dog")      # No match as "o" is not at the start of "dog".
>>> pattern.match("dog", 1)   # Match as "o" is the 2nd character of "dog".
<re.Match object; span=(1, 2), match='o'>

如果你想定位匹配在 string 中的位置,使用 search() 來替代(另參考 search() vs. match())。

Pattern.fullmatch(string[, pos[, endpos]])
如果整個 string 匹配這個正則表達式,就返回一個相應的 匹配對象 。 否則就返回 None ; 注意跟零長度匹配是不同的。

可選參數 pos 和 endpos 與 search() 含義相同。

>>> pattern = re.compile("o[gh]")
>>> pattern.fullmatch("dog")      # No match as "o" is not at the start of "dog".
>>> pattern.fullmatch("ogre")     # No match as not the full string matches.
>>> pattern.fullmatch("doggie", 1, 3)   # Matches within given limits.
<re.Match object; span=(1, 3), match='og'>

Pattern.split(string, maxsplit=0)
等價於 split() 函數,使用了編譯後的樣式。

Pattern.findall(string[, pos[, endpos]])
類似函數 findall() , 使用了編譯後樣式,但也可以接收可選參數 pos 和 endpos ,限制搜索範圍,就像 search()。

Pattern.finditer(string[, pos[, endpos]])
類似函數 finiter() , 使用了編譯後樣式,但也可以接收可選參數 pos 和 endpos ,限制搜索範圍,就像 search()。

Pattern.sub(repl, string, count=0)
等價於 sub() 函數,使用了編譯後的樣式。

Pattern.subn(repl, string, count=0)
等價於 subn() 函數,使用了編譯後的樣式。

Pattern.flags
正則匹配標記。這是可以傳遞給 compile() 的參數,任何 (?…) 內聯標記,隱性標記比如 UNICODE 的結合。

Pattern.groups
捕獲組合的數量。

Pattern.groupindex
映射由 (?P) 定義的命名符號組合和數字組合的字典。如果沒有符號組,那字典就是空的。

Pattern.pattern
編譯對象的原始樣式字符串。

在 3.7 版更改: 添加 copy.copy() 和 copy.deepcopy() 函數的支持。編譯後的正則表達式對象被認爲是原子性的。

匹配對象

匹配對象總是有一個布爾值 True。如果沒有匹配的話 match() 和 search() 返回 None 所以你可以簡單的用 if 語句來判斷是否匹配

match = re.search(pattern, string)
if match:
    process(match)

匹配對象支持以下方法和屬性:

Match.expand(template)
對 template 進行反斜槓轉義替換並且返回,就像 sub() 方法中一樣。轉義如同 \n 被轉換成合適的字符,數字引用(\1, \2)和命名組合(\g<1>, \g) 替換爲相應組合的內容。

在 3.5 版更改: 不匹配的組合替換爲空字符串。

Match.group([group1, …])
返回一個或者多個匹配的子組。如果只有一個參數,結果就是一個字符串,如果有多個參數,結果就是一個元組(每個參數對應一個項),如果沒有參數,組1默認到0(整個匹配都被返回)。 如果一個組N 參數值爲 0,相應的返回值就是整個匹配字符串;如果它是一個範圍 [1…99],結果就是相應的括號組字符串。如果一個組號是負數,或者大於樣式中定義的組數,一個 IndexError 索引錯誤就 raise。如果一個組包含在樣式的一部分,並被匹配多次,就返回最後一個匹配。:

>>> m = re.match(r"(\w+) (\w+)", "Isaac Newton, physicist")
>>> m.group(0)       # The entire match
'Isaac Newton'
>>> m.group(1)       # The first parenthesized subgroup.
'Isaac'
>>> m.group(2)       # The second parenthesized subgroup.
'Newton'
>>> m.group(1, 2)    # Multiple arguments give us a tuple.
('Isaac', 'Newton')

如果正則表達式使用了 (?P…) 語法, groupN 參數就也可能是命名組合的名字。如果一個字符串參數在樣式中未定義爲組合名,一個 IndexError 就 raise。

一個相對複雜的例子

>>> m = re.match(r"(?P<first_name>\w+) (?P<last_name>\w+)", "Malcolm Reynolds")
>>> m.group('first_name')
'Malcolm'
>>> m.group('last_name')
'Reynolds'

命名組合同樣可以通過索引值引用

>>> m.group(1)
'Malcolm'
>>> m.group(2)
'Reynolds'

如果一個組匹配成功多次,就只返回最後一個匹配

>>> m = re.match(r"(..)+", "a1b2c3")  # Matches 3 times.
>>> m.group(1)                        # Returns only the last match.
'c3'

Match.getitem(g)
這個等價於 m.group(g)。這允許更方便的引用一個匹配

>>> m = re.match(r"(\w+) (\w+)", "Isaac Newton, physicist")
>>> m[0]       # The entire match
'Isaac Newton'
>>> m[1]       # The first parenthesized subgroup.
'Isaac'
>>> m[2]       # The second parenthesized subgroup.
'Newton'

Match.groups(default=None)
返回一個元組,包含所有匹配的子組,在樣式中出現的從1到任意多的組合。 default 參數用於不參與匹配的情況,默認爲 None。

例如

>>> m = re.match(r"(\d+)\.(\d+)", "24.1632")
>>> m.groups()
('24', '1632')

如果我們使小數點可選,那麼不是所有的組都會參與到匹配當中。這些組合默認會返回一個 None ,除非指定了 default 參數。

>>> m = re.match(r"(\d+)\.?(\d+)?", "24")
>>> m.groups()      # Second group defaults to None.
('24', None)
>>> m.groups('0')   # Now, the second group defaults to '0'.
('24', '0')

Match.groupdict(default=None)
返回一個字典,包含了所有的 命名 子組。key就是組名。 default 參數用於不參與匹配的組合;默認爲 None。 例如

>>> m = re.match(r"(?P<first_name>\w+) (?P<last_name>\w+)", "Malcolm Reynolds")
>>> m.groupdict()
{'first_name': 'Malcolm', 'last_name': 'Reynolds'}

Match.start([group])
Match.end([group])

返回 group 匹配到的字串的開始和結束標號。group 默認爲0(意思是整個匹配的子串)。如果 group 存在,但未產生匹配,就返回 -1 。對於一個匹配對象 m, 和一個未參與匹配的組 g ,組 g (等價於 m.group(g))產生的匹配是

m.string[m.start(g):m.end(g)]

注意 m.start(group) 將會等於 m.end(group) ,如果 group 匹配一個空字符串的話。比如,在 m = re.search(‘b(c?)’, ‘cba’) 之後,m.start(0) 爲 1, m.end(0) 爲 2, m.start(1) 和 m.end(1) 都是 2, m.start(2) raise 一個 IndexError 例外。

這個例子會從email地址中移除掉 remove_this

>>> email = "tony@tiremove_thisger.net"
>>> m = re.search("remove_this", email)
>>> email[:m.start()] + email[m.end():]
'[email protected]'

Match.span([group])
對於一個匹配 m , 返回一個二元組 (m.start(group), m.end(group)) 。 注意如果 group 沒有在這個匹配中,就返回 (-1, -1) 。group 默認爲0,就是整個匹配。

Match.pos
pos 的值,會傳遞給 search() 或 match() 的方法 a 正則對象 。這個是正則引擎開始在字符串搜索一個匹配的索引位置。

Match.endpos
endpos 的值,會傳遞給 search() 或 match() 的方法 a 正則對象 。這個是正則引擎停止在字符串搜索一個匹配的索引位置。

Match.lastindex
捕獲組的最後一個匹配的整數索引值,或者 None 如果沒有匹配產生的話。比如,對於字符串 ‘ab’,表達式 (a)b, ((a)(b)), 和 ((ab)) 將得到 lastindex == 1 , 而 (a)(b) 會得到 lastindex == 2 。

Match.lastgroup
最後一個匹配的命名組名字,或者 None 如果沒有產生匹配的話。

Match.re
返回產生這個實例的 正則對象 , 這個實例是由 正則對象的 match() 或 search() 方法產生的。

Match.string
傳遞到 match() 或 search() 的字符串。

正則表達式例子

檢查對子

def displaymatch(match):
    if match is None:
        return None
    return '<Match: %r, groups=%r>' % (match.group(), match.groups())

假設你在寫一個撲克程序,一個玩家的一手牌爲五個字符的串,每個字符表示一張牌,“a” 就是 A, “k” K, “q” Q, “j” J, “t” 爲 10, “2” 到 “9” 表示2 到 9。

要看給定的字符串是否有效,我們可以按照以下步驟

>>> valid = re.compile(r"^[a2-9tjqk]{5}$")
>>> displaymatch(valid.match("akt5q"))  # Valid.
"<Match: 'akt5q', groups=()>"
>>> displaymatch(valid.match("akt5e"))  # Invalid.
>>> displaymatch(valid.match("akt"))    # Invalid.
>>> displaymatch(valid.match("727ak"))  # Valid.
"<Match: '727ak', groups=()>"

最後一手牌,“727ak” ,包含了一個對子,或者兩張同樣數值的牌。要用正則表達式匹配它,應該使用向後引用如下

>>> pair = re.compile(r".*(.).*\1")
>>> displaymatch(pair.match("717ak"))     # Pair of 7s.
"<Match: '717', groups=('7',)>"
>>> displaymatch(pair.match("718ak"))     # No pairs.
>>> displaymatch(pair.match("354aa"))     # Pair of aces.
"<Match: '354aa', groups=('a',)>"

To find out what card the pair consists of, one could use the group() method of the match object in the following manner:

>>> pair.match("717ak").group(1)
'7'

# Error because re.match() returns None, which doesn't have a group() method:
>>> pair.match("718ak").group(1)
Traceback (most recent call last):
  File "<pyshell#23>", line 1, in <module>
    re.match(r".*(.).*\1", "718ak").group(1)
AttributeError: 'NoneType' object has no attribute 'group'

>>> pair.match("354aa").group(1)
'a'

模擬 scanf()

Python 目前沒有一個類似c函數 scanf() 的替代品。正則表達式通常比 scanf() 格式字符串要更強大一些,但也帶來更多複雜性。下面的表格提供了 scanf() 格式符和正則表達式大致相同的映射。

scanf() 格式符 正則表達式
%c .
%5c .{5}
%d [-+]?\d+
%e, %E, %f, %g [-+]?(\d+(.\d*)?
%i [-+]?(0[xX][\dA-Fa-f]+
%o [-+]?[0-7]+
%s \S+
%u \d+
%x, %X [-+]?(0[xX])?[\dA-Fa-f]+

從文件名和數字提取字符串

/usr/sbin/sendmail - 0 errors, 4 warnings

你可以使用 scanf() 格式化

%s - %d errors, %d warnings

等價的正則表達式是:

(\S+) - (\d+) errors, (\d+) warnings

search() vs. match()

Python 提供了兩種不同的操作:基於 re.match() 檢查字符串開頭,或者 re.search() 檢查字符串的任意位置(默認Perl中的行爲)。
例如

>>> re.match("c", "abcdef")    # No match
>>> re.search("c", "abcdef")   # Match
<re.Match object; span=(2, 3), match='c'>

在 search() 中,可以用 ‘^’ 作爲開始來限制匹配到字符串的首位

>>> re.match("c", "abcdef")    # No match
>>> re.search("^c", "abcdef")  # No match
>>> re.search("^a", "abcdef")  # Match
<re.Match object; span=(0, 1), match='a'>

注意 MULTILINE 多行模式中函數 match() 只匹配字符串的開始,但使用 search() 和以 ‘^’ 開始的正則表達式會匹配每行的開始

>>> re.match('X', 'A\nB\nX', re.MULTILINE)  # No match
>>> re.search('^X', 'A\nB\nX', re.MULTILINE)  # Match
<re.Match object; span=(4, 5), match='X'>

建立一個電話本
split() 將字符串用參數傳遞的樣式分隔開。這個方法對於轉換文本數據到易讀而且容易修改的數據結構,是很有用的,如下面的例子證明。

First, here is the input. Normally it may come from a file, here we are using triple-quoted string syntax:

>>> text = """Ross McFluff: 834.345.1254 155 Elm Street
...
... Ronald Heathmore: 892.345.3428 436 Finley Avenue
... Frank Burger: 925.541.7625 662 South Dogwood Way
...
...
... Heather Albrecht: 548.326.4584 919 Park Place"""

條目用一個或者多個換行符分開。現在我們將字符串轉換爲一個列表,每個非空行都有一個條目:

>>> entries = re.split("\n+", text)
>>> entries
['Ross McFluff: 834.345.1254 155 Elm Street',
'Ronald Heathmore: 892.345.3428 436 Finley Avenue',
'Frank Burger: 925.541.7625 662 South Dogwood Way',
'Heather Albrecht: 548.326.4584 919 Park Place']

最終,將每個條目分割爲一個由名字、姓氏、電話號碼和地址組成的列表。我們爲 split() 使用了 maxsplit 形參,因爲地址中包含有被我們作爲分割模式的空格符:

>>> [re.split(":? ", entry, 3) for entry in entries]
[['Ross', 'McFluff', '834.345.1254', '155 Elm Street'],
['Ronald', 'Heathmore', '892.345.3428', '436 Finley Avenue'],
['Frank', 'Burger', '925.541.7625', '662 South Dogwood Way'],
['Heather', 'Albrecht', '548.326.4584', '919 Park Place']]

:? 樣式匹配姓後面的冒號,因此它不出現在結果列表中。如果 maxsplit 設置爲 4 ,我們還可以從地址中獲取到房間號:

>>> [re.split(":? ", entry, 4) for entry in entries]
[['Ross', 'McFluff', '834.345.1254', '155', 'Elm Street'],
['Ronald', 'Heathmore', '892.345.3428', '436', 'Finley Avenue'],
['Frank', 'Burger', '925.541.7625', '662', 'South Dogwood Way'],
['Heather', 'Albrecht', '548.326.4584', '919', 'Park Place']]

文字整理

sub() 替換字符串中出現的樣式的每一個實例。這個例子證明了使用 sub() 來整理文字,或者隨機化每個字符的位置,除了首位和末尾字符

>>> def repl(m):
...     inner_word = list(m.group(2))
...     random.shuffle(inner_word)
...     return m.group(1) + "".join(inner_word) + m.group(3)
>>> text = "Professor Abdolmalek, please report your absences promptly."
>>> re.sub(r"(\w)(\w+)(\w)", repl, text)
'Poefsrosr Aealmlobdk, pslaee reorpt your abnseces plmrptoy.'
>>> re.sub(r"(\w)(\w+)(\w)", repl, text)
'Pofsroser Aodlambelk, plasee reoprt yuor asnebces potlmrpy.'

找到所有副詞

findall() 匹配樣式 所有 的出現,不僅是像 search() 中的第一個匹配。比如,如果一個作者希望找到文字中的所有副詞,他可能會按照以下方法用 findall()

>>> text = "He was carefully disguised but captured quickly by police."
>>> re.findall(r"\w+ly", text)
['carefully', 'quickly']

找到所有副詞和位置

如果需要匹配樣式的更多信息, finditer() 可以起到作用,它提供了 匹配對象 作爲返回值,而不是字符串。繼續上面的例子,如果一個作者希望找到所有副詞和它的位置,可以按照下面方法使用 finditer()

>>> text = "He was carefully disguised but captured quickly by police."
>>> for m in re.finditer(r"\w+ly", text):
...     print('%02d-%02d: %s' % (m.start(), m.end(), m.group(0)))
07-16: carefully
40-47: quickly

原始字符記法

原始字符串記法 (r"text") 保持正則表達式正常。否則,每個正則式裏的反斜槓(’’) 都必須前綴一個反斜槓來轉義。比如,下面兩行代碼功能就是完全一致的

>>> re.match(r"\W(.)\1\W", " ff ")
<re.Match object; span=(0, 4), match=' ff '>
>>> re.match("\\W(.)\\1\\W", " ff ")
<re.Match object; span=(0, 4), match=' ff '>

當需要匹配一個字符反斜槓,它必須在正則表達式中轉義。在原始字符串記法,就是 r"\"。否則就必須用 “\\”,來表示同樣的意思

>>> re.match(r"\\", r"\\")
<re.Match object; span=(0, 1), match='\\'>
>>> re.match("\\\\", r"\\")
<re.Match object; span=(0, 1), match='\\'>

寫一個詞法分析器

一個 詞法器或詞法分析器 分析字符串,並分類成目錄組。 這是寫一個編譯器或解釋器的第一步。

文字目錄是由正則表達式指定的。這個技術是通過將這些樣式合併爲一個主正則式,並且循環匹配來實現的

import collections
import re

Token = collections.namedtuple('Token', ['type', 'value', 'line', 'column'])

def tokenize(code):
    keywords = {'IF', 'THEN', 'ENDIF', 'FOR', 'NEXT', 'GOSUB', 'RETURN'}
    token_specification = [
        ('NUMBER',   r'\d+(\.\d*)?'),  # Integer or decimal number
        ('ASSIGN',   r':='),           # Assignment operator
        ('END',      r';'),            # Statement terminator
        ('ID',       r'[A-Za-z]+'),    # Identifiers
        ('OP',       r'[+\-*/]'),      # Arithmetic operators
        ('NEWLINE',  r'\n'),           # Line endings
        ('SKIP',     r'[ \t]+'),       # Skip over spaces and tabs
        ('MISMATCH', r'.'),            # Any other character
    ]
    tok_regex = '|'.join('(?P<%s>%s)' % pair for pair in token_specification)
    line_num = 1
    line_start = 0
    for mo in re.finditer(tok_regex, code):
        kind = mo.lastgroup
        value = mo.group()
        column = mo.start() - line_start
        if kind == 'NUMBER':
            value = float(value) if '.' in value else int(value)
        elif kind == 'ID' and value in keywords:
            kind = value
        elif kind == 'NEWLINE':
            line_start = mo.end()
            line_num += 1
            continue
        elif kind == 'SKIP':
            continue
        elif kind == 'MISMATCH':
            raise RuntimeError(f'{value!r} unexpected on line {line_num}')
        yield Token(kind, value, line_num, column)

statements = '''
    IF quantity THEN
        total := total + price * quantity;
        tax := price * 0.05;
    ENDIF;
'''

for token in tokenize(statements):
    print(token)

這個詞法器產生以下輸出

Token(type='IF', value='IF', line=2, column=4)
Token(type='ID', value='quantity', line=2, column=7)
Token(type='THEN', value='THEN', line=2, column=16)
Token(type='ID', value='total', line=3, column=8)
Token(type='ASSIGN', value=':=', line=3, column=14)
Token(type='ID', value='total', line=3, column=17)
Token(type='OP', value='+', line=3, column=23)
Token(type='ID', value='price', line=3, column=25)
Token(type='OP', value='*', line=3, column=31)
Token(type='ID', value='quantity', line=3, column=33)
Token(type='END', value=';', line=3, column=41)
Token(type='ID', value='tax', line=4, column=8)
Token(type='ASSIGN', value=':=', line=4, column=12)
Token(type='ID', value='price', line=4, column=15)
Token(type='OP', value='*', line=4, column=21)
Token(type='NUMBER', value=0.05, line=4, column=23)
Token(type='END', value=';', line=4, column=27)
Token(type='ENDIF', value='ENDIF', line=5, column=4)
Token(type='END', value=';', line=5, column=9)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章