ABSP第七章:[lesson25續:替換,更為複雜的正則表達式還有一個練手project]

說明

原文來自Automate the Boring Stuff with Python,原書為Creative Common License。本翻譯亦為CCL,但不得用於商業用途以及,如需轉載請註明出處。

25課拆成兩半翻譯,前半部分在這裡:ABSP第七章:[lesson25 - ?, +, *, 和 {} ,正則表達式句法以及貪婪/非貪婪匹配]

用sub()方法進行替換

講了這麼多查找,當然要講一下替換。regex對象的sub()方法接受兩個參數,第一個是用作替換的文本,第二個就是進行操作的原始文本。sub()方法會返回替換好的新文本。

試一試下面的代碼:

>>> namesRegex = re.compile(rAgent w+)>>> namesRegex.sub(CENSORED, Agent Alice gave the secret documents to Agent Bob.)CENSORED gave the secret documents to CENSORED.

有時候你會想用匹配的文字本身去替換。在sub()的第一個參數裡面可以用1,2或者3表示使用1,2或者3組作為替換文本。

就像上面代碼裡面,如果我們不是把Agent Alice整個替換成CENSORED,而是保留特工名字的第一個字母。要實現這個要求,你可以用Agent (w)w*作為正則,然後把r1***作為第一個參數交給sub()。這個參數的1會表示正則里第一組的內容,也就是特工名字的第一個字母。代碼如下,自己試一試:

>>> agentNamesRegex = re.compile(rAgent (w)w*)>>> agentNamesRegex.sub(r1****, Agent Alice told Agent Carol that AgentEve knew Agent Bob was a double agent.)A**** told C**** that E**** knew B**** was a double agent.

傳給sub()的參數r1***乍一看很像是給re.compile的參數。但是這裡並不是把它轉化成正則表達式。1也並不是什麼正則的元字元,僅僅是在sub()的參數中,1才代表第一組這個意思

管理複雜的正則表達式

在需要匹配的文本規則很簡單的時候正則表達式看起來還好。但是如果規則越來越複雜的話你就需要更長,更難讀懂的正則表達式去匹配。為了讓正則看起來更容易懂,添加註釋和換行是一個可行的辦法。要添加註釋的話,首先要給re.compile()方法傳入re.VERBOSE參數。

譯註:之前提到了(ABSP第七章:[lesson25 - ?, +, *, 和 {} ,正則表達式句法以及貪婪/非貪婪匹配]),re.DOTALL,re.I包括現在的re.VERBOSE實際上都是數值,彼此相加(或者作bit OR運算,符號是|)就可以同時開啟多個模式

現在就可以把下面這個難以讀懂的正則表達式:

phoneRegex = re.compile(r((d{3}|(d{3}))?(s|-|.)?d{3}(s|-|.)d{4}(s*(ext|x|ext.)s*d{2,5})?))

轉換成多行,具有注釋的表達式了:

phoneRegex = re.compile(r( (d{3}|(d{3}))? # area code (s|-|.)? # separator d{3} # first 3 digits (s|-|.) # separator d{4} # last 4 digits (s*(ext|x|ext.)s*d{2,5})? # extension ), re.VERBOSE)

上面的正則表達式除了re.VERBOSE這個參數以外,還有一個細節就是三重引號。在python裡面三重引號()可以創建一段包含換行的字元串(是真的用enter輸入的換行,不是
)。這樣的話正則表達式看起來就更加易懂了。

在正則表達式裡面作注釋的原則和在python代碼裡面作注釋是一樣的。#以後直到一行結束的所有內容都屬於注釋。而為了排版輸入的多餘空格也不會被用來形成正則表達式。

把re.IGNORECASE, re.DOTALL和re.VERBOSE組合起來

如前所述,如果你想用re.VERBOSE來往正則裡面添加備註,又想用re.IGNORECASE忽略大小寫怎麼辦呢?畢竟re.compile()只接受2個參數,第一個就是正則表達式文本,第二個就是上面這些模式,多一個都不行。要想同時使用多個模式,可以用bit OR運算(|)或者+來實現。實際上bit OR運算會更符合程序設計的初衷,看起來也挺正則的不是?

所以如果你想同時保證.匹配換行符,又要忽略大小寫:

>>> someRegexValue = re.compile(foo, re.IGNORECASE | re.DOTALL)

如果把VERBOSE也加進來:

>>> someRegexValue = re.compile(foo, re.IGNORECASE | re.DOTALL | re.VERBOSE)

涉及到位運算這種底層的東西實在是有些古早了,這種處理方式也是從早期python延續下來的。位運算不在這本書討論範圍之列,如果有興趣可以來這裡看看:nostarch.com/automatest

re.compile()還有很多其他的參數(也被稱作Flag)可以用,很多不大常用。有興趣可以自己去看,也在剛才的網址裡面。

練手項目:電話號碼和郵箱地址提取器

比如現在你有一個無聊的日常工作:在一個很長的文檔或者網頁裡面找出裡面的電話號碼和郵箱地址。如果是從上到下一點點找會很費事,但是如果你能弄一個程序檢索你剪貼板裡面的文本,從裡面提取電話號碼和郵箱的話,你只需要按CTRL+A選中全部文本,CTRL+C複製進剪貼板,然後運行你的程序,然後就可以把電話號碼和郵箱提取出來放在剪貼板裡面了。

當你開始新的項目的時候,很容易啥都不想就直接開始寫代碼。但是很多時候,先退一步看看整體設計是更明智的辦法。我的建議是先繪製一個整體的計劃,把需要實現的功能列出來。不要關注具體代碼怎麼寫,那些工筆描繪遲些再去考慮。現在先用粗線條打稿。

比如說,你的電話號碼和郵箱地址提取器需要有下面的這些功能:

  • 從剪貼板獲取文字
  • 從文字裡面找到所有的電話號碼和郵箱地址
  • 把這些提取的信息再貼回剪貼板

那麼針對這些功能,我們想一下用代碼怎麼實現。用下面的代碼可以實現我們的需求:

  • 使用pyperclip模塊進行字元串的複製和粘貼
  • 建立兩個正則表達式,一個匹配電話號碼,一個匹配郵件地址
  • 兩個正則表達式都需要找到所有匹配的文本,而不僅僅是第一個
  • 把匹配的結果整理好形成一個完整的單一字元串
  • 如果沒有找到符合條件的文本,要顯示一些特定的信息

上面的這些內容相當於我們這個項目的路線圖。當你寫代碼的時候,可以先集中於特定的步驟。每一步其實都是相對容易管理,並且用已有知識用python不難實現的。

第一步:建立一個電話號碼的正則表達式

註:接下來我們不用互動式編程,而是書寫代碼

新建一個文件,輸入下面代碼,命名為:phoneAndEmail.py

#! python3# phoneAndEmail.py - Finds phone numbers and email addresses on the clipboard.import pyperclip, rephoneRegex = re.compile(r( (d{3}|(d{3}))? # area code (s|-|.)? # separator (d{3}) # first 3 digits (s|-|.) # separator (d{4}) # last 4 digits (s*(ext|x|ext.)s*(d{2,5}))? # extension ), re.VERBOSE)# TODO: Create email regex.# TODO: Find matches in clipboard text.# TODO: Copy results to the clipboard.

代碼中的#TODO注釋是程序的骨架注釋,隨著逐漸書寫代碼可以把他們替換掉。

電話號碼首先是從可選性的區號開始,所以區號這一組()後面跟著一個?。區號可以是3個數字(d{3}),也可以是三個數字加一個()(也就是(d{3})),兩種情況要用|符號連起來。在這一行正則之後跟著的是注釋,提醒你以後看到這一行代碼的時候知道,ok這一段是表示區號。

因為電話號碼的分隔符可以是空格(s)橫線(-)或者點號(.),三者可以用|連起來。接下來的三行和前面差不多。#extension這一行的意思是以ext,x或者ext.開頭,包含2到5個數字的正則式子。

第2步:建立一個識別郵箱地址的正則表達式

除了電話號碼,你還需要一個識別郵箱的正則表達式,在phoneAndEmail.py原先代碼的基礎上進行修改

# TODO: Create email regex. emailRegex = re.compile(r( [a-zA-Z0-9._%+-]+ # username @ # @ symbol [a-zA-Z0-9.-]+ # domain name (.[a-zA-Z]{2,4}) # dot-something ), re.VERBOSE)

正則表達式裡面,第一行是用戶名的規則:由小寫及大寫字母,數字,.號,下劃線_,%,+和-組成。將這些用[ ]組合起來就是[a-zA-Z0-9._%+-].

郵箱的域名和用戶名之間有@作為分隔。域名和用戶名的規則稍有不同,只支持符號、數字、點號和-,寫成規則就是:[a-zA-Z0-9.-]。在這之後再就是一個.com部分。不同網站的域名可能彼此有區別,最後一塊仍舊有其規則:2到4個字母。

實際上的郵箱地址還有非常多細緻而不好理解的規則,上面的正則表達式不見得可以匹配所有的郵箱地址,但是對於大多數常規的地址來說已經足夠了。

第3步:從剪貼板里的文本找出全部符合的內容

現在你已經有了識別電話號碼和郵箱的正則表達式,接下來你可以讓python的re模塊來進行識別。pyperclip.paste()方法可以把剪貼板中的文本作為字元串交回來,拿到這個字元串再交給re的findall()方法去找符合條件的內容就行了。

# TODO: Find matches in clipboard text. text = str(pyperclip.paste()) matches = [] for groups in phoneRegex.findall(text): phoneNum = -.join([groups[1], groups[3], groups[5]]) if groups[8] != : phoneNum += x + groups[8] matches.append(phoneNum) for groups in emailRegex.findall(text): matches.append(groups[0])

正則表達式調用findall()方法返回了多個match到的結果,以tuple方式呈現。這些tuple每一個又都按照正則表達式所定義的組合併而成。0號組返回的是整個匹配字元串,所以要拿到郵箱地址全文就用這個tuple的0號。

而在matches=[]這裡,你定義了一個空的list去存儲接下來獲取的數據。matches.append(groups[0])把找到的郵箱地址一個一個存進這個列表,而在phoneRegex.findall()後面的循環里,你不僅僅是把0號組整個存進去。phoneRegex的正則規則裡面對電話號碼各個部分都定義了特定的組。這個程序裡面利用這個正則規則把電話號碼的各個部分實際信息提取出來,然後再形成一個標準的輸出形式:phoneNum。phoneNum由匹配結果的1號,3號,5號和8號(如果有的話)組成。為什麼是這4個部分?回去看一下正則怎麼寫的。

第4步:把匹配得來的結果形成一個完整的字元串放回剪貼板

現在你已經有了電話號碼和郵箱地址,存在了matches裡面,你想把他們放回剪貼板。直接放是不行的,pyperclip.copy()只接受字元串,不接受list。所以你需要把這些結果用join()方法合在一起。

為了方便理解,讓我們把你找到的任何匹配值都輸出出來。如果什麼都找不到,程序也需要給出提示。接下來對程序做如下修改:

# Copy results to the clipboard.if len(matches) > 0: pyperclip.copy(
.join(matches)) print(Copied to clipboard:) print(
.join(matches))else: print(No phone numbers or email addresses found.)

運行程序

比如現在你可以去nostarch.com/contactus.,按ctrl+a選中全部文字,ctrl+c複製,然後運行程序。順利的話應該能看到下面的結果:

譯註:上面的網站需要google認證所以……。如果訪問存在困難的話可以直接翻到文章末尾,我把網站內容拷貝下來貼在那裡了。

Copied to clipboard:800-420-7240415-863-9900415-863-9950info@nostarch.commedia@nostarch.comacademic@nostarch.comhelp@nostarch.com

其他類似應用的修改建議:

通過模式識別文本(並且還可以進一步用sub()進行替換)可以有非常多的應用,下面是一些例子:

  • 找到以http://或者https://作為開頭的網址
  • 把各種格式的日期進行統一(比如把3/14/2015, 03-14-2015或2015/3/14)按照一個格式輸出
  • 把網站中的敏感信息移除,比如身份證號,社保號,信用卡號碼等等
  • 找到一些常見的筆誤,比如單詞裡面多餘的空格,不小心重複了的單詞,或者在一句話末尾連著幾個一起的感嘆號。那些好鬼煩的啊!!!!

總結

電腦檢索文字快是快,但是它只能嚴格按照指令進行。正則表達式可以讓你更好的說明自己的意圖。實際上一些文字處理軟體和電子表格軟體裡面的查找與替換功能是支持正則表達式的。

python的re模塊讓你可以形成一個regex對象。正則對象具有多種方法,包括search(),返回的是單個match對象;findall()返回的是所有匹配的內容,sub()可以進行替換。

關於正則表達式還有其他的一些內容是這裡沒有說完的,你可以去python的官方文檔閱讀:docs.python.org/3/libra. 而正則表達式的這個教程網站也值得一去:regular-expressions.info

現在你已經是用正則處理和匹配文字的專家了,是時候開始學習如何讀取、寫入計算機硬碟裡面的文件了。

練習題

  1. 生成一個regex對象的方法是什麼?
  2. 為什麼要用raw字元串去生成正則表達式?
  3. search()方法返回的是什麼?
  4. 怎樣獲得match對象裡面符合正則表達式的實際文本內容?
  5. r(ddd)-(ddd-dddd)建立的正則表達式裡面0組是什麼?1組呢?2組呢?
  6. 括弧和點號在正則表達式裡面具有特殊意義,如果你要匹配的就是括弧或者點號要怎麼辦?
  7. findall()方法返回由字元串組成的列表,或者由tuple組成的列表。返回哪一種是由什麼決定的呢?
  8. |在正則表達式里是什麼意思?
  9. ?在正則表達式裡面代表哪兩個意思?
  10. +和*在正則表達式裡面的意思有什麼不同?
  11. {3}和{3,5}有什麼不同?
  12. d, w, s各自表示什麼?
  13. D, W, S表示什麼?
  14. 怎樣讓一個正則表達式對大小寫不敏感?
  15. .一般匹配什麼?如果給正則表達式re.compile()第二個參數傳遞了re.DOTALL呢?
  16. .*和.*?之間有什麼區別?
  17. 匹配所有數字和小寫字母的元字元怎麼寫?
  18. 如果numRegex = re.compile(rd+),numRegex.sub(X, 12 drummers, 11 pipers, five rings, 3 hens) 的返回值是什麼?
  19. re.VERBOSE作為re.compile()的第二個參數傳入以後會怎麼樣?
  20. 每3個數字加一個,這種數字書寫形式如何匹配?下面這些都要被匹配才可以:42;1,234;6,368,745。但是這些不可以被匹配:12,34,567(間隔不到3個);1234(沒有,間隔)
  21. 你要怎樣匹配姓Nakamoto的名字?我們假定名字在姓前面出現,姓和名第一個字母大寫。下面的這些要被匹配:Satoshi Nakamoto,Alice Nakamoto,Robocop Nakamoto;而下面這些不能被匹配:satoshi Nakamoto(名字首字母沒有大寫),Mr. Nakamoto(Mr.是稱號,類似的Dr. Ms. Mrs.之類的都不行),Nakamoto(沒寫名字),Satoshi nakamoto(姓第一個字母沒有大寫)
  22. 匹配這樣的句子,第一個詞是Alice, Bob, 或者 Carol,第二個詞是eats, pets, 或者 throws,第三個詞是apples, cats, 或者 baseballs。同時句子以?結尾。正則表達式應該是大小寫不敏感的。下面這些可以匹配:Alice eats apples. Bob pets cats. Carol throws baseballs. Alice throws Apples. BOB EATS CATS.,而這些不可以匹配:Robocop eats apples. ALICE THROWS FOOTBALLS. Carol eats 7 cats.

其他練手項目

除了剛才講的電話號碼、郵箱地址提取器,你還可以試試下面這些項目

強密碼檢測

寫一個函數,可以用正則表達式確定用作密碼的字元串足夠強。一個足夠強的密碼至少有8個字元長度,有大寫和小寫字母,至少一個數字。可能需要用幾個正則表達式來完成這個需求

用正則寫一個strip()函數

用正則寫一個函數,接收字元串作為參數,實現strip()的功能。如果參數只有字元串自己,那就把頭尾的空白符號去掉。如果還有其他字元作為第二個參數傳入,那就把這個字元從字元串里除去。

關於strip():Python String strip() Method


附錄:網站內容文本

Skip to main content

Home

Toggle navigation

Search form

Search

GO!

Topics

Arduino

Art & Design

General Computing

Hacking & Computer Security

Hardware / DIY

JavaScript

Kids

LEGO?

LEGO? MINDSTORMS?

Linux & BSD

Manga

Programming

Python

Science & Math

Scratch

System Administration

Early Access

Free ebook edition with every print book purchased from nostarch.com!

Shopping cart

0 Items Total: $0.00

User login

Log in

Create account

Contact Us

No Starch Press, Inc.

245 8th Street

San Francisco, CA 94103 USA

Phone: 800.420.7240 or +1 415.863.9900 (9 a.m. to 5 p.m., M-F, PST)

Fax: +1 415.863.9950

Reach Us by Email

General inquiries: info@nostarch.com

Media requests: media@nostarch.com

Academic requests: academic@nostarch.com (Please see this page for academic review requests)

Help with your order: info@nostarch.com

Reach Us on Social Media

Twitter

Facebook

Navigation

My account

Want sweet deals?

Sign up for our newsletter.

About Us | Jobs | Sales and Distribution | Rights | Media | Academic Requests | Conferences | Order FAQ | Contact Us | Write for Us | Privacy


推薦閱讀:

ABSP第8章: 練手項目2則

TAG:Python | 辦公自動化 | 正則表達式 |