天天看點

《Python核心程式設計(第3版)》——1.5 更長的正規表達式示例

本節書摘來自異步社群《python核心程式設計(第3版)》一書中的第1章,第1.5節,作者[美] wesley chun(衛斯理 春),孫波翔 李斌 李晗 譯,更多章節内容可以通路雲栖社群“異步社群”公衆号檢視。

我們現在将浏覽一個深入的示列,它以不同的方式使用正規表達式來操作字元串。首先是一些實際上生成用于操作随機數(但不是太随機)的代碼。示例1-5展示了gendata.py,這是一個生成資料集的腳本。盡管該程式隻是将簡單地将生成的字元串集顯示到标準輸出,但是該輸出可以很容易重定向到測試檔案。

示例1-5 用于正規表達式練習的資料生成器(gendata.py)

《Python核心程式設計(第3版)》——1.5 更長的正規表達式示例

該腳本生成擁有三個字段的字元串,由一對冒号或者一對雙冒号分隔。第一個字段是随機(32位)整數,該整數将被轉換為一個日期。下一個字段是一個随機生成的電子郵件位址。最後一個字段是一個由單橫線(-)分隔的整數集。

運作這段代碼,我們将獲得以下輸出(讀者将會從此獲益頗多),并将該輸出在本地另存為redata.txt檔案。

讀者或者可能會辨識出來,但是來自該程式的輸出是為正規表達式處理做準備的。後續将逐行解釋,我們将實作一些正規表達式來操作這些資料,以及為本章末尾的練習留下很多内容。

逐行解釋

第1~6行

在示例腳本中,需要使用多個子產品。由于多種原因,盡管我們小心翼翼地避免使用from-import語句(例如,很容易判斷一個函數來自哪個子產品,以及可能導緻本地子產品沖突等),我們還是選擇從這些子產品中僅導入特定的屬性,來幫助讀者僅專注于那些屬性,以及縮短每行代碼的長度。

第8行

tlds是一組進階域名集合,當需要随機生成電子郵件位址時,就可以從中随機選出一個。

第10~12行

每次執行gendata.py,就會生成第5行和第10行之間的輸出(該腳本對于所有需要随機整數的場景都使用random.randrange()函數)。對于每一行,我們選取所有可能範圍(0~231–1 [sys.maxint])中的随機整數,然後使用time.ctime()函數将該整數轉換為日期。python中的系統時間和大多數基于posix的計算機一樣,兩者都使用從“epoch”至今的秒數,epoch是指1970年1月1日格林威治時間的午夜。如果我們選擇一個32位整數,那麼該整數将表示從epoch到最大可能時間(即epoch後的232秒)之間的某個時刻。

第13~16行

僞造郵件位址的登入名長度為4~7個字元(是以使用randrange(4,8))。為了将它們放在一起,需要随機選擇4~7個小寫字母,将所有字母逐個連接配接成一個字元串。random.choice()函數的功能就是接受一個序列,然後傳回該序列中的一個随機元素。在該示例中,string.ascii_lowercase是字母表中擁有26個小寫字母的序列集合。

我們決定僞造電子郵件位址的主域名長度不能多于12個字元,但是至少和登入名一樣長。再一次使用随機的小寫字母,逐個字母來組合這個名字。

第17~18行

該腳本的關鍵部分就是将所有随機資料放入輸出行。先是資料字元串,然後是分隔符。然後将所有電子郵件位址通過登入名、“@”符号、域名和一個随機選擇的進階域名組合在一起。在最終的雙冒号之後,我們将使用用于表示初始時間的随機數字元串(日期字元串),後面跟着登入名和域名的長度,所有這些都由一個連字元分隔。

對于後續的練習,為正規表達式建立寬松和限制性的版本。建議讀者在一個簡短的應用中測試這些正規表達式,該應用利用之前所展示的示例檔案redata.txt(或者使用通過運作gendata.py生成的資料)。當做練習時,讀者将需要再次使用該資料。

在将正規表達式放入應用中之前,為了測試正規表達式,我們将導入re子產品,然後将redata.txt中的一個示例行賦給字元串變量data。如下所示,這些語句在所有展示的示例中都是常量。

在第一個示例中,我們将建立一個正規表達式來提取(僅僅)資料檔案redata.txt中每一行時間戳中一周的幾天。我們将使用下面的正規表達式。

<code>"^mon|^tue|^wed|^thu|^fri|^sat|^sun"</code>

該示例需要字元串以列出的7個字元串中的任意一個開頭(“^”正規表達式中的脫字元)。如果我們将該正規表達式“翻譯”成自然語言,讀起來就會像這樣:“字元串應當以“mon”,“tue”,. . . ,“sat”或者“sun”開頭。

換句話說,如果按照如下所示的方式對日期字元串分組,我們就可以使用一個脫字元來替換所有脫字元。

<code>"^(mon|tue|wed|thu|fri|sat|sun)"</code>

包覆字元串集的圓括号意思是:這些字元串中的一個将會有一次成功比對。這是我們一開始就使用的“友好的”正規表達式版本,該版本并沒有使用圓括号。如下所示,在這個修改過的正規表達式版本中,可以以子組的方式來通路比對字元串。

