2017-06-03

Java 腦袋學 Python Regular Expression

Python Regular Expression 的套件為 re。

compile(), search() & findall()

#!python3
import re
regex = re.compile(r'\d{4}-?\d{6}') # r 表示使用 Raw string,即不 escape 反斜線 

s = '手機號碼有兩種,第一種是 0912-345678,第二種是 0912345678。'

# search 只會找到第一個符合的
mo = regex.search(s)
if mo != None:
    print(mo.group())
# 0912-345678

# findall 會找到全部符合的    
mo = regex.findall(s)
for mo in mobile_regex.findall(s):
    print(mo)
# 0912-345678
# 0912345678
Raw string 請參考 Java 腦袋學 Python 字串,如果不用 Raw string,那所有反斜線都要改用兩個反斜線,這樣變得很麻煩而且讓原本就難以閱讀的 RE 語法變得更加外星語。

快速複習 RE

  • \d 表示 0 到 9 的數字,也可以用 [0-9]  或者 (0|1|2|3|4|5|6|7|8|9) 表示
  • \D 表示 0 到 9 以外的任何字元,包括空白,也可以用 [^0-9] 表示
  • \w 表示字母、數字與底線,可以當作單字,也可以用 [a-zA-Z0-9_] 表示
  • \W 表示 \w 以外的任何字元,包括空白,也可以用 [^a-zA-Z0-9_] 表示
  • \s 表示空白、\t 與 \n,可以當作空白字元
  • \S 表示 \s 以外的任何字元
  • 自訂字元用中括號 [] 包起來,也可以使用 - 連字號指定字母與數字的範圍
  • 在中括號裡可以直接使用特殊字元,不用 \ Escape
  • 在中括號裡第一個字元使用 ^ 表示指定相反的字元集合
  • ^ 開頭與 $ 結尾
  • . 表示換行字元之外的任何字元,但對應的只有一個字元
  • .* 表示換行字元以外的所有字元,且為貪婪模式
  • .*? 表示換行字元以外的所有字元,但為非貪婪模式 
  • 非貪婪模式可以用在 {S,E}?、*? 與 +?
  • 分組用 ( 與 )
  • 或 |
  • 出現零次或一次 ?
  • 出現零次或 N 次 *
  • 出現一次以上 +
  • 出現指定次數 {N}、{S, E}、{S,}、{,E}
要找上述的特殊符號,就用 \ Escape。

指定字元

#!python3
import re

s = 'javascript java8 spring4 python3 css'

for mo in re.compile(r'\w+\d').findall(s):
    print(mo)
# java8
# spring4
# python3

Group 分組

#!python3
import re

# 用括弧分組
regex = re.compile(r'(\d{2})-(\d{8})')
s = '先來找簡單的電話號碼 04-22334455'

# search 只會找到第一個符合的
mo = regex.search(s)
print(mo.group()) # 04-22334455 符合字串
print(mo.group(0)) # 04-22334455 符合字串
print(mo.group(1)) # 04 符合字串的第一個分組
print(mo.group(2)) # 22334455 符合字串的第二個分組
print(mo.groups()) # ('04', '22334455') tuple 組成的分組
area, number = mo.groups()
print('{}-{}'.format(area, number)) # 04-22334455

或 |

#!python3
import re

regex = re.compile(r'python|java')
s = 'java spring python hibernate'

for mo in regex.findall(s):
    print(mo)
# java
# python
或也可以用 []。
#!python3
import re

regex = re.compile(r'[Pp]ython')
s = 'Python PYthon python'

for mo in regex.findall(s):
    print(mo)
# Python
# python

指定次數 ? * + { }

#!python3
import re

s = 'br bar baar baaar baaaar baaaaar'

# 零次或一次
for mo in re.compile(r'ba?r').findall(s):
    print(mo)
# br
# bar

# 零次或 N 次
for mo in re.compile(r'ba*r').findall(s):
    print(mo)
# br
# bar
# baar
# baaar
# baaaar
# baaaaar

# 一次以上
for mo in re.compile(r'ba+r').findall(s):
    print(mo)
# bar
# baar
# baaar
# baaaar
# baaaaar

