Python::re 模塊 -- 在Python中使用正則表達式

前言

這篇文章,並非對正則表達式的介紹,而是對Python中如何結合re模塊使用正則表達式的介紹。文章的側重點是如何使用re模塊在Python語言中使用正則表達式,對於Python表達式的語法和詳細的介紹,能夠參考別的文章,這篇文章只是給出一些經常使用的正則表達式語法,以方便對re模塊的使用進行講解。html

對正則表達式的介紹,能夠參看這兩篇文章:python

正則表達式30分鐘入門教程正則表達式

正則表達式之道編程

注意:實驗環境爲 Python 3.4.3緩存

正則表達式簡介

正則表達式,又稱正規表示式、正規表示法、正規表達式、規則表達式、常規表示法(英語:Regular Expression,在代碼中常簡寫爲regex、regexp或RE),計算機科學的一個概念。正則表達式使用單個字符串來描述、匹配一系列符合某個句法規則的字符串。在不少文本編輯器裏,正則表達式一般被用來檢索、替換那些符合某個模式的文本。編輯器

許多程序設計語言都支持利用正則表達式進行字符串操做。例如,在Perl中就內建了一個功能強大的正則表達式引擎。正則表達式這個概念最初是由Unix中的工具軟件(例如sed和grep)普及開的。正則表達式一般縮寫成「regex」,單數有regexp、regex,複數有regexps、regexes、regexen。函數

最初的正則表達式出現於理論計算機科學的自動控制理論和形式化語言理論中。在這些領域中有對計算(自動控制)的模型和對形式化語言描述與分類的研究。 1940年,Warren McCulloch與Walter Pitts將神經系統中的神經元描述成小而簡單的自動控制元。 1950年代,數學家斯蒂芬·科爾·克萊尼利用稱之爲「正則集合」的數學符號來描述此模型。肯·湯普遜將此符號系統引入編輯器QED,而後是Unix上的編輯器ed,並最終引入grep。自此,正則表達式被普遍地使用於各類Unix或者相似Unix的工具,例如Perl。工具

Python提供了對正則表達式的支持,它內嵌在Python中,經過Python的re模塊提供。學習

re模塊提供了相似於Perl的正則表達式語法。this

經過使用正則表達式,咱們能夠制定須要匹配的字符串的特定格式,而後從須要處理的字符串中提取咱們感興趣的字符串。Python中的re模塊也提供了像sub()subn()split()這些方法來實現經過正則表達式來靈活地進行文本的替換和分割。

在Python中,正則表達式會被編譯成一系列的字節碼,而後由經過C編寫的正則表達式引擎進行執行。

初嘗正則表達式

下面,咱們先看看正則表達是什麼,瞭解下正則表達式是怎麼處理字符串問題的。若是你之前沒有接觸過正則表達式,是正則表達式小白,那麼能夠初略的熟悉下這節內容,內心有個大概的瞭解。在接下來的幾節中,咱們將學習到正則表達式的一些知識,等學習完了有關正則的知識,能夠再回過頭來看看這些例子,加深下理解。

下面,咱們簡單的看看在Python中使用正則表達式的例子:

簡答的字符串匹配操做

>>> import re
 >>> p = re.compile(r'ab*')
 >>> m = p.search('abbbba')
 >>> m.group()
 'abbbb'
>>> import re
 >>> p = re.compile(r'(ab)*')
 >>> m = p.search('abababa')
 >>> m.group(0)
 'ababab'

找出字符串中的數字

>>> import re
 >>> p = re.compile(r'\d+')
 >>> m = p.search('this year is 2015')
 >>> m.group()
 '2015'

找出字符串中的字符

>>> import re
 >>> p = re.compile(r'\w+')
 >>> m = p.search('this year is 2015')
 >>> m.group()
 'this'

匹配郵箱地址

>>> p = re.compile(r'[\w\d]+@\w+\.com')
 >>> m = p.search('mymail@mail.com')
 >>> m.group()
 'mymail@mail.com'

匹配IP地址

>>> p = re.compile(r'(\d{1,3}\.){3}\d{1,3}')
 >>> m = p.search('ip address is : 192.168.1.1 not 192.1.1')
 >>> m.group()
 '192.168.1.1'