我們在該示例所實作的這個特性可能看起來并不是革命性的,但是在下一個示例或者作為正規表達式的一部分提供額外資料來實作字元串比對操作的任何地方,它确定有它的獨到之處,即使這些字元并不是你所感興趣字元的一部分。

以上兩個正規表達式都是非常嚴格的,尤其是要求一個字元串集。這可能在一個國際化的環境中并不能良好地工作,因為所在的環境中會使用當地的日期和縮寫。一個寬松的正規表達式将為:^w{3}。該正規表達式僅僅需要一個以三個連續字母數字字元開頭的字元串。再一次,将正規表達式轉換為正常的自然語言:脫字元^表示“作為起始”,w表示任意單個字母數字字元,{3}表示将會有3個連續的正規表達式副本,這裡使用{3}來修飾正規表達式。再一次,如果想要分組,就必須使用圓括号,例如^(w{3})。

注意,正規表達式^(w){3}是錯誤的。當{3}在圓括号中時,先比對三個連續的字母數字字元,然後表示為一個分組。但是如果将{3}移到外部,它就等效于三個連續的單個字母數字字元。

當我們通路子組1時,出現字母“u”的原因是子組1持續被下一個字元替換。換句話說,m.group(1)以字母“t”作為開始,然後變為“h”,最終被替換為“u”。這些是單個字母數字字元的三個獨立(并且重疊)分組,與一個包含三個連續字母數字字元的單獨分組相反。

在下一個(而且是最後)的示例中,我們将建立一個正規表達式來提取redata.txt每一行的末尾所發現的數字字段。

然而,在建立任何正規表達式之前,我們就意識到這些整數資料項位于資料字元串的末尾。這就意味着我們需要選擇使用搜尋還是比對。發起一個搜尋将更易于了解,因為我們确切知道想要查找的内容(包含三個整數的資料集),所要查找的内容不是在字元串的起始部分,也不是整個字元串。如果我們想要實作比對,就必須建立一個正規表達式來比對整個行,然後使用子組來儲存想要的資料。要展示它們之間的差别,就需要先執行搜尋,然後實作比對,以展示使用搜尋更适合目前的需要。

因為我們想要尋找三個由連字元分隔的整數,是以可以建立自己的正規表達式來說明這一需求:d+-d+-d+。該正規表達式的含義是,“任何數值的數字(至少一個)後面跟着一個連字元,然後是多個數字、另一個連字元,最後是一個數字集。”我們現在将使用search()來測試該正規表達式:

一個比對嘗試失敗了,為什麼呢?因為比對從字元串的起始部分開始,需要被比對的數值位于字元串的末尾。我們将不得不建立另一個正規表達式來比對整個字元串。但是可以使用惰性比對,即使用“.+”來表明一個任意字元集跟在我們真正感興趣的部分之後。

該正規表達式效果非常好,但是我們隻想要末尾的數字字段,而并不是整個字元串,是以不得不使用圓括号對想要的内容進行分組。

發生了什麼?我們将提取1171590364-6-8,而不僅僅是4-6-8。第一個整數的其餘部分在哪兒?問題在于正規表達式本質上實作貪婪比對。這就意味着對于該通配符模式,将對正規表達式從左至右按順序求值,而且試圖擷取比對該模式的盡可能多的字元。在之前的示例中,使用“.+”擷取從字元串起始位置開始的全部單個字元,包括所期望的第一個整數字段。d+僅僅需要一個數字,是以将得到“4”,其中.+比對了從字元串起始部分到所期望的第一個數字的全部内容:“thu feb 15 17:46:04 2007::[email protected]::117159036”,如圖1-2所示。

《Python核心程式設計(第3版)》——1.5 更長的正規表達式示例

其中的一個方案是使用“非貪婪”操作符“?”。讀者可以在“*”、“+”或者“?”之後使用該操作符。該操作符将要求正規表達式引擎比對盡可能少的字元。是以,如果在“.+”之後放置一個“?”,我們将獲得所期望的結果,如圖1-3所示。

《Python核心程式設計(第3版)》——1.5 更長的正規表達式示例

另一個實際情況下更簡單的方案,就是把“::”作為字段分隔符。讀者可以僅僅使用正則字元串strip(':: ')方法擷取所有的部分,然後使用strip('-')作為另一個橫線分隔符,就能夠擷取最初想要查詢的三個整數。現在,我們不想先選擇該方案,因為這就是我們如何将字元串放在一起,以使用gendata.py作為開始!

最後一個示例:假定我們僅想取出三個整數字段中間的那個整數。如下所示,這就是實作的方法(使用一個搜尋,這樣就不必比對整個字元串):-(d+)-。嘗試該模式,将得到以下内容。

本章幾乎沒有涉及正規表達式的強大功能,在有限的篇幅裡面我們不可能做到。然而,我們希望已經向讀者提供了足夠有用的介紹性資訊,使讀者能夠掌握這個強有力的工具,并融入到自己的程式設計技巧裡面。建議讀者閱讀參考文檔以擷取在python中如何使用正規表達式的更多細節。對于想要更深入研究正規表達式的讀者,建議閱讀由 jeffrey e. f. friedl.編寫的mastering regular expressions。