---
# System prepended metadata

title: 'python [正則表達式Regular Expression](https://zh.wikipedia.org/wiki/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F)'

---

python [正則表達式Regular Expression](https://zh.wikipedia.org/wiki/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F)
===
### 前面不是正則
#### 類繼承搜尋
```python
# 新式類 class A(object):
# 舊式類 class A:
In [1]: class A: 
   ...:     def __init__(self): 
   ...:         self.a = 100 
   ...:
   
In [2]: class B(A): 
   ...:     pass 
   ...:
   
In [3]: class C(A): 
   ...:     pass 
   ...:
   
In [4]: class D(B, C): 
   ...:     pass 
   ...:
   
In [5]: d = D()
In [6]: d.a                
Out[6]: 100
```

- 新式類係橫向搜尋
    - D>BC>A
- 舊式類係縱向搜尋
    - D>B>A + D>C>A

#### 搜尋類屬性和實例屬性
```python
In [1]: class Foo(object): 
   ...:     # pro = property 
   ...:     c_pro = 10 # 類屬性 
   ...:     def __init__(self): 
   ...:         self.obj_pro = 100 # 實例(對象)屬性 
   ...:
In [2]: foo = Foo() 

# foo如何知道FOO有哪些屬性?
In [3]: dir(foo)               
Out[3]: 
['__class__',
 '__delattr__',
 '__dict__',
 '__dir__',
 ...
 '__weakref__',
 'c_pro',
 'obj_pro']

# 實例屬性會被放到對象當中的__dict__裡
In [4]: foo.__dict__    
Out[4]: {'obj_pro': 100}


In [5]: dir(Foo)              
Out[5]: 
['__class__',
 '__delattr__',
 '__dict__',
 '__dir__',
 ...
 '__weakref__',
 'c_pro']

# 類在構造後, 一但定義類屬性時, 類即會具備__dict__屬性(魔法屬性)
# 定義的類都會在這個魔法字典的裡
In [6]: Foo.__dict__    
Out[6]: 
mappingproxy({'__module__': '__main__',
              'c_pro': 10,
              '__init__': <function __main__.Foo.__init__(self)>,
              '__dict__': <attribute '__dict__' of 'Foo' objects>,
              '__weakref__': <attribute '__weakref__' of 'Foo' objects>,
              '__doc__': None})

# foo會先搜他的__dict__, 此時有這個屬性
In [7]: foo.obj_pro          
Out[7]: 100

# foo先搜自己的__dict__有無, 此時沒有
# 接著搜尋類的__dict__有無, 此時存在c_pro
In [8]: foo.c_pro              
Out[8]: 10

# 當都沒有時, ERROR
In [9]: foo.haha
AttributeError: 'Foo' object has no attribute 'haha'

# 如果我要調用一個一開始就沒有建構的屬性?
# __getattr__ > get attribute
```
#### 打印think different itcast
```python
# coding:utf-8

class foo(object):
    def __init__(self):
        pass

    def __getattr__(self, item):
        print(item, "", end="") # item沒有被返回, 故自己手動打印
        # return item # 思考
        return self

    # print(foo())會打印對象, 此時定義對象為空str
    def __str__(self):
        return ""

print(foo().think.different.itcast)

# 思考: return 誰?
#   如果return item
#   調用foo().think 返回值為think > str
#   接著調用different時,即會ERROR > "think".diffrernt
#   (AttributeError: 'str' object has no attribute 'different')
#   故不能回傳item, 應回傳self > foo().different
# 
# 小結: 當在dict找不到時, 會到getattr找
```

#### \_\_getattribute\_\_ vs \_\_getattr\_\_

- Q. 誰規定先找dict後找getattr?
    - getattribute
    - 當執行xx.xx時, 即會執行getattribute, python即在裡面定義
    - 改getattribute切記, 請勿在裡面寫xx.xx, 會造成無限迴圈

正則: 描述某種規則的表達式
---
### match
```python
In [1]: import re 
In [2]: a = "abcde" 
In [3]: b = "abcdf"
In [4]: c = re.match(a, b)
In [5]: print(c)                       
None # 他不是返回"", 因為空str也可能為規則

In [6]: b = "abcde"
In [7]: d = re.match(a, b)
In [8]: print(d)                     
<re.Match object; span=(0, 5), match='abcde'>
In [11]: d.group() # 用來查看的              
Out[11]: 'abcde'

# match 只匹配從左至右, 符合的話就返回
In [12]: e = re.match("abcde", "abcdefgh")
In [13]: e.group()                   
Out[13]: 'abcde'
```

### 字符
```python
# . > 任何字符(除了\n)
In [18]: re.match(".", "abc")          
Out[18]: <re.Match object; span=(0, 1), match='a'>
In [20]: print(re.match(".", "\n"))    
None
In [22]: print(re.match("..", "a"))   
None # 要兩個卻只有一個
In [23]: print(re.match("..", "ab"))   
<re.Match object; span=(0, 2), match='ab'>
In [24]: print(re.match("..", "abc"))
<re.Match object; span=(0, 2), match='ab'>

# \d > digit數字
In [25]: print(re.match("\d", "1")) 
<re.Match object; span=(0, 1), match='1'>
In [26]: print(re.match("\d", "2"))  
<re.Match object; span=(0, 1), match='2'>
In [27]: print(re.match("\d", "a"))
None

# \D > 非數字
In [29]: print(re.match("\D", "a"))   
<re.Match object; span=(0, 1), match='a'>

# \s > 空白, 包括 \r\n\t等
In [35]: print(re.match("\s", " a"))    
<re.Match object; span=(0, 1), match=' '>
In [36]: print(re.match("\s", "\ra"))
<re.Match object; span=(0, 1), match='\r'>
In [37]: print(re.match("\s", "\na"))
<re.Match object; span=(0, 1), match='\n'>
In [38]: print(re.match("\s", "\ta")) 
<re.Match object; span=(0, 1), match='\t'>

# \S > 非空白
In [39]: print(re.match("\S", "\ta"))  
None

# \w > 單詞字符, 即A-Z, a-z, 0-9, _
In [40]: print(re.match("\w", "\ta")) 
None
In [41]: print(re.match("\w", "-a")) 
None
In [42]: print(re.match("\w", "@a"))   
None
In [43]: print(re.match("\w", "_a"))    
<re.Match object; span=(0, 1), match='_'>
In [44]: print(re.match("\w", "Aa"))  
<re.Match object; span=(0, 1), match='A'>

# \W > 非單詞字符
In [46]: print(re.match("\W", "Aa"))  
None

# 匹配方法: 一個錯就None
In [47]: print(re.match("\w\W", "Aa")) 
None
In [48]: print(re.match("\w\w", "Aa")) 
<re.Match object; span=(0, 2), match='Aa'>

# 應用: 手機號碼
In [49]: print(re.match("09\d\d\d\d\d\d\d\d", "0987654321"))                             
<re.Match object; span=(0, 10), match='0987654321'>
In [50]: print(re.match("09\d\d\d\d\d\d\d\d", "1987654321")) 
None
In [51]: print(re.match("09\d\d\d\d\d\d\d\d", "098765432a"))                             
None

# [] > 列舉
# 注意,沒有逗號
In [53]: print(re.match("1[3456]", "14"))
<re.Match object; span=(0, 2), match='14'>
In [52]: print(re.match("1[3456]", "19"))
None
In [57]: print(re.match("1[a-z0-9]", "1a"))
<re.Match object; span=(0, 2), match='1a'>
In [58]: print(re.match("1[a-z0-9]", "1z"))
<re.Match object; span=(0, 2), match='1z'>
In [59]: print(re.match("1[a-z0-9]", "13"))
<re.Match object; span=(0, 2), match='13'>
In [60]: print(re.match("1[a-z0-9]", "1A"))
None

# [^] > 反列舉
In [54]: print(re.match("1[^3456]", "19"))
<re.Match object; span=(0, 2), match='19'>
In [55]: print(re.match("1[^3456]", "1a")) 
<re.Match object; span=(0, 2), match='1a'>
In [56]: print(re.match("1[^3456]", "1@"))
<re.Match object; span=(0, 2), match='1@'>

# . == [^\n]
# \d == [0-9]
# \D == [^0-9]
# \w == [0-9A-Za-z] ...
```

#### 數量

```python
# * > 0到無限次
In [61]: print(re.match("\d*", "")) # 0次 
<re.Match object; span=(0, 0), match=''>
In [62]: print(re.match("\d*", "123")) # 3次
<re.Match object; span=(0, 3), match='123'>
In [63]: print(re.match("\d*", "abc")) # 0次
<re.Match object; span=(0, 0), match=''>
# "abc" == """abc" > ""符合規則

# + > 1到無限次
In [64]: print(re.match("\d+", "123"))
<re.Match object; span=(0, 3), match='123'>
In [65]: print(re.match("\d+", "abc"))
None
# 從第一位到無限大為數字, 故第一位不是數字為None
In [66]: print(re.match("\d+", "a1bc"))
None
In [67]: print(re.match("\d+", "1abc"))
<re.Match object; span=(0, 1), match='1'>

# ? > 0次或1次
# "\d?[a-z]" > 有或沒有數字後為a-z
In [68]: print(re.match("\d?[a-z]", "1abc")) # 有數字後a
<re.Match object; span=(0, 2), match='1a'>
In [69]: print(re.match("\d?[a-z]", "abc")) # 沒數字後a
<re.Match object; span=(0, 1), match='a'>
In [70]: print(re.match("\d?[a-z]", "12abc")) # 有數字後2 > None
None
In [75]: print(re.match("\d*[a-z]", "12abc")) # 兩次數字後a
<re.Match object; span=(0, 3), match='12a'>
In [76]: print(re.match("\d+[a-z]", "12abc")) # 兩次數字後a
<re.Match object; span=(0, 3), match='12a'>

# {} > 限定次數
In [77]: print(re.match("\d{3}[a-z]", "123abc")) # == \d\d\d[a-z]
<re.Match object; span=(0, 4), match='123a'>
In [78]: print(re.match("\d{4}[a-z]", "123abc"))
None
In [79]: print(re.match("\d{2}[a-z]", "123abc"))
None

# {,} > 至少次數
In [80]: print(re.match("\d{2,}[a-z]", "123abc")) # 至少兩次
<re.Match object; span=(0, 4), match='123a'>
# * == {0,}
# + == {1,}

# {m,n} > m到n次
In [81]: print(re.match("\d{2,4}[a-z]", "123abc"))                                       
<re.Match object; span=(0, 4), match='123a'>
In [82]: print(re.match("\d{2,4}[a-z]", "12345abc"))                                     
None
In [83]: print(re.match("\d{2,4}[a-z]", "1abc"))
None
# ? == {0,1}

# 應用: 手機號碼
In [84]: print(re.match("09\d{8}", "0987654321"))
<re.Match object; span=(0, 10), match='0987654321'>
```

#### 原始字符串

```python
In [86]: a = "\nabc"
In [88]: print(a)                     

abc

# 如果我要的是\nabc而不是空行?
In [89]: a = "\\nabc" # 再一個\轉譯
In [90]: print(a)                     
\nabc

# 那麼我要如何match?
In [95]: print(re.match("\\[a-z]{4}", a))
None
# Q. 為何沒有成功? 
#     因為第一個\也被認為是轉譯字符> 故少一個\
In [96]: print(re.match("\\\\[a-z]{4}", a)) # 1轉2,3轉4
<re.Match object; span=(0, 5), match='\\nabc'>

# 如此一個\就要\\, 太麻煩了
In [98]: a = "\nabc"
In [100]: print(a)                    

abc
In [102]: a = r"\nabc" # r = raw > 原始格式
In [103]: print(a)                   
\nabc
In [104]: a                         
Out[104]: '\\nabc'
In [108]: print(re.match(r"\\nabc", a))
<re.Match object; span=(0, 5), match='\\nabc'>
In [109]: r"\\nabc"                   
Out[109]: '\\\\nabc'
```

#### 邊界

```python
# ^ > 起始 > 在match的效果不明顯, 因為match本來就是從最左開始
# $ > 結尾
# ps. Vim
#     A > 跳到句首並編輯
#     I > 跳到句尾並編輯
#     ^ > 跳到句首
#     $ > 跳到句尾
In [2]: print(re.match(r"^09\w{8}$", "0987654321"))
<re.Match object; span=(0, 10), match='0987654321'>
In [3]: print(re.match(r"^09\w{8}$", "09876543212"))
None

# \b > 單詞邊界
In [6]: print(re.match(r"\w+et\b", "wetdream"))
None
In [7]: print(re.match(r"\w+et\b", "wet dream"))
<re.Match object; span=(0, 3), match='wet'>
In [8]: print(re.match(r"\w+\bet\b", "w et dream"))                                      
None # ??? 因為空格沒有match
In [9]: print(re.match(r"\w+\s\bet\b", "w et dream"))                                    
<re.Match object; span=(0, 4), match='w et'>
In [10]: print(re.match(r".+\bet\b", "w et dream"))                                      
<re.Match object; span=(0, 4), match='w et'>

# \B > 非單詞邊界
In [11]: print(re.match(r".+\bet\B", "w et dream")) 
None
In [12]: print(re.match(r".+\bet\B", "w etdream"))                                       
<re.Match object; span=(0, 4), match='w et'>
In [13]: print(re.match(r".+\Bet\B", "wetdream"))
<re.Match object; span=(0, 3), match='wet'>
```

### 分組

```python
# match 0-100
# 
# 在不考慮0跟100的情況下, 十位數一定是1-9, 個位數為0-9
In [30]: print(re.match(r"[1-9]\d?$", "1"))
<re.Match object; span=(0, 1), match='1'>
In [31]: print(re.match(r"[1-9]\d?$", "99"))
<re.Match object; span=(0, 2), match='99'>

# | > 按位或
In [33]: print(re.match(r"[1-9]\d?$|0$|100$", "0"))                                      
<re.Match object; span=(0, 1), match='0'>
In [34]: print(re.match(r"[1-9]\d?$|0$|100$", "100"))                                    
<re.Match object; span=(0, 3), match='100'>
In [35]: print(re.match(r"[1-9]\d?$|0$|100$", "200"))                                    
None

# 當十位數沒有時, 個位數也能有0
In [37]: print(re.match(r"[1-9]?\d?$|100$", "0"))
<re.Match object; span=(0, 1), match='0'>

# () > 分組
In [38]: print(re.match("<h1>.*</h1>", "<h1>Toyz</h1>"))                                 
<re.Match object; span=(0, 13), match='<h1>Toyz</h1>'>
In [41]: result = re.match("<h1>(.*)</h1>", "<h1>Toyz</h1>")
In [43]: result.group()               
Out[43]: '<h1>Toyz</h1>'
In [44]: result.group(1)                
Out[44]: 'Toyz'

# <h1> 第一組, </h1> 第二組
In [45]: result = re.match("(<h1>).*(</h1>)", "<h1>Toyz</h1>")
In [46]: result.group(1)                
Out[46]: '<h1>'
In [47]: result.group(2)                 
Out[47]: '</h1>'
In [48]: result.group(0) # group預設值  
Out[48]: '<h1>Toyz</h1>'

In [49]: result.groups()                
Out[49]: ('<h1>', '</h1>')
In [50]: result.groups()[0]            
Out[50]: '<h1>'
In [51]: result.groups()[1]            
Out[51]: '</h1>'

# >>情境: 完整的HTML標籤才match
In [52]: s = "<html><h1>Toyz</h1></html>"
In [53]: print(re.match("<.+><.+>.+</.+></.+>", s))                                      
<re.Match object; span=(0, 26), match='<html><h1>Toyz</h1></html>'>
In [54]: s = "<html><h1>Toyz</h1></ht>" # 不完整也match了
In [55]: print(re.match("<.+><.+>.+</.+></.+>", s))                                      
<re.Match object; span=(0, 24), match='<html><h1>Toyz</h1></ht>'>

# \num: 引用分組str
In [56]: print(re.match(r"<(.+)><(.+)>.+</\2></\1>", s))                                 
None
In [57]: s = "<html><h1>Toyz</h1></html>"
In [58]: print(re.match(r"<(.+)><(.+)>.+</\2></\1>", s))                                 
<re.Match object; span=(0, 26), match='<html><h1>Toyz</h1></html>'>

# 實作: 提取mail
In [62]: a = r"(\w+)@(gmail|yahoo|hotmail)\.(com)"
In [64]: r = re.match(a, "toyz5566@gmail.com") 
In [65]: r.group(1)                  
Out[65]: 'toyz5566'
In [66]: r.group(2)                   
Out[66]: 'gmail'
In [67]: r.group(3)                    
Out[67]: 'com'

# (?P<name>) > 命名
# (?P=name) > 提取
# P = Parameter
In [24]: a = r"<(?P<key1>.+)><(?P<key2>.+)>.+</(?P=key2)></(?P=key1)>"
In [25]: b = "<html><h1>Toyz</h1></html>"
In [26]: re.match(a, b)               
Out[26]: <re.Match object; span=(0, 26), match='<html><h1>Toyz</h1></html>'>
In [27]: b = "<html><h1>Toyz</h1></ht>"
In [28]: re.match(a, b) 
```

#### Search

```python
# Search 只要有就行
In [27]: b = "<html><h1>Toyz</h1></ht>"
In [29]: re.search(r"Toyz", b)         
Out[29]: <re.Match object; span=(10, 14), match='Toyz'>

# ^在此就有明顯作用
In [30]: re.search(r"^Toyz", b)
In [31]: b = "Toyz</h1></ht>"
In [33]: re.search(r"^Toyz", b)      
Out[33]: <re.Match object; span=(0, 4), match='Toyz'>
```

#### findall

```python
In [34]: b = "Toyz</h1></ht>Toyz"

# Search找到第一個即停止
In [35]: re.search(r"Toyz", b)          
Out[35]: <re.Match object; span=(0, 4), match='Toyz'>

# findall 搜完才停
In [36]: re.findall(r"Toyz", b)        
Out[36]: ['Toyz', 'Toyz']
```

#### sub: 匹配替換

```python
# re.sub("搜", "改", "搜尋內容")
In [37]: re.sub(r"wet", "dream", "wet abc apple wet") 
Out[37]: 'dream abc apple dream'
In [38]: re.sub(r"\d+", "50", "apple=1000, banana=20")                                   
Out[38]: 'apple=50, banana=50'

# 實際需求: 搜到哪個做相應修改
# 第二個參數可以接收函數!
# 概念
In [40]: def replace(ret): 
    ...:     print(ret.group()) 
    ...:     return "50" 
    
# replace 被調用過兩次
In [41]: re.sub(r"\d+", replace, "apple=1000, banana=20")                                
1000
20
Out[41]: 'apple=50, banana=50'

# 完成
In [42]: def replace(ret): 
    ...:     r = int(ret.group()) + 50 # ret.group()返回str要改
    ...:     return str(r) # 傳回去要改回str
    ...:            

In [43]: re.sub(r"\d+", replace, "apple=1000, banana=20")                                
Out[43]: 'apple=1050, banana=70'

# 解題: 把標籤都去掉
In [52]: s = """<div> 
    ...:         <p>岗位职责：</p> 
    ...: <p>完成推荐算法、数据统计、接口、后台等服务器端相关工作</p> 
    ...: <p><br></p> 
    ...: <p>必备要求：</p> 
    ...: <p>良好的自我驱动力和职业素养，工作积极主动、结果导向</p> 
    ...: <p>&nbsp;<br></p> 
    ...: <p>技术要求：</p> 
    ...: <p>1、一年以上 Python 开发经验，掌握面向对象分析和设计，了解设计模式</p> 
    ...: <p>2、掌握HTTP协议，熟悉MVC、MVVM等概念以及相关WEB开发框架</p> 
    ...: <p>3、掌握关系数据库开发设计，掌握 SQL，熟练使用 MySQL/PostgreSQL 中的一种<br></
    ...: p> 
    ...: <p>4、掌握NoSQL、MQ，熟练使用对应技术解决方案</p> 
    ...: <p>5、熟悉 Javascript/CSS/HTML5，JQuery、React、Vue.js</p> 
    ...: <p>&nbsp;<br></p> 
    ...: <p>加分项：</p> 
    ...: <p>大数据，数理统计，机器学习，sklearn，高性能，大并发。</p> 
    ...:  
    ...:         </div>"""
    
# 思考: 
# 有<p></p><div></div><br>,
# 皆以<英文字符>組成 > <\w+>
# 還有一些有/ > </?\w+>
In [55]: re.sub("</?\w+>", "", s)      
Out[55]: '\n        岗位职责：\n完成...'
```

#### split: 匹配切割

```python
In [59]: s = "apple,bana,cat,dog:daGG-haha"
In [60]: re.split(r",|:|-", s)       
Out[60]: ['apple', 'bana', 'cat', 'dog', 'daGG', 'haha']

In [61]: s = "applebanana"
In [62]: re.split("a", s)              
Out[62]: ['', 'ppleb', 'n', 'n', '']
```

#### 貪婪 > 在滿足別人的情況下做最大匹配

```python
# 情況一: 剛剛的解標籤
In [63]: s = """<div> ...</div>"""
In [65]: re.sub(r"<.+>", "", s)      
Out[65]: '\n        \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n        '
# 理論上, 我們想要換掉所有<>裡的東西, 但算法認定每行最外圍的<>為一個,
# 例如"<p>必备要求：</p>" 的 "p>必备要求：</p"為內容還被替換
# 因為+為貪婪模式, 只要能匹配就一直往下走, 故在第一個>沒有停,
# 而是到了最後換行沒辦法走下去了才停

# 情況二: 
In [68]: s = "my phone number: 09-87-654321"
In [74]: r = re.match(r".+(\d+-\d+-\d+)", s) 
In [76]: r.group(1)                   
Out[76]: '9-87-654321' # 0不見了

In [77]: r = re.match(r"(.+)(\d+-\d+-\d+)", s)
In [79]: r.groups()                    
Out[79]: ('my phone number: 0', '9-87-654321') # 0被分到第一組
# 因為\d+是至少有一個, 故第一組只留一個給第二組

# 非貪婪 > 在滿足別人的情況下做最小匹配
# 
# ?關掉+的貪婪, 
In [80]: r = re.match(r"(.+?)(\d+-\d+-\d+)", s) 
In [81]: r.groups()                  
Out[81]: ('my phone number: ', '09-87-654321')

In [82]: a = "apple5566gg"
In [87]: re.match(r"apple(\d+)", a).group(1)
Out[87]: '5566'
In [88]: re.match(r"apple(\d+?)", a).group(1)
Out[88]: '5' # 最小匹配
In [89]: re.match(r"apple(\d+)gg", a).group(1)
Out[89]: '5566'
In [90]: re.match(r"apple(\d+?)gg", a).group(1)
Out[90]: '5566' # 滿足gg

# 實作: 拿出照片
In [99]: s = """<img data-original="https://rpic.douyucdn.cn/appCovers/2016/11/13/1213973
    ...: _201611131917_small.jpg" src="https://rpic.douyucdn.cn/appCovers/2016/11/13/1213
    ...: 973_201611131917_small.jpg" style="display: inline;">""" 

In [102]: re.search(r"https.+?\.jpg", s).group()
Out[102]: 'https://rpic.douyucdn.cn/appCovers/2016/11/13/1213973_201611131917_small.jpg'
```

練習

```python
# 練習一:
# 
# 將
# http://www.interoem.com/messageinfo.asp?id=35
# http://3995503.com/class/class09/news_show.asp?id=14
# http://lib.wzmc.edu.cn/news/onews.asp?id=769
# http://www.zy-ls.com/alfx.asp?newsid=377&id=6
# http://www.fincm.com/newslist.asp?id=415
# 
# 變成
# http://www.interoem.com/
# http://3995503.com/
# http://lib.wzmc.edu.cn/
# http://www.zy-ls.com/
# http://www.fincm.com/
# 
# 思考:
# 取要砍掉的東西不容易, 故應該取要留下的,
# 並直接砍掉全部後返回留下的東西

In [124]: s                                 
Out[124]: 'http://www.interoem.com/messageinfo.asp?id=35'
In [127]: re.sub(r"(http://.+?/).+", lambda x:x.group(1), s)
Out[127]: 'http://www.interoem.com/'

# 練習2:
# hello world ha ha 找出所有單詞

In [134]: s = "hello world ha ha"
In [135]: re.split(r" ", s)                   
Out[135]: ['hello', 'world', 'ha', 'ha']
In [137]: re.findall(r"\b[a-z]+\b", s)         
Out[137]: ['hello', 'world', 'ha', 'ha']
```