# 指定次數{N}
for mo in re.compile(r'ba{3}r').findall(s):
    print(mo)
# baaar

# 指定次數 {S,E}
for mo in re.compile(r'ba{1,3}r').findall(s):
    print(mo)
# bar
# baar
# baaar

# 指定次數 {S,}
for mo in re.compile(r'ba{3,}r').findall(s):
    print(mo)
# baaar
# baaaar
# baaaaar

# 指定次數 {,E}
for mo in re.compile(r'ba{,3}r').findall(s):
    print(mo)
# br
# bar
# baar
# baaar

Greedy 貪婪模式

Python 預設是貪婪模式,即在符合條件的情況下,會找出最長的字串,例如 x{3,5} 可以找到五個 x 的話,就不會在 3 個 x 就結束。

可以在大括號後面加上 ?,改為非貪婪模式。
#!python3
import re

s = 'f fo foo fooo foooo fooooo'

for mo in re.compile(r'fo{1,3}').findall(s):
    print(mo)
# fo
# foo
# fooo
# fooo
# fooo

# 非貪婪模式
for mo in re.compile(r'fo{1,3}?').findall(s):
    print(mo)
# fo
# fo
# fo
# fo
# fo

findall() & Group 分組

findall() 會回傳所有符合的結果,但依據 RE 內容的不同而有不同的回傳物件。

當內容沒有分組時,回傳的是 list of str,但是有分組時,回傳的是 list of tuple。
#!python3
import re

s = '04-22334455 0912-345678 04-23456789'

for mo in re.compile(r'\d{2}-\d{8}').findall(s):
    print(mo)
# 04-22334455
# 04-23456789

s = '04-22334455 04-23456789'

for mo in re.compile(r'(\d{2})-(\d{8})').findall(s):
    print(mo)
# ('04', '22334455')
# ('04', '23456789')

額外的參數

re.DOTALL 讓 . 包括換行字元。
#!python3
import re

s = 'Ba\na\nar'
print(re.compile(r'B.*?r').search(s)) # None
print(re.compile(r'B.*?r', re.DOTALL).search(s).group()) # Ba\na\nar
re.IGNORECASE 或 re.I 不區分大小寫。
#!python3
import re

s = 'java Java JAVA'

for mo in re.compile(r'java').findall(s):
    print(mo)
# java

for mo in re.compile(r'java', re.IGNORECASE).findall(s):
    print(mo)
# java
# Java
# JAVA

# re.I 也行
for mo in re.compile(r'java', re.I).findall(s):
    print(mo)
# java
# Java
# JAVA
同時使用 re.DOTALL 與 re.IGNORECASE。
#!python3
import re

s = 'Ba\na\nar'
print(re.compile(r'b.*?r', re.DOTALL | re.IGNORECASE).search(s).group()) # Ba\na\nar

取代符合字串

用 sub() 取代 search() 或 findall()。
#!python3
import re

s = 'Python python'
print(re.compile(r'y').sub(r'*', s)) # P*thon p*thon
用原字串取代,使用 \N 表示分組 N,注意取代字串也是使用 Raw string 唷。
#!python3
import re

s = 'Python python'
print(re.compile(r'(python)', re.I).sub(r'[\1]', s)) # [Python] [python]

複雜的分組

當分組裡有分組時,如何判斷分組順序?依照左括號出現的順序。
#!python3
import re

s = 'Python3 python2'
print(re.compile(r'((python)(\d))', re.I).sub(r'\2[\3]', s)) # Python[3] python[2]
\1 表示 Python3 或 python2,\2 表示 Python 或 python,而 \3 表示 3 或 2。

分行並註解的語法

可以透過 re.VERBOSE 參數來使用多行模式('''),並可加入註解。
#!python3
import re

s = '1976-7-6 2017-1-7'
print(re.compile(r'''
((19|20)\d\d # 年
-
[0-1]?\d # 月
-
[0-3]?\d) # 日
''', re.VERBOSE).findall(s))
# [('1976-7-6', '19'), ('2017-1-7', '20')]

---
---
---

沒有留言:

張貼留言