本文為《爬著學(xué)Python》系列第六篇文章。
本文也算是系列教程的第二篇不規(guī)則更新。

在討論Python正則表達式之前,我想先說說Python的原始字符串(Raw String)。我建議各位在Python正則式的pattern中一律使用原始字符串,本文的目的就在于討論為什么要用原始字符串以及使用過程中需要注意的事項。
什么是原始字符串
原始字符串的Python提供的一種內(nèi)置的用于簡化轉(zhuǎn)義的字符串形式。用法很簡單,在定義字符串時加個r或者R就可以r'string'。
>>> a = r'hello'
>>> type(a)
<class 'str'>
可以看到,即使我們把a定義成原始字符串,但是它的類型依然是字符串。而且不僅如此
>>> a = r'hello'
>>> b = 'hello'
>>> a is b
True
我們看到如果我們再定義一個普通字符串,他們在內(nèi)存中引用的是同一個對象。目前,我們基本可以推測,所謂原始字符串,其實質(zhì)就是普通字符串。
既然一樣,那么問題來了,那要它何用呢?
原始字符串的作用
可以說,原始字符串就是為了Python正則表達式而存在的,可以說也是Python的語法糖。我們先試試看最常見的例子,如何在Python正則中匹配\這個符號呢。下文中除特殊說明待處理的原字符串都默認設(shè)置為a\b\/c/d
>>> import re
>>> BASE = 'a\b\/c/d'
>>> print(BASE)
\/c/d
我們注意到輸出這個字符串時,\b作為轉(zhuǎn)義字符正常工作著,本來應(yīng)該輸出的a被退格了。
現(xiàn)在我們要嘗試匹配BASE中的\該如何做呢?
>>> re.findall('\', BASE)
File "<stdin>", line 1
re.findall('\', a)
^
SyntaxError: EOL while scanning string literal
上面的這個做法顯然是在賣萌,程序是必然會報錯的,原因在于\'會被轉(zhuǎn)義為',而且我們知道
>>> re.findall('', BASE)
['', '', '', '', '', '', '', '']
也就是說,轉(zhuǎn)義后的'是作為字符串的內(nèi)容,不能作為標識字符串結(jié)束的邊界。
正確的做法是
>>> re.findall('\\\\', BASE)
['\\']
結(jié)果是我們匹配到了一個\,也就是說,待處理字符串中作為轉(zhuǎn)義字符使用的\是匹配不到的。我們?yōu)榱似ヅ湟粋€\,需要用上四個\。
那為什么我們的pattern需要四個\呢?原因在于
>>> print('\\\\')
\\
我們的pattern字符串'\\'會轉(zhuǎn)義成\,于是'\\\\'在正則匹配函數(shù)中先被理解為'\\',而'\\'用來匹配待處理字符串,則再一次被理解為用\來匹配字符串。
也就是說,正則表達式中進行了兩次轉(zhuǎn)義。第一次將字符串轉(zhuǎn)義成pattern,在pattern匹配時則再次轉(zhuǎn)義。為什么需要兩次轉(zhuǎn)義呢?
根本原因在于,字符串中的轉(zhuǎn)義規(guī)則和正則表達式中的轉(zhuǎn)義規(guī)則不一樣。舉例來說,'\b'在一般的字符串里面會轉(zhuǎn)義成退格,在正則表達式里面會轉(zhuǎn)義成單詞邊界。如果不約定好轉(zhuǎn)義規(guī)則,'\b'到底應(yīng)該轉(zhuǎn)義成什么呢?于是Python中給出的解決辦法是,先進行普通字符串的轉(zhuǎn)義,將轉(zhuǎn)義后的字符串作為參數(shù)傳入正則匹配函數(shù)中,在正則匹配的過程中再進行正則表達式的轉(zhuǎn)義。
這樣的做法消除了歧義,但是帶來了麻煩,造成了\泛濫的現(xiàn)象,這會給正則表達式帶來極大的不便利。像用'\\\\'來匹配\的處理辦法看上去太丑陋了。為了簡化理解和操作,Python提供了原始字符串
>>> re.findall(r'\\', BASE)
['\\']
這使得我們可以使用匹配r'\\'來匹配\,這就顯得容易理解多了,\在字符串中需要轉(zhuǎn)義,這是我們能接受的。
原始字符串和普通字符串唯一的區(qū)別在于——原始字符串中的\都是默認經(jīng)過轉(zhuǎn)義的。
例外
但是,我要說但是,r'\'這種字符串還是不能出現(xiàn)的
>>> print(r'\')
File "<stdin>", line 1
print(r'\')
^
SyntaxError: EOL while scanning string literal
也就是說,在原始字符串中,\依然會對引號進行轉(zhuǎn)義(看上去)
>>> print(r"\'")
\'
>>> print(repr(r"\'"))
"\\'"
>>> print("\\'")
\'
通過repr[1]函數(shù)可以清楚地看到,原始字符串中的\是經(jīng)過自動轉(zhuǎn)義的,因此先還原成\\,又在輸出函數(shù)中再次轉(zhuǎn)義成\。
總之,想要用原始字符串只輸出\幾乎是不可能的,這也是不推薦大家在正則表達式以外的地方使用原始字符串的原因。我們可以簡單理解為原始字符串是正則表達式中為了簡化而專用的字符串形式。在正則式以外的地方能避免原始字符串帶來的歧義就盡量不用。
測試
現(xiàn)在回到我們一直用的BASE,如何匹配其中的\/這兩個字符呢?我們需要的輸出是\/或者'\\/'
以下是我們的結(jié)論
>>> re.findall('\\\\/', BASE)
['\\/']
>>> re.findall(r'\\/', BASE)
['\\/']
鏈接
- 6.2. re — Regular expression operations — Python 3.6.2 documentation
- In context of Python Raw string - Stack Overflow
TODO
-
repr函數(shù) ?