閒話python 49: 淺談正則表達式的使用

如果接到一個任務,需要分析程序運行的日誌文件,並從其中提取相關信息做成報表,那該怎麼實現呢?一種比較直觀的方式是對字符串進行切分,然後根據關鍵字定位到信息所在子串。這種方式雖然也可行,並在很多的小項目中也確實可用,但不得不指出,這種方式的健壯性和效率都很低。因為,日誌文件中的有些參量只是具有共同的特徵,卻並不是完全相同,而且那些不同的位置或許每次運行都不一樣。這時,我們大概會考慮到正則表達式,因為它正是描述一種匹配模式的特徵,具有通用型,並且編寫的代碼簡潔,可讀性更強,可維護性更好。正則表達式的所有語法自然是比較複雜的,但是在日常使用中,需要掌握的其實並不多,而且掌握了基本的使用方法之後,通過查看文檔以擴展更加高階的用法也更容易。本文就淺顯討論以下python中正則表達式的用法,運行環境為python3.7。

1. 模式Pattern

學習過“數據結構與算法”的同學對“模式”應該並不陌生,簡要來說,模式就是目標字符串特徵的描述。對於一個很長的“源字符串”,我們總是希望從中提取對我們有用的信息,這些有用的信息所在的子串就是我們的目標,而描述這些目標所具有的一致的特徵就是模式。在Linux或者MacOS中常用grep指令的朋友對模式應該比較熟悉,只是在python中與grep有所不同,模式並不能簡單由字符串表示,而必須使用專門的模塊進行封裝。python提供了re模塊專門處理正則表達式相關的功能。下面展示以下python中模式的創建方式,也演示以下模式所具有的幾個基本屬性。

import re

p = re.compile(r'(hello).+?(world).+?(?P<vname>\\d+)')
print('p={}'.format(p))
# 查看編譯表達式時所用的字符串
print('p.pattern={}'.format(p.pattern))
# 查看該模式串的匹配模式
print('p.flags={}'.format(p.flags))
# 查看錶達式中分組數量
print('p.groups={}'.format(p.groups))
#查看錶達式中有別名的組
print('p.groupindex={}'.format(p.groupindex))/<vname>

輸出結果如下:

p=re.compile('(hello).+?(world).+?(?P<vname>\\\\d+)')
p.pattern=(hello).+?(world).+?(?P<vname>\\d+)
p.flags=32
p.groups=3
p.groupindex={'vname': 3}/<vname>/<vname>

從以上演示可以看出,模式是編譯“r”字符串得到的結果,其中使用“()”描述了多個需要匹配的子串。通過查看模式的屬性可以發現,當前所定義的模式具有3個匹配項,也就是groups屬性的值。其中第3個匹配項的名字為vname,正如groupindex屬性所描述的。關於正則表達式的基本語法這裡就不進行仔細描述,大家可以一邊查看文檔,一邊在以上演示的基礎上進行修改。

2. 匹配Match

生成模式之後,就可以使用這個模式在“願字符串”中進行匹配。調用方式也非常簡單,直接使用模式對象進行調用即可,當然,也可以使用“re”模塊名進行調用,這裡只演示更加直觀清晰的前者。

m = p.match('hello nihao world shijie 2019hehehe')
print('m={}'.format(m))
print('m.string={}'.format(m.string))
print('m.re={}'.format(m.re))
print('m.group={}'.format(m.group()))
print('m.groups={}'.format(m.groups()))
print('m.groupdict={}'.format(m.groupdict()))
print('m.start={}'.format(m.start(3)))
print('m.end={}'.format(m.end(3)))
print('m.span={}'.format(m.span(3)))

輸出結果如下:

m=<re.match>
m.string=hello nihao world shijie 2019hehehe
m.re=re.compile('(hello).+?(world).+?(?P<vname>\\\\d+)')
m.group=hello nihao world shijie 2019
m.groups=('hello', 'world', '2019')
m.groupdict={'vname': '2019'}
m.start=25
m.end=29
m.span=(25, 29)/<vname>/<re.match>

從演示結果來看,該模式正確地從“源字符串”中分離出了我們所想要的三個匹配項。使用group函數可以查看匹配到的這三個匹配項的具體內容,使用groupdict函數可以查看那個帶參數名的匹配項的具體內容,使用span函數可以查看每個匹配項子串在“源字符串”中的起始和結束位置。看到這裡,那些使用字符串切分的方式提取日誌信息的朋友,或許會感嘆應該早點掌握正則表達式的用法。

3. 應用實例

在實際的開發中,處理上面演示的提取信息,還有一些情況下使用正則表達式也是可以處理的。這裡列出4中應用實例,需要處理字符串的朋友可以對號入座,找到自己的應用場景直接開開始正則表達式的使用。

(1) 搜索子串

判斷模式在“源字符串”中的存在性的時候,我們常常需要使用search方法,從以下的演示可以看出,search方法會匹配第一個符合“模式”的子串。

pp = re.compile(r'hello')
mm = pp.search('hello world, hello china')
print('mm.group={}'.format(mm.group()))

輸出結果如下:

mm.group=hello

(2) 分割字符串

有時,我們還需要使用固定的模式來切分“源字符串”,python的“re”模塊提供了split方法來完成這種需求。

pp = re.compile(r'h\\w+lo')
# 查找所有子串
s_all = pp.findall('hello world, hello china')
print('s_all={}'.format(s_all))
# 查找左右子串的迭代器
it = pp.finditer('hello world, hello china')
for s in it:
print('item={}'.format(s))

輸出結果如下:

s_all=['hello', 'hello']
item=<re.match>
item=<re.match>/<re.match>

(4)替換子串

如果需要對“源字符串”中符合“模式”的一些匹配項進行替換,那麼sub函數可以派上用場。只是更改匹配項之間的相對順序,那麼可以直接在sub方法調用時指定“r”字符串作為描述參數。

p = re.compile(r'(hello).+?(world)')
print('p.sub={}'.format(p.sub(r'\\2 \\1', 'hello world, hello china')))

輸出結果如下:

p.sub=world hello, hello china

如果需要對子串進行一些修改再替換,那麼可以使用函數封裝一次匹配的返回值。

def func(m):
return 'nihao shijie'
print('p.sub={}'.format(p.sub(func, 'hello world, hello china')))

輸出結果如下:

p.sub=nihao shijie, hello china

到此,關於python中使用正則表達式的簡要討論就結束了。學會使用正則表達式不僅能夠顯著提高字符串處理的開發效率,而且也有利於程序處理的魯棒性、通用型和可維護性。既然如此有利,而且可以偷懶,那麼作為一個以“懶惰即美德”為追求的程序開發者而言,何樂而不為?本文的notebook版文件在github上的cnbluegeek/notebook倉庫中共享,歡迎感興趣的朋友前往下載。


分享到:


相關文章: