---
tags: Python, IND
---
# Python Regular Expression
[TOC]
## What is regex?
正則表達式,即「描述某種規則的表達式」。
這樣好像有講跟沒講一樣對吧XD
最一開始的時候,regex 是使用在對形式化語言的分類,也就是藉由盡量統一的規則來分類各種奇形怪狀的表達方式
現在也使用在 compiler 中,將輸入的字串 parse 成便於解析的語法,接著執行相對應的指令
## Why we need regex?
~~因為他好用 (X~~
藉由 regex,我們可以將很多不同樣貌的字串轉換成符合一定規則的表達式,也可以藉由 regex 找到字串中符合規則的部分,特別是對於看似複雜的字串,說不定可以藉由好的 regex 規則找到需要的部分
~~但 re 也是個謎樣的東西 OuO 常常寫到豆頁疼~~
## Basic syntax
* `|` 代表 `or`,優先度最低。例如 `"is|are"` 就會配對 "is" 或是 "are"
(以下都是針對單個字元,或是搭配括號的字串群組)
* `+` 代表前面的字元必須至少出現一次 ==(1 ~ n 次)==
* `?` 代表前面的字元最多只可以出現一次 ==(0 or 1 次)==
* `*` 代表前面的字元可以不出現,也可以出現一次或者多次 ==(0 ~ n 次)==
* `.` 代表所有不包含換行的字元
* `^` 代表字串的開頭,也包括換行後的字串
(以下是常用的 syntax)
* `[]` 代表 char set
* `[^charset]` 代表 char set 以外的東西,例如 `[^6]` 表示除了 '6' 以外的任意字元
* `[0-9]` or `\d` 代表所有的數字
* `\D` 代表所有非數字的字元,相當於 `[^0-9]`
* `[a-z]` 代表所有的小寫英文字母
* `[A-Z]` 代表所有的大寫英文字母
* `[0-9a-zA-Z_]` or `\w` 就是所有**十進位數字**跟**英文大小寫字母**和**底線**
* `\W` 就是除了 `\w` 的所有字元
* `()` 代表一個群組,在裡面寫的 re 如果找到東西都會當作同一個 group
## Usage
凡事都先 import 就對ㄌ
```python
import re
```
若 string 包含 pattern 字串,則回傳 `Match` Object,只會配對到第一個
```python
re.search(pattern, string)
```
從首字母開始尋找,string 如果包含 pattern 字串則尋找成功,return `Match` Object,失敗則 return `None`,若要完全匹配則 pattern 要以 $ 結尾
```python
re.match(pattern, string[, flags])
```
回傳一個包含所有符合 pattern 的字串的 `list`
```python
re.findall(pattern, string[, flags])
```
跟上面一樣但是回傳一個 iterator,裡面的每個物件都是一個 `Match` Object
```python
re.finditer(pattern, string[, flags])
```
`re.compile(pattern)` 可以讓你先把 pattern 變成一個物件
```python
compiled_pattern = re.compile(pattern)
compiled_pattern.match(string)
```
## 等等,flag 是啥
* `re.A` ASCII-only matching
* `re.I` 忽略大小寫
* `re.L` 表示特殊符號 `\w` `\W` `\b` `\B`(?)
* `re.M` 多行模式
* `re.S` 相當於 `'.'` 且包含換行在內的任意字元(`'.'`不包括換行)
* `re.U` 表示 unicode 的特殊符號集
* `re.X` 為了增加可讀性而忽略空格和 `'#'` 後面的註解
[Reference][re flag]
## Escape Character
描述 re 裡面的 pattern 時,有些符號會與單獨的該符號搞混,例如 `'` 或 `"`,直接在 pattern 中出現時,很可能會被當作 python 中字串的開頭或結尾,所以在 python 中會以 `\'`、`\"` 來表示
其中,`\n` 會拿來表示換行,`\t` 會用來表示 tab,等等
觀察發現,只要遇到 `\` ,python 就會先去看看下一個是甚麼,然後決定這個是甚麼字元
### 說到這裡,`\` 要怎麼辦
如果只單獨使用 `\`,python 就會再去往後看看後面放了甚麼,以此來判斷式不是跳脫字元,所以為了使用 `\`,我們就要打成 `\\`,那如果想要表示兩個 `\` 就要打成 `\\\\`,`\` 的數量變成兩倍了!
還有一個方法是在 pattern 前面加上 `r` 表示 raw data,那後面的 `\` 就只需要打一個就好
例子:
* `print('\"')` $\rightarrow$ `"`
* `print(r'\"')` $\rightarrow$ `\"`
[Reference](https://en.wikipedia.org/wiki/Escape_character)
## Match vs Search
+ re.match() 和 re.search()
+ re.match() 必須從開頭符合
+ re.search() 則不用
## VSCode 也可以用!
+ 
## Use cases
### 檔名範例
+ 輸入範例
+ [123] Hello.xml
+ [456] World.xml
+ Regex: `re.compile('\\[[0-9]*\\] (.*)\\.xml')`
### 找到指定的檔案類型
regex 也可以搭配 grep 使用!
假設有個 files.txt 長這樣
```shell
$ cat files.txt
foo.txt
bar.txt
foo1.txt
bar1.doc
foobar.txt
foo.doc
bar.doc
dataset.txt
purchase.db
purchase1.db
purchase2.db
purchase3.db
purchase.idx
foo2.txt
bar.txt
$
```
想找 .txt 檔的話可以快樂的這樣用
```shell
$ cat files.txt | grep "\.txt"
```
就會有像這樣的輸出了

[Reference][files.txt]
### 日期年份
+ Regex
```python
re.compile('[0-9]+月[0-9]+日'),
re.compile('(前)?[0-9]+?(年)?'),
re.compile('[0-9]+(世紀)?'),
```
### 極簡易英文斷詞
+ 輸入範例
+ `Hello, I am Donald Trump. What is your name?`
+ Regex: `re.compile('[\w]+')`
+ Output
+ `['Hello', 'I', 'am', 'Donald', 'Trump', 'What', 'is', 'your', 'name']`
### 網址抓取
+ Regex: `re.compile('https?://[^ ]+')`
+ Question
+ 網路協定還有 FTP, TELNET, 可以都寫在一個敘述裡面嗎?
+ Demo
```python
import re
slist = [
'My Website http://abc.com/',
'Why not google https://google.com/',
'Go to telnet://ptt.cc/',
'My VM SSH Remote ssh://localhost:2222/',
]
protocal = [
'http', 'https', 'telnet', 'ssh'
]
p = re.compile('(%s)://[^ ]+' % '|'.join(protocal))
for s in slist:
print(p.search(s).group(0))
```
### Greedy Problem
+ 如果有多重符合狀況,則一般情況下是取最長的字串
```python
import re
p = re.compile('\\(.*\\)')
ss = 'ABC (AABBCC)'
p.search(ss) # Output: (AABBCC), correct
ss = 'ABC (AABBCC) DEF (DDEEFF)'
p.search(ss) # Output: (AABBCC) DEF (DDEEFF), wrong!
```
+ 解決方法
+ 使用 Non-Greedy 的寫法
+ `re.search('\\(.*?\\)', s)`
+ 使用另一種 Tricky 的寫法
+ `re.search('\\([^()]*\\)', s)`
### POS 詞性標記
+ 例句:你(Nh) 是(SHI) 屬於(VG) 領導(VC) 者(Na) 型(Na) 。(PERIODCATEGORY)
+ Regex: `re.compile('([^(]*)\\(([^)]*)\\) ?')`
+ Demo
```python
import re
p = re.compile('([^(]*)\\(([^)]*)\\) ?')
txt = '你(Nh) 是(SHI) 屬於(VG) 領導(VC) 者(Na) 型(Na) 。(PERIODCATEGORY)'
for pp in p.findall(txt):
print(pp)
```
+ Output
```
('你', 'Nh')
('是', 'SHI')
('屬於', 'VG')
('領導', 'VC')
('者', 'Na')
('型', 'Na')
('。', 'PERIODCATEGORY')
```
### Wikitext 清除
+ 目標
+ 把維基語法清掉,只留下純文字
+ Code
{%gist /penut85420/c8dfce718eb37297f6e611714a59956e%}
## Reference
[re flag]: https://www.ibm.com/developerworks/cn/opensource/os-cn-pythonre/index.html
[files.txt]: https://www.cyberciti.biz/faq/grep-regular-expressions/