微信公眾號:學點啥玩點啥
小白友好型
1#第7章 模式匹配與正則表達式
2'''
3正則表達式, 簡稱為 regex, 是文本模式的描述方法。
4'''
5#7.2.1創建正則表達式對象
6'''
7python中所有正則表達式的函數都在re模塊中,向 re.compile()傳入一個字符串值,
8表示正則表達式,它將返回一個 Regex 模式對象(或者就簡稱為 Regex 對象)。
9'''
10import re
11phonenumregex = re.compile(r"\d\d\d-\d\d\d-\d\d\d\d")
12
13#7.2.2匹配regex對象
14'''
15Regex 對象的 search()方法查找傳入的字符串, 尋找該正則表達式的所有匹配。如果
16字符串中沒有找到該正則表達式模式, search()方法將返回 None。如果找到了該模式,
17search()方法將返回一個 Match 對象。Match 對象有一個 group()方法,它返回被
18查找字符串中實際匹配的文本(稍後我會解釋分組)。
19'''
20
21mo = phonenumregex.search("my number is 415-555-4242.")
22print(mo.group())
23
24#7.3用正則表達式匹配更多模式
25#7.3.1利用括號分組
26'''
27正則表達式字符串中的第一對括號是第 1 組。第二對括號是第 2 組。向 group()匹配對
28象方法傳入整數 1 或 2, 就可以取得匹配文本的不同部分。 向 group()方法傳入 0 或
29不傳入參數, 將返回整個匹配的文本。如果想要一次就獲取所有的分組, 請使用groups()
30方法, 注意函數名的複數形式。
31'''
32import re
33phonenumregex = re.compile(r"(\d\d\d)-(\d\d\d)-(\d\d\d\d)")
34mo = phonenumregex.search("my number is 415-555-4242.")
35print(mo.group())
36print(mo.group(1))
37print(mo.group(2))
38print(mo.group(3))
39print(mo.groups())
40
41'''
42括號在正則表達式中有特殊的含義, 但是如果你需要在文本中匹配括號, 怎麼辦?例如,
43你要匹配的電話號碼, 可能將區號放在一對括號中。在這種情況下, 就需要用倒斜槓對
44(和)進行字符轉義。
45'''
46phoneNumRegex = re.compile(r'(\(\d\d\d\)) (\d\d\d-\d\d\d\d)')
47mo = phoneNumRegex.search('My phone number is (415) 555-4242.')
48print(mo.group(1))
49
50#7.3.2用管道匹配多個分組
51'''
52字符|稱為「管道」。希望匹配許多表達式中的一個時, 就可以使用它。例如,正則表達式
53r'Batman|Tina Fey'將匹配'Batman'或'Tina Fey'。如果 Batman 和 Tina Fey 都出
54現在被查找的字符串中, 第一次出現的匹配文本將作為 Match 對象返回。
55'''
56hero = re.compile(r"Batman|Tina Fey")
57mo1 = hero.search('Batman and Tina Fey.')
58mo2 = hero.search('Tina Fey and Batman.')
59print(mo1.group())
60print(mo2.group())
61
62'''
63也可以使用管道來匹配多個模式中的一個, 作為正則表達式的一部分。例如,假設你希望
64匹配'Batman'、 'Batmobile'、 'Batcopter'和'Batbat'中任意一個。因為所有這些字
65符串都以 Bat 開始, 所以如果能夠只指定一次前綴, 就很方便。
66'''
67batregex = re.compile(r"Bat(man|mobile|copter|bat)")
68mo = batregex.search('Batmobile lost a wheel')
69print(mo.group())
70print(mo.group(1))
71
72#7.3.3用問號實現可選匹配
73'''
74有時候, 想匹配的模式是可選的。就是說, 不論這段文本在不在, 正則表達式都會認為匹配。
75字符?表明它前面的分組在這個模式中是可選的。
76'''
77batregex = re.compile(r"Bat(wo)?man")
78mo1 = batregex.search('The Adventures of Batman')
79mo2 = batregex.search('The Adventures of Batwoman')
80print(mo1.group())
81print(mo2.group())
82
83#7.3.4用星號匹配零次或多次
84'''
85*(稱為星號)意味著「匹配零次或多次」,即星號之前的分組,可以在文本中出現任意次。
86它可以完全不存在,或一次又一次地重複。
87'''
88batRegex = re.compile(r"Bat(wo)*man")
89mo1 = batRegex.search('The Adventures of Batman')
90mo2 = batRegex.search('The Adventures of Batwoman')
91mo3 = batRegex.search('The Adventures of Batwowowowoman')
92print(mo1.group())
93print(mo2.group())
94print(mo3.group())
95
96#7.3.5用加號匹配一次或多次
97'''
98*意味著「匹配零次或多次」, +(加號) 則意味著「匹配一次或多次」。 星號不要求分組出現
99在匹配的字符串中, 但加號不同, 加號前面的分組必須「至少出現一次」。這不是可選的。
100'''
101batRegex = re.compile(r'Bat(wo)+man')
102mo1 = batRegex.search('The Adventures of Batwoman')
103mo2 = batRegex.search('The Adventures of Batwowowowoman')
104mo3 = batRegex.search('The Adventures of Batman')
105print(mo1.group())
106print(mo2.group())
107print(mo3)
108'''
109正則表達式 Bat(wo)+man 不會匹配字符串'The Adventures of Batman',因為加號要求 wo
110至少出現一次。如果需要匹配真正的加號字符, 在加號前面加上倒斜槓實現轉義: \+。
111'''
112
113#7.3.6用花括號匹配特定次數
114'''
115如果想要一個分組重複特定次數,就在正則表達式中該分組的後面,跟上花括號包圍的數字。
116例如,正則表達式(Ha){3}將匹配字符串'HaHaHa',但不會匹配'HaHa',因為後者只重複了
117(Ha)分組兩次。除了一個數字,還可以指定一個範圍,即在花括號中寫下一個最小值、一個
118逗號和一個最大值。例如,正則表達式(Ha){3,5}將匹配'HaHaHa'、 'HaHaHaHa'和
119'HaHaHaHaHa'。也可以不寫花括號中的第一個或第二個數字, 不限定最小值或最大值。例如,
120(Ha){3,}將匹配 3 次或更多次實例, (Ha){,5}將匹配 0 到 5 次實例。
121'''
122haRegex = re.compile(r"(Ha){3}")
123mo1 = haRegex.search('HaHaHa')
124mo2 = haRegex.search('Ha')
125print(mo1.group())
126print(mo2)
127
128#7.4貪心和非貪心匹配
129'''
130Python 的正則表達式默認是「貪心」 的, 這表示在有二義的情況下,它們會儘可能匹配最長
131的字符串。花括號的「非貪心」 版本匹配儘可能最短的字符串,即在結束的花括號後跟著一個
132問號。
133'''
134greedyHaRegex = re.compile(r"(Ha){3,5}")
135mo1 = greedyHaRegex.search('HaHaHaHaHa')
136print(mo1.group())
137nongreedyHaRegex = re.compile(r"(Ha){3,5}?")
138mo2 = nongreedyHaRegex.search('HaHaHaHaHa')
139print(mo2.group())
140
141
142#7.5findall()方法
143'''
144除了search方法外, Regex對象也有一個findall()方法。search()將返回一個Match對象,
145包含被查找字符串中的「第一次」 匹配的文本,而 findall()方法將返回一組字符串,包含被
146查找字符串中的所有匹配。
147'''
148phoneNumRegex = re.compile(r'\d\d\d-\d\d\d-\d\d\d\d')
149mo = phoneNumRegex.search('Cell: 415-555-9999 Work: 212-555-0000')
150print(mo.group())
151
152mo1 = phoneNumRegex.findall('Cell: 415-555-9999 Work: 212-555-0000')
153print(mo1)
154'''
155作為 findall()方法的返回結果的總結,請記住下面兩點:
1561. 如果調用在一個沒有分組的正則表達式上, 例如\d\d\d-\d\d\d-\d\d\d\d, 方法
157findall()將返回一個匹配字符串的列表, 例如['415-555-9999', '212-555-0000']。
1582. 如果調用在一個有分組的正則表達式上, 例如(\d\d\d)-(\d\d\d)-(\d\d\d\d), 方
159法 findall()將返回一個字符串的元組的列表(每個分組對應一個字符串), 例如[('415',
160'555', '1122'), ('212', '555', '0000')]。
161'''
162
163#7.6字符分類
164'''
165縮寫字符分類 表示
166\d 0 到 9 的任何數字
167\D 除 0 到 9 的數字以外的任何字符
168\w 任何字母、數字或下劃線字符(可以認為是匹配「單詞」字符)
169\W 除字母、數字和下劃線以外的任何字符
170\s 格、制表符或換行符(可以認為是匹配「空白」字符)
171\S 除空格、制表符和換行符以外的任何字符
172'''
173xmasRegex = re.compile(r'\d+\s\w+')
174xmasRegex.findall('12 drummers, 11 pipers, 10 lords, 9 ladies, 8 maids, 7\
175swans, 6 geese, 5 rings, 4 birds, 3 hens, 2 doves, 1 partridge')
176
177#7.7建立自己的字符分類
178'''
179有時候你想匹配一組字符, 但縮寫的字符分類(\d、 \w、 \s 等) 太寬泛。你可
180以用方括號定義自己的字符分類。例如, 字符分類[aeiouAEIOU]將匹配所有元音字
181符, 不論大小寫。
182'''
183vowelRegex = re.compile(r'[aeiouAEIOU.]')
184v = vowelRegex.findall('RoboCop eats baby food. BABY FOOD.')
185print(v)
186'''
187也可以使用短橫表示字母或數字的範圍。例如, 字符分類[a-zA-Z0-9]將匹配所
188有小寫字母、 大寫字母和數字。
189請注意,在方括號內,普通的正則表達式符號不會被解釋。這意味著,你不需
190要前面加上倒斜槓轉義.、 *、 ?或()字符。例如,字符分類將匹配數字 0 到 5 和一個
191句點。你不需要將它寫成[0-5\.]。
192通過在字符分類的左方括號後加上一個插入字符(^), 就可以得到「非字符類」。
193非字符類將匹配不在這個字符類中的所有字符。
194'''
195vowelRegex = re.compile(r'[^aeiouAEIOU.]')
196v = vowelRegex.findall('RoboCop eats baby food. BABY FOOD.')
197print(v)
198
199#7.8插入字符和美元符號
200'''
201可以在正則表達式的開始處使用插入符號(^),表明匹配必須發生在被查找文
202本開始處。類似地,可以再正則表達式的末尾加上美元符號($),表示該字符串必
203須以這個正則表達式的模式結束。可以同時使用^和$,表明整個字符串必須匹配該
204模式,也就是說,只匹配該字符串的某個子集是不夠的。
205'''
206beginsWithHello = re.compile(r'^Hello')
207print(beginsWithHello.search('Hello world!'))
208print(beginsWithHello.search('He said hello.'))
209'''
210正則表達式 r'\d$'匹配以數字 0 到 9 結束的字符串。
211'''
212endsWithNumber = re.compile(r'\d$')
213print(endsWithNumber.search('Your number is 42'))
214endsWithNumber = re.compile(r'\d+$')
215print(endsWithNumber.search('Your number is 42'))
216'''
217正則表達式 r'^\d+$'匹配從開始到結束都是數字的字符串。
218'''
219wholeStringIsNum = re.compile(r'^\d+$')
220wholeStringIsNum.search('1234567890')
221wholeStringIsNum.search('12345xyz67890') == None
222wholeStringIsNum.search('12 34567890') == None
223
224#7.9通配字符
225'''
226在正則表達式中, .(句點)字符稱為「通配符」。它匹配除了換行之外的所有字符。
227'''
228atRegex = re.compile(r'.at')
229print(atRegex.findall('The cat in the hat sat on the flat mat.'))
230
231#7.9.1用點-星匹配所有字符
232'''
233回憶一下,句點字符表示「除換行外所有單個字符」,星號字符表示「前面字符出現零次或多次。
234'''
235nameRegex = re.compile(r"First Name: (.*) Last Name: (.*)")
236mo = nameRegex.search('First Name: Al Last Name: Sweigart')
237print(mo)
238print(mo.group())
239print(mo.group(1))
240print(mo.group(2))
241'''
242點-星使用「貪心」 模式:它總是匹配儘可能多的文本。要用「非貪心」 模式匹配所有文本,
243就使用點-星和問號。像和大括號一起使用時那樣, 問號告訴 Python 用非貪心模式匹配。
244'''
245nongreedyRegex = re.compile(r'<.*?>')
246mo = nongreedyRegex.search('<To serve man> for dinner.>')
247mo.group()
248
249nongreedyRegex = re.compile(r'<.*>')
250mo = nongreedyRegex.search('<To serve man> for dinner.>')
251mo.group()
252
253#7.9.2用句點字符匹配換行符
254'''
255點-星將匹配除換行外的所有字符。通過傳入 re.DOTALL 作為 re.compile()的第
256二個參數, 可以讓句點字符匹配所有字符, 包括換行字符。
257'''
258noNewlineRegex = re.compile('.*')
259noNewlineRegex.search('Serve the public trust.\nProtect the innocent.\
260\nUphold the law.').group()
261
262noNewlineRegex = re.compile('.*',re.DOTALL)
263noNewlineRegex.search('Serve the public trust.\nProtect the innocent.\
264\nUphold the law.').group()
265
266#7.10正則表達式符號複習
267'''
268?匹配零次或一次前面的分組。
269*匹配零次或多次前面的分組。
270+匹配一次或多次前面的分組。
271{n}匹配 n 次前面的分組。
272{n,}匹配 n 次或更多前面的分組。
273{,m}匹配零次到 m 次前面的分組。
274{n,m}匹配至少 n 次、至多 m 次前面的分組。
275{n,m}?或*?或+?對前面的分組進行非貪心匹配。
276^spam 意味著字符串必須以 spam 開始。
277spam$意味著字符串必須以 spam 結束。
278.匹配所有字符,換行符除外。
279\d、 \w 和\s 分別匹配數字、單詞和空格。
280\D、 \W 和\S 分別匹配出數字、單詞和空格外的所有字符。
281[abc]匹配方括號內的任意字符(諸如 a、 b 或 c)。
282[^abc]匹配不在方括號內的任意字符。
283'''
284
285#7.11不區分大小寫的匹配
286'''
287要讓正則表達式不區分大小寫,可以向 re.compile()傳入 re.IGNORECASE 或 re.I,
288作為第二個參數。
289'''
290robocop = re.compile(r'robocop', re.I)
291robocop.search('RoboCop is part man, part machine, all cop.').group()
292
293#7.12用sub()方法替換字符串
294'''
295正則表達式不僅能找到文本模式, 而且能夠用新的文本替換掉這些模式。Regex
296對象的 sub()方法需要傳入兩個參數。第一個參數是一個字符串, 用於取代發現的匹
297配。第二個參數是一個字符串,即正則表達式。sub()方法返回替換完成後的字符串。
298'''
299namesRegex = re.compile(r'Agent \w+')
300namesRegex.sub('CENSORED', 'Agent Alice gave the secret documents to Agent Bob.')
301
302#7.13管理複雜的正則表達式
303'''
304如果要匹配的文本模式很簡單, 正則表達式就很好。但匹配複雜的文本模式,可能需要長的、費解
305的正則表達式。你可以告訴 re.compile(), 忽略正則表達式字符串中的空白符和注釋,從而緩解
306這一點。 要實現這種詳細模式, 可以向 re.compile()傳入變量 re.VERBOSE, 作為第二個參數。
307'''
308phoneRegex = re.compile(r'''(
309 (\d{3}|\(\d{3}\))? # area code
310 (\s|-|\.)? # separator
311 \d{3} # first 3 digits
312 (\s|-|\.) # separator
313 \d{4} # last 4 digits
314 (\s*(ext|x|ext.)\s*\d{2,5})? # extension
315 )''', re.VERBOSE)
316'''
317請注意, 前面的例子使用了三重引號('"), 創建了一個多行字符串。這樣就可以
318將正則表達式定義放在多行中, 讓它更可讀。
319正則表達式字符串中的注釋規則, 與普通的 Python 代碼一樣: #符號和它後面直
320到行末的內容, 都被忽略。而且, 表示正則表達式的多行字符串中, 多餘的空白字符
321也不認為是要匹配的文本模式的一部分。這讓你能夠組織正則表達式, 讓它更可讀。
322'''
323
324#7.14組合使用 re.IGNOREC ASE、 re.DOTALL 和 re.VERBOSE
325someRegexValue = re.compile('foo', re.IGNORECASE | re.DOTALL | re.VERBOSE)