前言

正則表達式(稱為RE,或正則,或正則表達式模式)本質上是嵌入在Python中的一種微小的、高度專業化的編程語言,可通過 re 模塊獲得。 使用這種小語言,你可以為要匹配的可能字符串集指定規則;此集可能包含英語句子,電子郵件地址,TeX命令或你喜歡的任何內容。 然后,您可以詢問諸如“此字符串是否與模式匹配?”或“此字符串中的模式是否匹配?”等問題。 你還可以使用正則修改字符串或以各種方式將其拆分。
正則表達式模式被編譯成一系列字節碼,然后由用 C 編寫的匹配引擎執行。對於高級用途,可能需要特別注意引擎如何執行給定的正則,並將正則寫入以某種方式生成運行速度更快的字節碼。 本文檔未涉及優化,因為它要求你充分了解匹配引擎的內部結構。
正則表達式語言相對較小且受限制,因此並非所有可能的字符串處理任務都可以使用正則表達式完成。 還有一些任務 可以 用正則表達式完成,但表達式變得非常復雜。 在這些情況下,你最好編寫 Python 代碼來進行處理;雖然 Python 代碼比精心設計的正則表達式慢,但它也可能更容易理解。
源代碼參考 : Lib/re.py
這個模塊提供了與 Perl 語言類似的正則表達式匹配操作。
模式和被搜索的字符串既可以是 Unicode 字符串 (str) ,也可以是8位字節串 (bytes)。 但是,Unicode 字符串與8位字節串不能混用:也就是說,你不能用一個字節串模式去匹配 Unicode 字符串,反之亦然;類似地,當進行替換操作時,替換字符串的類型也必須與所用的模式和搜索字符串的類型一致。
正則表達式使用反斜杠字符 ('\') 來表示特殊形式或是允許在使用特殊字符時不引發它們的特殊含義。 這會與 Python 的字符串字面值中對相同字符出於相同目的的用法產生沖突;例如,要匹配一個反斜杠字面值,用戶可能必須寫成 '\\\\' 來作為模式字符串,因為正則表達式必須為 \\,而每個反斜杠在普通 Python 字符串字面值中又必須表示為 \\。 而且還要注意,在 Python 的字符串字面值中使用的反斜杠如果有任何無效的轉義序列,現在將會產生 DeprecationWarning 並將在未來改為 SyntaxError。 此行為即使對於正則表達式來說有效的轉義字符同樣會發生。
解決辦法是對於正則表達式樣式使用 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'。
-
*?,+?,?? -
'*','+',和'?'修飾符都是 貪婪的;它們在字符串進行盡可能多的匹配。有時候並不需要這種行為。如果正則式<.*>希望找到'<a> b <c>',它將會匹配整個字符串,而不僅是'<a>'。在修飾符之后添加?將使樣式以 非貪婪`方式或者 :dfn:`最小 方式進行匹配; 盡量 少 的字符將會被匹配。 使用正則式<.*?>將會僅僅匹配'<a>'。
- "{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')'。
-
不在集合范圍內的字符可以通過 取反 來進行匹配。如果集合首字符是
'^',所有 不 在集合內的字符將會被匹配,比如[^5]將匹配所有字符,除了'5',[^^]將匹配所有字符,除了'^'.^如果不在集合首位,就沒有特殊含義。 -
在集合內要匹配一個字符
']',有兩種方法,要么就在它之前加上反斜杠,要么就把它放到集合首位。比如,[()[\]{}]和[]()[{}]都可以匹配括號。
-
Unicode Technical Standard #18 里的嵌套集合和集合操作支持可能在未來添加。這將會改變語法,所以為了幫助這個改變,一個
FutureWarning將會在有多義的情況里被raise,包含以下幾種情況,集合由'['開始,或者包含下列字符序列'--','&&','~~', 和'||'。為了避免警告,需要將它們用反斜杠轉義。
在 3.7 版更改: 如果一個字符串構建的語義在未來會改變的話,一個
FutureWarning會raise。 -
-
| -
A|B, A 和 B 可以是任意正則表達式,創建一個正則表達式,匹配 A 或者 B. 任意個正則表達式可以用'|'連接。它也可以在組合(見下列)內使用。掃描目標字符串時,'|'分隔開的正則樣式從左到右進行匹配。當一個樣式完全匹配時,這個分支就被接受。意思就是,一旦 A 匹配成功, B 就不再進行匹配,即便它能產生一個更好的匹配。或者說,'|'操作符絕不貪婪。 如果要匹配'|'字符,使用\|, 或者把它包含在字符集里,比如[|].
-
(...) -
(組合),匹配括號內的任意正則表達式,並標識出組合的開始和結尾。匹配完成后,組合的內容可以被獲取,並可以在之后用
\number轉義序列進行再次匹配,之后進行詳細說明。要匹配字符'('或者')', 用\(或\), 或者把它們包含在字符集合里:[(],[)].
-
(?…) -
這是個擴展標記法 (一個
'?'跟隨'('並無含義)。'?'后面的第一個字符決定了這個構建采用什么樣的語法。這種擴展通常並不創建新的組合;(?P<name>...)是唯一的例外。 以下是目前支持的擴展。 -
(?aiLmsux) -
(
'a','i','L','m','s','u','x'中的一個或多個) 這個組合匹配一個空字符串;這些字符對正則表達式設置以下標記re.A(只匹配ASCII字符),re.I(忽略大小寫),re.L(語言依賴),re.M(多行模式),re.S(點dot匹配全部字符),re.U(Unicode匹配), andre.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匹配), andre.X(冗長模式)。(標記描述在 模塊內容 .)'a','L'and'u'作為內聯標記是相互排斥的, 所以它們不能結合在一起,或者跟隨'-'。 當他們中的某個出現在內聯組中,它就覆蓋了括號組內的匹配模式。在Unicode樣式中,(?a:...)切換為 只匹配ASCII,(?u:...)切換為Unicode匹配 (默認). 在byte樣式中(?L:...)切換為語言依賴模式,(?a:...)切換為 只匹配ASCII (默認)。這種方式只覆蓋組合內匹配,括號外的匹配模式不受影響。3.6 新版功能.
在 3.7 版更改: 符號
'a','L'和'u'同樣可以用在一個組合內。
-
(?P<name>…) -
(命名組合)類似正則組合,但是匹配到的子串組在外部是通過定義的 name 來獲取的。組合名必須是有效的Python標識符,並且每個組合名只能用一個正則表達式定義,只能定義一次。一個符號組合同樣是一個數字組合,就像這個組合沒有被命名一樣。
命名組合可以在三種上下文中引用。如果樣式是
(?P<quote>['"]).*?(?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'在 3.5 版更改: 添加定長組合引用的支持。
-
(?<!…) -
匹配當前位置之前不是
…的樣式。這個叫: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樣式匹配,將匹配'<user@host.com>'或'user@host.com',但不會匹配'<user@host.com',也不會匹配'user@host.com>'。
由 '\' 和一個字符組成的特殊序列在以下列出。 如果普通字符不是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
-
\W -
匹配任何不是單詞字符的字符。 這與
\w正相反。 如果使用了ASCII旗標,這就等價於[^a-zA-Z0-9_]。 如果使用了LOCALE旗標,則會匹配在當前區域設置中不是字母數字又不是下划線的字符。
-
\Z -
只匹配字符串尾。
絕大部分Python的標准轉義字符也被正則表達式分析器支持。:
\a \b \f \n \N \r \t \u \U \v \x \\
(注意 \b 被用於表示詞語的邊界,它只在字符集合內表示退格,比如 [\b] 。)
'\u', '\U' 和 '\N' 轉義序列只在 Unicode 模式中可被識別。 在 bytes 模式中它們會導致錯誤。 未知的 ASCII 字母轉義序列保留在未來使用,會被當作錯誤來處理。
八進制轉義包含為一個有限形式。如果首位數字是 0, 或者有三個八進制數位,那么就認為它是八進制轉義。其他的情況,就看作是組引用。對於字符串文本,八進制轉義最多有三個數位長。
在 3.3 版更改: 增加了 '\u' 和 '\U' 轉義序列。
在 3.6 版更改: 由 '\' 和一個ASCII字符組成的未知轉義會被看成錯誤。
在 3.8 版更改: 添加了 '\N{name}' 轉義序列。 與在字符串字面值中一樣,它擴展了命名 Unicode 字符 (例如 '\N{EM DASH}')。
模塊內容
模塊定義了幾個函數,常量,和一個例外。有些函數是編譯后的正則表達式方法的簡化版本(少了一些特性)。絕大部分重要的應用,總是會先將正則表達式編譯,之后在進行操作。
在 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.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>會使用命名組合name,(在(?P<name>…)語法中定義)\g<number>會使用數字組;\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('http://www.python.org')) http://www\.python\.org >>> 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) 的列號。
在 3.5 版更改: 添加了附加屬性。
-
正則表達式對象 (正則對象)
編譯后的正則表達式對象支持一下方法和屬性:
-
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'>3.4 新版功能.
-
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<id>)定義的命名符號組合和數字組合的字典。如果沒有符號組,那字典就是空的。
-
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<name>) 替換為相應組合的內容。在 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<name>…)語法, 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'3.6 新版功能.
-
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():] 'tony@tiger.net'
-
Match.span([group]) -
對於一個匹配 m , 返回一個二元組
(m.start(group), m.end(group))。 注意如果 group 沒有在這個匹配中,就返回(-1, -1)。group 默認為0,就是整個匹配。
-
Match.lastindex -
捕獲組的最后一個匹配的整數索引值,或者
None如果沒有匹配產生的話。比如,對於字符串'ab',表達式(a)b,((a)(b)), 和((ab))將得到lastindex == 1, 而(a)(b)會得到lastindex == 2。
-
Match.lastgroup -
最后一個匹配的命名組名字,或者
None如果沒有產生匹配的話。
在 3.7 版更改: 添加了對 copy.copy() 和 copy.deepcopy() 的支持。匹配對象被看作是原子性的。
正則表達式例子
檢查對子
在這個例子里,我們使用以下輔助函數來更好地顯示匹配對象:
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',)>"
要找出對子由什么牌組成,開發者可以按照下面的方式來使用匹配對象的 group() 方法:
>>> pair = re.compile(r".*(.).*\1") >>> 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() 格式符和正則表達式大致相同的映射。
|
|
正則表達式 |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
從文件名和數字提取字符串
/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() 將字符串用參數傳遞的樣式分隔開。這個方法對於轉換文本數據到易讀而且容易修改的數據結構,是很有用的,如下面的例子證明。
首先,這里是輸入。 它通常來自一個文件,這里我們使用三重引號字符串語法
>>> 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; ''' 