正則表達式中的元字符

大多數的字符在進行正則表達式匹配的時候,會簡單的進行一對一的匹配,好比,普通的字符串test將會精確地匹配到test。可是,則正則表達式中,有一些字符具備特殊的用於,它們在匹配的時候不會精確匹配到對應的字符。這些具備特殊用於的字符,在正則表達式中被稱爲元字符

元字符不會匹配自身,相反,單個的元字符能夠匹配不一樣內容和長度的字符串,或者影響正則表達式的執行。正是因爲元字符的存在,才使得正則表達式如此靈活和強大。

在正則表達式中,元字符由一些這些字符組成,這些字符在使用的時候再也不表示它們原來的做用,若是要使用它們原來的意思,則須要使用反斜槓轉義:

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

下面,咱們將會正則表達式中的這些元字符進行解釋,可是在這以前,咱們須要先介紹下在Python中使用反斜槓進行轉義時碰到的問題。

轉義

在Python的字符串中,一些特殊的轉義字符具備特殊的意義。好比,在字符串中的'\n'表示的是換行,'\b'表示的是回退鍵(Backspace),等等。詳細的轉義字符表能夠查看這裏。咱們能夠看到,對於轉義字符,咱們須要在前面加上一個反斜槓來表示。那麼,若是咱們須要在字符串中使用一個反斜槓,而不是把這個反斜槓用於轉義的時候,咱們須要使用兩個反斜槓來表示'\\'

因此,若是咱們但願在字符串中包含'\n',可是咱們不但願進行轉義,那麼能夠在前面的反斜槓以前添加上一個反斜槓來進行跳脫(escape)。因此'\\n'就變成了單純的'\n'字符了,而不是一個換行符。反斜槓的做用就是用來對轉義字符進行跳脫。除了使用反斜槓來進行轉義字符的跳脫,也能夠用Python的raw字符串來達到一樣的效果,python的raw字符串能夠在字符串前面加上一個字符r來表示:r'raw string',這個字符串就是一個raw字符串。在raw字符串中,全部的轉義都將失去做用,因此r'\n'將表示兩個字符'\''n',而不是一個換行符。

前面咱們講到了Python中的轉義字符,那轉義字符對正則表達式有什麼影響呢?

咱們知道,正則表達式實際上就是一個字符串,和別的字符串的區別就是它們能夠被正則表達式引擎解析和執行。因此,爲了使正則表達式能夠正常的工做,咱們須要保證傳遞給正則表達式引擎的正則表達式字符串的語法是正確的,而且是咱們指望的樣子。可是,因爲Python中的轉義字符的存在,而且,在正則表達式的元字符中也包含了反斜槓,因此咱們須要正確地處理正則表達式中的反斜槓。

若是咱們須要在正則表達式中使用反斜槓原本的意思,而不是做爲元字符使用,那麼咱們須要傳遞給正則表達式引擎的就是一個'\\'表示的字符串。其中第一個反斜槓用於對後面的反斜槓進行跳脫,使得後面的反斜槓失去元字符的做用。如今,咱們須要兩個反斜槓'\\'傳遞給正則表達式引擎,那麼,咱們須要用'\\\\'這樣的Python字符串來表示。爲何呢?由於,咱們是在Python中使用正則表達式,而且正則表達式是使用Python中的字符串表示的,那麼考慮到Python中轉義字符的問題,咱們若是須要一個反斜槓,那麼須要在字符串中這樣表示'\\',若是須要兩個,那麼就要這樣表示'\\\\'。這樣,咱們的Python字符串就會產生兩個反斜槓組成的字符串了。

而對於正則表達式的一些特殊的元字符,好比'\d',若是用Python的字符串表示,則須要表示成'\\d'來保證傳遞給正則表達式引擎的是'\d'

>>> print('\\')
 \
 >>> print('\\\\')
 \\
 >>> print('\\d')
 \d

若是正則表達式中包含了不少的反斜槓,這樣會致使正則表達式變的複雜和難以理解,因此,咱們通常都用Python的raw字符串來表示一個正則表達式。由於在raw字符串中,反斜槓將不具備特殊用途,因此r'\\'表示'\\'r'\d'表示'\d'

>>> print(r'\\')
 \\
 >>> print(r'\d')
 \d

咱們在編寫正則表達式的時候,都應該使用Python的raw字符串來表示。

元字符

介紹完了反斜槓的問題,下面,咱們介紹正則表達式中的元字符,是它們使得正則表達式如此靈活和強大的。

注意:這裏只是介紹正則表達式中一些經常使用的元字符,詳細的介紹能夠參考Python標準庫中的re模塊

元字符[]用於表示一個字符類,一個字符類表示了一個但願被匹配的字符組成的集合。字符類中的字符能夠是以單個的字符出現,也能夠是以字符區間的形式出現,字符區間是由兩個字符組成,中間由一個連字符'-'分隔。如:[abc]能夠匹配a、b、c中的任何一個字符,一樣,也能夠用[a-c]來達到一樣的效果,[a-c]表示匹配a到c之間的任何一個字母。若是須要匹配任何一個小寫字母,則能夠這樣寫[a-z]

在元字符[]中的字符,將會失去特殊的做用,也就是說,若是在[]中包含了元字符,則這些元字符將再也不具備特使做用,而只是表示字符自身。這能夠用來代替反斜槓來跳脫元字符:
[$]能夠和\$達到相同的效果。

若是須要匹配再也不字符類中的字符,則能夠對字符類進行取反,使用'^'符號來進行取反。將'^'符號放在字符類中的字符集合中的第一個,使得能夠匹配不在這個字符集合中的字符。好比:[^a-z]表示匹配任何不是小寫字母的字符。

>>> p = re.compile(r'[ab]')
 >>> m = p.search('ababaabaacdefg')
 >>> m.group()
 'a'

元字符\w能夠匹配字母和數字。若是正則表達式是由字節表示的,則\w至關因而字符類[a-zA-Z0-9_]。若是正則表達式是一個字符串,則\w將會匹配全部在Unicode database中標記爲文字(letters)的字符,Unicode database由模塊unicodedata模塊提供。在編譯正則表達式的時候,能夠經過指定 re.ASCII 選項來限制\w匹配的範圍。

>>> p = re.compile(r'\w')
 >>> m = p.search('ababaabaacdefg')
 >>> m.group()
 'a'

元字符\d將會匹配數字,對於Unicode表示的正則表達式(str類型),將會匹配Unicode表示的十進制數字,包括[0-9],以及其餘的數字字符。若是 re.ASCII 選項在編譯的時候被指定,則只會匹配[0-9](因爲 re.ASCII 選項會影響整個正則表達式,因此在須要匹配0到9組成的數字的時候,可使用[0-9])。對於8位字節(bytes類型)表示的正則表達式,則至關因而[0-9]

>>> p = re.compile(r'\d')
 >>> m = p.search('123')
 >>> m.group()
 '1'

元字符\D匹配任何不是Unicode數字的字符,這個元字符和\d的相反。若是指定了 re.ASCII 選項,則至關因而 [^0-9]

>>> p = re.compile(r'\D')
 >>> m = p.search('123abc')
 >>> m.group()
 'a'

元字符\s,在Unicode表示(str類型)的正則表達式中,用於匹配Unicode空白字符,包括[ \t\n\r\f\v],若是 re.ASCII 選項被指定,則只匹配[ \t\b\r\f\v]。對於8位字節(bytes)表示的正則表達式,則和[ \t\n\r\f\v]是等價的。

>>> p = re.compile(r'\s')
 >>> m = p.search('hello world')
 >>> m.group()
 ' '

元字符\S,匹配不是Unicode空白字符的任何字符。和\s相反。若是 re.ASCII 選項被指定,則和 [^ \t\n\r\f\v]等價。

>>> p = re.compile(r'\S')
 >>> m = p.search('hello world')
 >>> m.group()
 'h'

元字符\W匹配不是Unicode中的word字符的任何字符。和\w相反。若是指定了 re.ASCII 選項,則和 [^a-zA-Z0-9_]等價。

>>> p = re.compile(r'\W')
 >>> m = p.search('hello-world')
 >>> m.group()
 '-'

元字符 .能夠匹配除了換行符外的任何一個字符。可是,若是在編譯的時候指定了 re.DOTALL 選項,則能夠匹配任何一個字符,包括換行符。

>>> p = re.compile(r'.')
 >>> m = p.search('hello')
 >>> m.group()
 'h'

元字符|表示操做。若是A和B都是正則表達式,則A|B表示會匹配A和B中的任何一個。因爲|的優先級具備很低的優先級,因此當進行以下hello|world模式串進行匹配的時候,helloworld之間的任何一個匹配了,則這個正則表達式就匹配成功了,而不是做用於ow。若是須要在正則表達式中使用|做爲普通的字符,則可使用反斜槓進行轉義\|,或者使用字符類[|]

>>> p = re.compile(r'\d+|\w+')
 >>> m = p.search('a1b2c3d4')
 >>> m.group()
 'a1b2c3d4'

元字符^匹配一行的開頭。除非指定了re.MULTILINE選項,不然會匹配字符串的開頭,若是re.MULTILINE選項被指定,則會匹配字符串中換行符後面的位置。
若是須要在模式串中去掉元字符的特殊意義,則可使用反斜槓來轉義\^,或者使用字符類[^]

>>> p = re.compile(r'^hello')
 >>> p.search('hello world')
 <_sre.SRE_Match object; span=(0, 5), match='hello'>
 >>> print(p.search('a word hello'))
 None

元字符$匹配一行的結尾,一行的結尾的定義是:一個字符串的結尾,當指定了re.MULTILNE選項後,會匹配換行符以前的位置。
若是須要去掉元字符的特殊意義,則可使用反斜槓進行轉義\^,或者使用字符類[$]

>>> p = re.compile(r'd$')
 >>> p.search('hello world')
 <_sre.SRE_Match object; span=(10, 11), match='d'>
 >>> print(p.search('hello'))
 None

 >>> p = re.compile(r'\w+d$', re.M)
 >>> p.search('word\n Word').group()
 'word'

元字符\A\Z分別匹配字符串的開頭和結尾,不會受到re.MULTILINE選項的影響。當沒有指定re.MULTILINE選項的狀況下,^相對因而\A,而$至關因而\Z。(爲何要使用AZ做爲匹配字符串開頭和結尾的元字符,多是考慮到A是英文字符的第一個字母,而Z是英文字符的最後一個字母的緣故,這樣相對容易記憶)。

元字符\b匹配單詞的邊界,這是一個零寬斷言,匹配單詞的邊界(開頭或結尾)。單詞的定義是:一系列字母數字組成的序列,因此單詞能夠被空白符或者非字母數字分隔。

>>> p = re.compile(r'\bhello\b')
 >>> p.search('hello world').group()
 'hello'
 >>> print(p.search('helloworld'))
 None

元字符\B匹配不是單詞邊界的位置,這個元字符也是一個零寬斷言,和元字符\b的意思相反。

重複匹配

正則表達式除了能夠經過元字符匹配任意的字符之外,還具備重複的能力,能夠指定匹配的次數。

元字符 *匹配重複前一個字符或分組0次或屢次,若是須要匹配'*'字符,則須要進行轉義'\*'。因爲Python的re模塊中的正則表達式引擎是採用C編寫的,因此重複次數的最大值被int類型的最大值限制,重複次數最大爲20億次。

Python中正則表達式引擎的重複匹配是貪婪的,匹配引擎會盡量多的進行匹配,直到不能匹配爲止,而後匹配引擎會進行回溯,嘗試更小的重複次數進行匹配,直到匹配上。因此對於字符串aaaaaaba*將會匹配aaaaaa,而不是匹配a

>>> p = re.compile(r'ca*t')
 >>> m = p.search('caaat')
 >>> m.group()
 'caaat'

元字符+,用於重複前一個字符或分組一次或屢次。元字符*+是有區別的,+要求前一個字符出現至少一次,而*並不要求前一個字符必定要出現。因此對於正則表達式ca*t,能夠匹配ct,可是正則表達式ca+t將不能匹配ct

元字符?,用於重複前一個字符或分組一次或0次。正則表達式home-?brew能夠匹配home-brewhomebrew

元字符{m, n},能夠匹配前一個字符或分組m ~ n次。正則表達式a/{1,3}b將會匹配a/ba//ba///b。若是省略了m,則表示匹配0 ~ n次,若是省略了n,則表示至少匹配m次。所以,{0,}*是等價的,{0,1}?是等價的,{1,}+是等價的。

使用正則表達式

如今,咱們已經對正則表達式有了一些瞭解,也知道了正則表達式中元字符的做用。下面,咱們開始結合Python的re模塊來學習使用這些正則表達式。

Python的re模塊提供了一套接口來使用正則表達式引擎,經過re模塊提供的接口,能夠將正則表達式編程成正則表達式對象,而後進行須要的匹配操做。

編譯正則表達式

正則表達式能夠被編譯成模式對象,而後調用模式對象的方法來進行字符串匹配操做,好比搜索、替換或者分割。

經過re模塊的compile()函數能夠將一個正則表達式編譯成一個模式對象

re.compile(pattern, flags=0)
>>> import re
 >>> p = re.compile(r'a*')
 >>> p
 re.compile('a*')

傳遞給re.compile()函數一個可選的flags參數,能夠控制編譯後的對象在匹配的時候的行爲。

flags 參數的值能夠是如下的值:

re.A | re.ASCII
: 對\w\W\b\B\d\D\s\S產生影響,編譯後的模式對象在進行匹配的時候,只會匹配ASCII字符,而不是Unicode字符。

這個選項只對Unicode模式串有效,對字節模式串無效。

re.I | re.IGNORECASE
: 在匹配的時候忽略大小寫,在字符類和字符字面值進行匹配的時候會忽略大小寫。進行大小寫轉換的時候,不會考慮當前的locale信息,除非指定了re.LOCALE選項。

>>> p = re.compile(r'[a-z]+', re.I)
    >>> m = p.search('abcdABCD')
    >>> m.group()
    abcdABCD

re.M | re.MULTILINE
: 默認,元字符^會匹配字符串的開始處,元字符$會匹配字符串的結束位置和字符串後面緊跟的換行符以前(若是存在這個換行符)。若是指定了這個選項,則^將會匹配字符串的開頭和每一行的開始處,緊跟在每個換行符後面的位置。相似的,$會匹配字符串的最後和每一行的最後,在接下來的換行符的前面的位置。

>>> p = re.compile(r'(^hello$)\s(^hello$)\s(^hello$)\s')
    >>> m = p.search('hello\nhello\nhello\n')
    >>> print(m)
    None
    
    >>> p = re.compile(r'(^hello$)\s(^hello$)\s(^hello$)\s', re.M)
    >>> m = p.search('\nhello\nhello\nhello\n')
    >>> m.groups()
    ('hello', 'hello', 'hello')

re.S | re.DOTALL
: 使得.元字符能夠匹配任何字符,包括換行符。

re.X | re.VERBOSE
: 這個選項容許編寫可讀性更強的正則表達式代碼,並進行自由的格式化操做。當這個選項被指定之後,在正則表達式之間的空格符會被忽略,除非這個空格符是在一個字符類中[ ],或者在空格前使用一個反斜槓\。這個選項容許對正則表達式進行縮進,使得正則表達式的代碼更加格式化,更加清晰。而且能夠在正則表達式的代碼中使用註釋,這些註釋會被正則表達式引擎在處理的時候忽略。註釋以'#'字符開頭。因此若是須要在正則表達式中使用'#'符號,須要在前面添加反斜槓'\#'或者將它放在[]中,[#]

charref = re.compile(r"""
    &[#]                # Start of a numeric entity reference
    (
         0[0-7]+         # Octal form
       | [0-9]+          # Decimal form
       | x[0-9a-fA-F]+   # Hexadecimal form
    )
    ;                   # Trailing semicolon
    """, re.VERBOSE)

若是沒有指定re.**VERBOSE**選項,則至關於:

    charref = re.compile("&#(0[0-7]+"
                 "|[0-9]+"
                 "|x[0-9a-fA-F]+);")

進行匹配

一旦咱們經過re.compile()編譯後產生了一個正則表達式對象,咱們就能夠經過這個正則表達式對象進行匹配操做了。re模塊的正則表達式對象包含了一些方法,能夠進行須要的匹配操做。

方法 描述
match() 從字符串開頭位置開始匹配
search() 對字符串的任意位置進行匹配
findall() 返回字符串中全部匹配的子串組成的列表
finditer() 返回一個包含了全部的匹配對象的迭代器

若是沒有匹配到,則match()search()方法返回None,若是匹配成功,則返回一個匹配對象。返回的匹配對象包含了匹配信息:包括匹配的子串的開始位置和結束位置等信息。

>>> p = re.compile(r'[a-z]+')
 >>> print(p.match('hello123'))
 <_sre.SRE_Match object; span=(0, 5), match='hello'>
 >>> print(p.match('123hello'))
 None
 >>> print(p.search('123hello'))
 <_sre.SRE_Match object; span=(3, 8), match='hello'>

 >>> p = re.compile(r'\d+')
 >>> p.findall('a number A is 12, and a number B is 8, A + B = 20')
 ['12', '8', '20']

 >>> i = p.finditer('a number A is 12, and a number B is 8, A + B = 20')
 >>> for item in i:
    print(item.group())
    
 12
 8
 20

除了使用正則表達式對象中的方法,re模塊也提供了一些模塊級別的同名函數來進行匹配操做。包括:match(),search(),findall()和sub()等函數。這些函數接受和正則表達式對象中的方法相似的參數,除了第一個參數是一個正則表達式字符串之外,後面的參數和正則表達式對象中的方法接受的參數是一致的。而且這些函數的返回值也是相同的。

>>> re.search(r'\d+', 'a number A is 12')
 <_sre.SRE_Match object; span=(14, 16), match='12'>

 >>> re.findall(r'\d+', 'a number A is 12, and a number B is 8, A + B = 20')
 ['12', '8', '20']

實際上,這些函數在底層會對正則表達式字符串進行編譯,產生一個正則表達式對象,而後調用正則表達式中同名的方法來實現。而且,這些函數會將正則表達式對象緩存起來,因此下次再次使用相同的正則表達式的時候,就能夠不用再次對這個正則表達式進行編譯了。

對於使用模塊級別的函數仍是使用正則表達式對象中的方法來進行匹配,須要依據不一樣的使用場景來權衡。若是是在循環中,則使用編譯好的正則表達式對象會相對高效,而在循環外,因爲緩存的存在,這兩種方式區別不大。

咱們獲得匹配對象之後,就能夠經過匹配對象中的方法對匹配的信息進行進一步的處理了。

匹配對象中重要的方法以下:

方法 描述
group() 返回正則表達式匹配到的字符串
start() 返回匹配的起始位置
end() 返回匹配的結束位置
span() 返回一個包含匹配的起始位置和結束位置的元組(start, end)
>>> p = re.compile(r'[a-z]+')
 >>> m = p.search('123hello')
 >>> m
 <_sre.SRE_Match object; span=(3, 8), match='hello'>
 >>> m.group()
 'hello'
 >>> m.start()
 3
 >>> m.end()
 8
 >>> m.span()
 (3, 8)

正則表達式不是萬能的

雖然正則表達式很強大,能夠靈活地處理不少字符串處理得工做。可是,因爲正則表達式語言相對嚴格和小巧,因此一些字符串處理工做,正則表達式並不能處理的很好。

在一些狀況下,並不適合使用正則表達式,相反,使用Python的字符串中的方法,可能更加合適。好比,若是你須要對一個字符串進行匹配,而匹配的模式串是一個固定的字符串的話,那麼,使用Python中的字符串中的一些方法,如replace()方法,會比使用正則表達式來的更加高效和簡單。

所以,在咱們使用Python的re模塊中的正則表達式來進行字符串的處理工做以前,咱們不妨考慮下,是否可使用字符串中簡單高效的方法來解決。

後續

這篇文章沒有提到正則表達式中的分組(group),在re模塊中也支持分組,而且添加了Python的一些特性,後續再來介紹下re模塊中的分組的使用,以及一些別的特性。


正則表達式 - 維基百科

https://docs.python.org/3/howto/regex.html

相關文章
相關標籤/搜索