Python 旋風之旅—字符串操作和正則表達式

Python 語言真正發光點之一是字符串的操作。 本節將介紹一些 Python 的內置字符串方法和格式設置操作,然後再繼續簡要介紹正則表達式極其有用的主題。 這種字符串操作模式通常會在數據科學工作的場景中出現,並且在這種情況下是 Python 的一大優勢。

可以使用單引號或雙引號(在功能上是等效)定義 Python 中的字符串:

x = 'a string'
y = "a string"
x == y
True

另外,可以使用三引號語法定義多行字符串:

multiline = """
one
two
three
"""

從這裏,讓我們快速瀏覽一些 Python 的字符串操作工具。

 

Python 中的簡單字符串處理

對於字符串的基本操作,使用 Python 的內置字符串方法非常方便。 如果您有使用 C 或其他低級語言進行工作的背景,則可能會發現 Python 方法的簡單性令人耳目一新。 前面我們介紹了Python的字符串類型和其中一些方法。 在這裏,我們將進一步深入

 

格式化字符串:調整大小寫

在 Python 中調整字符串的大小寫非常容易。 在這裏,我們以以下凌亂的字符串爲例,學習 upper(), lower(), capitalize(), title(), 和 swapcase()方法:

fox = "tHe qUICk bROWn fOx."

要將整個字符串轉換爲大寫或小寫,可以分別使用 upper() 或 lower() 方法:

fox.upper()
'THE QUICK BROWN FOX.'
fox.lower()
'the quick brown fox.'

常見的格式設置需求是僅將每個單詞的首字母大寫,或者將每個句子的首字母大寫。 這可以通過 title() 和 capitalize() 方法完成:

fox.title()
'The Quick Brown Fox.'
fox.capitalize()
'The quick brown fox.'

可以使用swapcase() 方法交換大小寫:

fox.swapcase()
ThE QuicK BrowN FoX.'

 

格式化字符串:添加和刪除空格

另一個常見的需求是從字符串的開頭或結尾刪除空格(或其他字符)。 刪除字符的基本方法是 strip() 方法,該方法從行的開頭和結尾刪除空格:

line = '         this is the content         '
line.strip()
'this is the content'

要僅刪除右側或左側的空格,請分別使用rstrip() 或lstrip():

line.rstrip()
'         this is the content'
line.lstrip()
'this is the content         '

要刪除空格以外的字符,可以將所需的字符傳遞給strip() 方法:

num = "000000000000435"
num.strip('0')
'435'

可以使用 center(),  ljust(), 和 rjust() 方法完成此操作的相反操作,即添加空格或其他字符。

例如,我們可以使用 center() 方法在給定數量的空格內將給定字符串居中:

line = "this is the content"
line.center(30)
'     this is the content      '

同樣,ljust() 和 rjust() 將在給定長度的空格內將字符串左對齊或右對齊:

line.ljust(30)
'this is the content           '
line.rjust(30)
'           this is the content'

所有這些方法還接受除空格外的任何其他字符用於填充。 例如:

'435'.rjust(10, '0')
'0000000435'

由於填充零是一種常見的需求,Python 還提供了 zfill(),這是一種用零右填充字符串的特殊方法:

'435'.zfill(10)
'0000000435'

 

查找和替換子字符串

如果要查找字符串中某個字符的出現,則 find()/rfind(), index()/rindex(), 和 replace() 方法是最好的內置方法。
find() 和 index() 非常相似,因爲它們搜索字符串中字符或子字符串的首次出現,並返回子字符串的索引:

line = 'the quick brown fox jumped over a lazy dog'
line.find('fox')
16
line.index('fox')

16

find() 和 index() 之間的唯一區別是沒有找到搜索字符串時它們的行爲。 find() 返回 -1,而index() 引發ValueError:

line.find('bear')
-1
line.index('bear')
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-21-4cbe6ee9b0eb> in <module>()
----> 1 line.index('bear')

ValueError: substring not found

相關的 rfind() 和 rindex() 的工作方式類似,只是它們從字符串的末尾而不是開頭搜索第一個匹配項:

line.rfind('a')
35

對於在字符串的開頭或結尾檢查子字符串的特殊情況,Python提供了startswith() 和endswith() 方法:

line.endswith('dog')
True
line.startswith('fox')
False

要更進一步,使用新的字符串替換給定的子字符串,可以使用replace() 方法。 在這裏,讓我們用“read”替換爲“brown”:

line.replace('brown', 'red')
'the quick red fox jumped over a lazy dog'

replace() 函數返回一個新字符串,並將替換所有匹配的字串:

line.replace('o', '--')
'the quick br--wn f--x jumped --ver a lazy d--g'

有關 replace() 函數的更靈活方法,請參見“靈活模式匹配:正則表達式”中對正則表達式的討論。

 

分割字串(Splitting and partitioning strings)

如果您想找到一個子字符串,然後根據其位置拆分字符串,則可以使用partition() 和/或 split() 方法。 兩者都將返回一個子字符串序列。

partition() 方法返回包含三個元素的元組:第一個分割點實例之前的子字符串,分割點本身以及之後的子字符串:

line.partition('fox')
('the quick brown ', 'fox', ' jumped over a lazy dog')

rpartition() 方法與此類似,但是從字符串的右邊搜索。

split() 方法可能更有用; 它查找拆分點的所有實例,並返回之間的子字符串。 缺省值是在任何空格上分割,返回字符串中各個單詞的列表:

line.split()
['the', 'quick', 'brown', 'fox', 'jumped', 'over', 'a', 'lazy', 'dog']

一個相關的方法是splitlines(),它分割換行符。 讓我們用Haiku來做這件事,Haiku 通常被認爲是17世紀詩人MatsuoBashō的作品:

haiku = """matsushima-ya
aah matsushima-ya
matsushima-ya"""

haiku.splitlines()
['matsushima-ya', 'aah matsushima-ya', 'matsushima-ya']

請注意,如果要與 split() 相反的功能,則可以使用 join() 方法,該方法返回從拆分點和可迭代對象構建的字符串:

'--'.join(['1', '2', '3'])
'1--2--3'

一種常見的模式是使用特殊字符“ \n”(換行符)將先前已拆分的行連接在一起,並恢復:

print("\n".join(['matsushima-ya', 'aah matsushima-ya', 'matsushima-ya']))
matsushima-ya
aah matsushima-ya
matsushima-ya

 

格式化字符串

在前面的方法中,我們學習瞭如何從字符串中提取值,以及如何將字符串本身轉換爲所需的格式。 字符串方法的另一種用途是將其他類型的值轉換爲字符串。 當然,始終可以使用 str() 函數來轉換成字符串。 例如:

pi = 3.14159
str(pi)
'3.14159'

對於更復雜的格式,您可能會傾向於使用“Python 基本語義:運算符”中概述的字符串運算:

"The value of pi is " + str(pi)
'The value of pi is 3.14159'

一種更靈活的方法是使用格式字符串,即帶有特殊標記(用花括號{}表示)的字符串,字符串格式的值將插入其中。 這是一個基本示例:

"The value of pi is {}".format(pi)
'The value of pi is 3.14159'

在{}標記內,您還可以包含有關您希望在此處顯示的內容的信息。 如果包含數字,它將引用要插入的參數的索引:

"""First letter: {0}. Last letter: {1}.""".format('A', 'Z')
'First letter: A. Last letter: Z.'

如果包含字符串,它將作爲關鍵字參數的鍵:

"""First letter: {first}. Last letter: {last}.""".format(last='Z', first='A')
'First letter: A. Last letter: Z.'

最後,對於數字輸入,可以包括格式代碼,這些代碼控制如何將值轉換爲字符串。 例如,要將數字打印爲小數點後三位數的浮點數,可以使用以下格式:

"pi = {0:.3f}".format(pi)
'pi = 3.142'

如前所述,此處的“ 0”是指要插入的值的索引。 “:”標記將遵循格式代碼。 “ .3f”表示所需的精度:小數點後三位數,浮點格式。

這種格式化非常靈活,此處的示例幾乎不涉及可用的格式設置選項。 有關這些格式字符串的語法的更多信息,請參見Python的在線文檔的“格式規範”部分。

 

靈活模式匹配:正則表達式

Python的 str 類型的方法提供了一套功能強大的工具,用於格式化,分割和處理字符串數據。 但是 Python 的內置正則表達式模塊中提供了更強大的工具。 正則表達式是一個巨大的主題。 涉及該主題的書籍夠寫整本書(包括Jeffrey E.F. Friedl的Mastering Regular Expressions,第3版),因此僅靠一個小節就很難做到欣賞他的強大功能。

我的目標是讓您瞭解可以使用正則表達式解決哪些類型的問題,以及有關如何在 Python 中使用它們的基本概念。我將在“正則表達式的更多資源” 中給出更多的學習建議。

從根本上來說,正則表達式是字符串中靈活的模式匹配的一種方式。 如果您經常使用命令行,則可能熟悉這種帶有“ *”字符(用作通配符)的靈活匹配。 例如,我們可以使用“ *”通配符來匹配所有IPython筆記(即擴展名爲.ipynb的文件),其文件名中帶有“ Python”:

在 jupyter notebook 中執行命令:

!ls *Python*.ipynb
01-How-to-Run-Python-Code.ipynb 02-Basic-Python-Syntax.ipynb

正則表達式將這種“通配符”概念概括爲各種靈活的字符串匹配語法。Python的正則表達式接口包含在內置的 re 模塊中; 作爲一個簡單的示例,讓我們使用它來複制字符串 split() 方法的功能:

import re
regex = re.compile('\s+')
regex.split(line)
['the', 'quick', 'brown', 'fox', 'jumped', 'over', 'a', 'lazy', 'dog']

在這裏,我們首先編譯了一個正則表達式,然後使用它來分割字符串。 就像 Python 的 split() 方法返回空格之間的所有子字符串的列表一樣,正則表達式 split() 方法返回與輸入模式匹配的所有子字符串的列表。

在這種情況下,輸入爲 "\s+": "\s" 是與任何空格(空格,製表符,換行符等)匹配的特殊字符,而"+"是指示一個或多個它前面的實體。 因此,該正則表達式匹配由一個或多個空格組成的任何子字符串。

這裏的 split() 方法基本上是基於此模式匹配行爲的便捷方法; 更基本的是 match() 方法,它將告訴您字符串的開頭是否與模式匹配:

for s in ["     ", "abc  ", "  abc"]:
    if regex.match(s):
        print(repr(s), "matches")
    else:
        print(repr(s), "does not match")
'     ' matches
'abc  ' does not match
'  abc' matches

像split()一樣,也有類似的便利例程來查找第一個匹配項(例如str.index()或str.find())或查找並替換(例如str.replace())。 我們將再次使用之前的字串:

line = 'the quick brown fox jumped over a lazy dog'

這樣,我們可以看到regex.search() 方法的操作方式與str.dex()或str.find()相似:

line.index('fox')
16
regex = re.compile('fox')
match = regex.search(line)
match.start()
16

同樣,regex.sub()方法的操作與str.replace()的操作非常相似:

line.replace('fox', 'BEAR')
'the quick brown BEAR jumped over a lazy dog'
regex.sub('BEAR', line)
'the quick brown BEAR jumped over a lazy dog'

稍加思考,其他字符串內置方法的操作也都可以轉換爲正則表達式。

 

一個更復雜的例子

但是,您可能會問,爲什麼要使用更復雜和冗長的正則表達式語法而不是更直觀和簡單的字符串方法? 因爲正則表達式提供了更大的靈活性。

在這裏,我們將考慮一個更復雜的示例:匹配電子郵件地址的常見任務。 我將首先簡單地編寫一個(有些難以理解的)正則表達式,然後逐步分析。 從這裏開始:

email = re.compile('\w+@\w+\.[a-z]{3}')

使用此方法,如果從文檔中得到一行如下的字串,我們可以快速提取類似於電子郵件地址的內容

text = "To email Guido, try [email protected] or the older address [email protected]."
email.findall(text)
['[email protected]', '[email protected]']

(請注意,這些地址是虛構的。 可能有更好的方法與Guido聯繫)

我們可以做進一步的操作,例如用另一個字符串替換這些電子郵件地址,比如在輸出中隱藏地址:

email.sub('[email protected]', text)
'To email Guido, try [email protected] or the older address [email protected].'

最後,請注意,如果您確實要匹配任何電子郵件地址,則前面的正則表達式太簡單了。 例如,它僅允許由字母數字字符組成的地址以幾個常見域後綴之一結尾。 因此,例如,此處使用的句點意味着我們僅找到地址的一部分:

email.findall('[email protected]')
['[email protected]']

這說明如果您不小心,正則表達式會變得多麼無情! 如果您在網上搜索,可以找到一些與所有有效電子郵件匹配的正則表達式建議,但請注意:它們比此處使用的簡單表達式要複雜得多!

 

正則表達式的基本語法

對於這一小節,正則表達式的語法太大了。 儘管如此,一定的熟悉度還有很長的路要走:我將在這裏介紹一些基本的構造,然後列出一些更完整的資源,您可以從中學習更多。 我希望以下快速入門可以使您有效地使用這些資源。

 

簡單字符串直接匹配

如果您用一個簡單的字符串或數字字符串構建正則表達式,它將與該字符串完全匹配:

regex = re.compile('ion')
regex.findall('Great Expectations')
['ion']

 

有特殊含義的字符

雖然簡單的字母或數字是直接匹配項,但是在正則表達式中有少數幾個具有特殊含義的字符。 他們是:

. ^ $ * + ? { } [ ] \ | ( )

我們將立刻討論其中一些的含義。 同時,您應該知道,如果您想直接匹配這些字符中的任何一個,則可以使用反斜槓(\)對其進行轉義:

regex = re.compile(r'\$')
regex.findall("the cost is $20")
['$']

r'\$'中的前綴 r 表示原始字符串; 在標準Python字符串中,反斜槓用於表示特殊字符。 例如,製表符卡用 "\t"表示:

print('a\tb\tc')
a	b	c

在原始字符串中是不會進行此類替換的:

print(r'a\tb\tc')
a\tb\tc

因此,每當在正則表達式中使用反斜槓時,最好使用原始字符串。

 

特殊字符可以匹配一組字符

就像正則表達式中的“ \”字符可以轉義特殊字符,將其轉換爲普通字符一樣,它也可以用於賦予普通字符特殊的含義。 這些特殊字符與指定的字符組匹配,我們之前已經見過它們。 在之前的電子郵件地址正則表達式中,我們使用了字符“ \w”,這是與任何字母數字字符匹配的特殊標記。 同樣,在簡單的split()示例中,我們還看到了“ \s”,這是一個特殊的標記,指示任何空白字符。

將它們放在一起,我們可以創建一個正則表達式,該正則表達式將匹配任意兩個之間具有空格的字母/數字:

regex = re.compile(r'\w\s\w')
regex.findall('the fox is 9 years old')
['e f', 'x i', 's 9', 's o']

此示例開始暗示正則表達式的強大功能和靈活性。

下表列出了其中一些常用的特殊含義的字符:

                            

這不是完整的列表或描述; 有關更多詳細信息,請參見Python的正則表達式語法文檔

 

方括號匹配自定義字符組

如果內置字符組對您來說不夠具體,則可以使用方括號指定您感興趣的任何字符集。例如,以下內容將匹配任何小寫的元音:

regex = re.compile('[aeiou]')
regex.split('consequential')
['c', 'ns', 'q', '', 'nt', '', 'l']

同樣,您可以使用破折號指定範圍:例如, "[a-z]"將匹配任何小寫字母,而“ [1-3]”將匹配“ 1”,“ 2”或“ 3“。 例如,您可能需要從文檔中提取特定的數字代碼,該數字代碼由大寫字母后跟數字組成。 您可以按照以下步驟進行操作:

regex = re.compile('[A-Z][0-9]')
regex.findall('1043879, G2, H6')
['G2', 'H6']

通配符匹配重複字符

如果您想在一個字符串匹配三個連續的字母數字字符,則可以編寫例如"\w\w\w"。 因爲這是一個普遍的需求,所以有一種特定的語法可以匹配重複項—帶數字的花括號:

regex = re.compile(r'\w{3}')
regex.findall('The quick brown fox')
['The', 'qui', 'bro', 'fox']

還有一些標記可用於匹配任意數量的重複-例如,“ +”字符將匹配其前面的一個或多個重複:

regex = re.compile(r'\w+')
regex.findall('The quick brown fox')
['The', 'quick', 'brown', 'fox']

下表是可用於正則表達式的重複標記的表:

     

牢記這些基礎,讓我們回到我們的電子郵件地址匹配器:

email = re.compile(r'\w+@\w+\.[a-z]{3}')

現在我們可以理解這是什麼意思:我們想要一個或多個字母數字字符 ("\w+") ,然後是at符號("@"), 然後是一個或多個字母數字字符(("\w+"),再加上句點 ("\."  –注意需要反斜槓轉義),後面緊跟三個小寫字母。

如果現在要修改此正則表達式以使奧巴馬的電子郵件地址匹配,則可以使用方括號表示法:

email2 = re.compile(r'[\w.]+@\w+\.[a-z]{3}')
email2.findall('[email protected]')
['[email protected]']

我們將“"\w+" 更改爲 "[\w.]+",因此我們將匹配任何字母數字字符或句點。 藉助這種更加靈活的表達方式,我們可以匹配更廣泛的電子郵件地址(儘管還不是全部-您是否可以識別此表達方式的其他缺陷?)。

 

括號表示要提取的組

對於複合正則表達式(例如我們的電子郵件匹配器),我們通常希望提取其部分而不是匹配的完全。 可以使用括號將結果分組:

email3 = re.compile(r'([\w.]+)@(\w+)\.([a-z]{3})')
text = "To email Guido, try [email protected] or the older address [email protected]."
email3.findall(text)
[('guido', 'python', 'org'), ('guido', 'google', 'com')]

如我們所見,該分組實際上提取了電子郵件地址的子部分列表。

我們可以更進一步,使用"(?P<name> )" 語法,命名提取的部分,在這種情況下,可以將分組提取爲Python字典:

email4 = re.compile(r'(?P<user>[\w.]+)@(?P<domain>\w+)\.(?P<suffix>[a-z]{3})')
match = email4.match('[email protected]')
match.groupdict()
{'domain': 'python', 'suffix': 'org', 'user': 'guido'}

結合這些想法(以及我們此處未介紹的一些強大的regexp語法),您可以靈活,快速地從Python的字符串中提取信息。

 

正則表達式的更多資源

上面的討論只是對這個大話題的簡明介紹(而且還遠遠沒有包括全部知識)。 如果您想了解更多信息,我建議以下資源:

  • Python 的 re 包文檔:我發現我會很快忘記每次使用正則表達式時都該如何使用它們。 既然我已經掌握了基礎知識,那麼我發現此頁面是一個非常有價值的資源,可以用來回憶起每個特定字符或序列在正則表達式中的含義。
  • Python的正式正則表達式HOWTO:Python中正則表達式的一種更具敘述性的方法。
  • 精通正則表達式(OReilly,2006年):是一本有關該主題的500多頁的書。 如果您想要真正完整地處理此主題,這是爲您準備的資源。

有關大規模使用字符串操作和正則表達式的一些示例,請參見Pandas:面向列的標籤數據,我們將在通過 Pandas包中的字符串數據表應用這些類型的表達式。

 

本文來自翻譯如下文章,僅用於學習

原文:

https://nbviewer.jupyter.org/github/jakevdp/WhirlwindTourOfPython/blob/master/14-Strings-and-Regular-Expressions.ipynb

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章