Pythonではじめる正規表現

[link-1]: https://docs.python.org/ja/3/library/re.html "reドキュメント"
[link-2]: https://docs.python.org/ja/3/howto/regex.html "正規表現HOW TO"
[link-3]: https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Regular_expressions/Capturing_group "MDN Docs キャプチャグループ"
## はじめに
正規表現を勉強するときに、どこから手をつけていいのか分からなかったので、実際に使う上で生じた使い方や疑問をまとめました。

## 基本的な使い方
Pythonの標準モジュール`re`をインポートします。
```python
import re
```
以降はインポート文を省略します。
正規表現パターンには2通りの書き方があります。正規表現パターン(`pattern`)が文字列(`string`)にマッチするかどうかを調べる書き方は以下のようになります。
```python
# コンパイルする
prog = re.compile(pattern)
result = p.match(string)
```
```python
# コンパイルしない
result = re.match(pattern, string)
```
使い分けについては公式リファレンスに説明があります。

> re.compile() やモジュールレベルのマッチング関数に渡された最新のパターンはコンパイル済みのものがキャッシュされるので、一度に正規表現を少ししか使わないプログラムでは正規表現をコンパイルする必要はありません。

つまり、1つの正規表現パターンを使い回すときはコンパイルして、使い捨てのパターンのときにコンパイルする必要はありません。

## マッチングを実行するメソッド
よく使うであろうものだけを紹介します。その他のものは[reドキュメント][link-1]にあります。
### `match()`
**文字列の先頭**で正規表現パターンとマッチすれば、`Match`オブジェクトを返します。マッチしなければ`None`を返します。
```python
# コンパイルする場合
p = re.compile("ab+")
print(p.match("abbc"))  # <re.Match object; span=(0, 3), match='abb'>

# コンパイルしない場合
print(re.match("ab+", "abbc"))  # <re.Match object; span=(0, 3), match='abb'>

# 文字列にマッチしない場合
print(re.match("ab+", "cabbc"))  # None
```
`Match`オブジェクトのメソッドで重要なものは以下のものです。
|メソッド|説明|
|---|---|
|group()|マッチした文字列を返す(str)|
|start()|マッチの開始位置を返す(int)|
|end()|マッチの終了位置を返す(int)|

ただし、`match()`メソッドは文字列の先頭でマッチ判定するので、`start()`の返り値は必ず`0`になります。
```python
m = re.match("ab+", "abbc")
print(m.group())  # "abb"
print(m.start())  # 0
print(m.end())  # 3
```
### `search()`
文字列の先頭から走査して、**最初にパターンマッチした箇所**に対して`Match`オブジェクトを返します。マッチしなければ`None`を返します。
```python
# コンパイルする場合
p = re.compile("ab+")
print(p.search("cabbc"))  # <re.Match object; span=(1, 4), match='abb'>

# コンパイルしない場合
print(re.search("ab+", "cabbc"))  # <re.Match object; span=(1, 4), match='abb'>

# 文字列にマッチしない場合
print(re.search("ab+", "cbc"))  # None
```
`match()`との違いは、`match()`が文字列の先頭のみ判定するのに対して、`search()`はパターンが文字列のどこにあっても判定することです。
### `findall()`
パターンマッチした箇所をすべて取り出してリストにします。
```python
# コンパイルする場合
p = re.compile("ab+")
print(p.findall("abc-abbc"))  # ['ab', 'abb']

# コンパイルしない場合
print(re.findall("ab+", "abc-abbc"))  # ['ab', 'abb']

# 文字列にマッチしない場合
print(re.findall("ab+", "acd"))  # []
```

### `sub()`
パターンマッチした文字列を置換した文字列を返します。
例えば下の例では、パターン`ab+`にマッチする文字列(`ab`, `abb`)を`xyz`に置き換えます。
```python
# コンパイルする場合
p = re.compile("ab+")
print(p.sub("xyz", "abc-abbc"))  # 'xyzc-xyzc'

# コンパイルしない場合
print(re.sub("ab+", "xyz", "abc-abbc"))  # 'xyzc-xyzc'
```
## 特殊文字
特殊文字とは、グルーピングや繰り返しなど、特別な意味を持つものを言います。
正規表現で使える特殊文字の一覧は次のとおりです。
`. ^ $ * + ? { } [ ] \ | ( )`
これらの特殊文字自身をマッチさせるときは、バックスラッシュ`\`を使います。
```python
# .*は任意の文字列にマッチ
print(re.search("\[.*\]", "example: [xyz]"))  # <re.Match object; span=(9, 14), match='[xyz]'>
```
### 改行以外の任意の1文字にマッチ `.`
```python
print(re.search("a.", "abca"))  # <re.Match object; span=(0, 2), match='ab'>
```
改行(`\n`)もマッチさせたい場合は、`re.DOTALL`を使います。
```python
# コンパイルする場合
p = re.compile("a.", re.DOTALL)
print(p.search("a\nb"))  # <re.Match object; span=(0, 2), match='a\n'>

# コンパイルしない場合
print(re.search("a.", "a\nb", re.DOTALL))  # <re.Match object; span=(0, 2), match='a\n'>
```
### 文字列の先頭または末尾にマッチ `^` `$`
文字列の先頭
```python
print(re.search("^a", "abc"))  # <re.Match object; span=(0, 1), match='a'>
print(re.search("^a", "babc"))  # None
```
文字列の末尾
```python
print(re.search("a$", "bca"))  # <re.Match object; span=(2, 3), match='a'>
print(re.search("a$", "bcab"))  # None
```
### 直前の文字を0回以上繰り返す `*`
```python
print(re.search("ba*", "baaac"))  # <re.Match object; span=(0, 4), match='baaa'>
print(re.search("ba*", "bc"))  # <re.Match object; span=(0, 1), match='b'>
```
`.`と組み合わせると、任意の長さの文字列をマッチさせることができます。
```python
print(re.search("a.*", "xabcabc"))  # <re.Match object; span=(1, 7), match='abcabc'>
```
### 直前の文字を1回以上繰り返す `+`
上記の`*`と比べると違いが分かりやすいです。
```python
print(re.search("ba+", "baaac"))  # <re.Match object; span=(1, 5), match='baaa'>
print(re.search("ba+", "bc"))  # None
```
### 直前の文字を0回または1回繰り返す `?`
上記の`*` `+`と比べてみてください。
```python
print(re.search("ba?", "baaac"))  # <re.Match object; span=(0, 2), match='ba'>
print(re.search("ba?", "bc"))  # <re.Match object; span=(0, 1), match='b'>
```
### グルーピング `()`
繰り返しの`*` `+` `?`は直前の文字だけでしたが、複数文字を繰り返したい場合があります。
そのときには文字列を`()`で囲むことにより解決します。
```python
print(re.search("(ba)+", "babaxyz"))  # <re.Match object; span=(0, 4), match='baba'>
```
### どれかの文字にマッチ `[]`
```python
print(re.search("[abc]", "xxbx"))  # <re.Match object; span=(2, 3), match='b'>
print(re.search("[abc]", "xxcx"))  # <re.Match object; span=(2, 3), match='c'>
```
ハイフン`-`をつけると、連続した文字の範囲を表すことができます。
```python
# 英小文字にマッチ
print(re.search("[a-z]", "ApB"))  # <re.Match object; span=(1, 2), match='p'>

# 0-9の数字にマッチ
print(re.search("[0-9]", "abc3xyz"))  # <re.Match object; span=(3, 4), match='3'>
```
### バックスラッシュを用いた文字集合
`\`を使ったものの中には、いくつか特別なパターンを表すものがあります。  
代表的なものだけ抜粋します。
|文字列|意味|マッチする|マッチしない|
|---|---|---|---|
|\d|任意の十進数|0, 6|a, A|
|\D|任意の非数字文字|a, A|1, 5|
|\s|任意の空白文字|\n, \t|a, 3|
|\w|任意の英数文字と下線|a, _|\n|

Pythonではバックスラッシュ文字はエスケープ文字として機能するため、次のように書く必要があります。
```python
print(re.search("\\d+", "This year is 2000."))  # <re.Match object; span=(13, 17), match='2000'>
```
バックスラッシュが続くと可読性が下がるので、Raw文字で書く方が分かりやすいです。
```python
print(re.search(r"\d+", "This year is 2000."))  # <re.Match object; span=(13, 17), match='2000'>
```
## マッチした一部分を取り出す方法
ときどき、パターン全体ではなく、一部分だけ欲しいときがあります。  
例えば、`Yamada Taro`という文字列から、苗字だけ取り出したいとします。
このとき、欲しい部分を`()`でくくると抽出することができます。
```python
m = re.search(r"(\w+) (\w+)", "Yamada Taro")
print(m.group(1))  # 'Yamada'
print(m.group(2))  # 'Taro'
```
これはキャプチャグループと呼ばれている機能です。
## 貪欲マッチ・非貪欲マッチ
`*`や`+`などの繰り返しパターンは、できるだけ長い文字列にマッチしようとします。
そのため、以下の例で前述のキャプチャグループを使って、名前だけ取り出そうとしたときに思ったようにできません。
```python
# []は特殊文字なのでバックスラッシュが必要
m = re.search(r"\[name: (.*)\]", "[name: Suzuki][email: abc@example.com]")
print(m.group(1))  # 'Suzuki][email: abc@example.com'
```
これを貪欲マッチと呼びます。
上記の例では、`Suzuki`と`email`の間にある`][`で区切りたいです。
このようなとき、繰り返しパターンの後ろに`?`をつけると、できるだけ短い文字列にマッチしようとするので、取り出すことができます。
```python
m = re.search(r"\[name: (.*?)\]", "[name: Suzuki][email: abc@example.com]")
print(m.group(1))  # 'Suzuki'
```
短い文字列にマッチさせるのを非貪欲マッチと呼びます。
## 参考
[reリファレンス][link-1]
[正規表現HOW TO][link-2]
[MDN Docs キャプチャグループ][link-3]