Pythonの正規表現まとめ

# はじめに

個人的まとめ

# raw string 記法

- PythonのreモジュールはPerlに見られる正規表現マッチング操作と同様のものを提供するもの
- DjangoなどでもURLパターンの指定の際などに使われる

[re - Regular expression operations - Python 3.9.7 documentation](https://docs.python.org/ja/3/library/re.html)

[Regular Expression HOWTO - Python 3.9.7 documentation](https://docs.python.org/ja/3/howto/regex.html#regex-howto)

- 多くの場合 Python コードの中の正規表現はこの raw string 記法を使って書かれる

```python
\section    # マッチさせるテキスト
\\section    # re.compile() のためのバックスラッシュエスケープ
"\\\\section"    # 文字列リテラルのためのバックスラッシュエスケープ
```

- `'\'`は`re.compile()`に通すために`'\\'`とバックスラッシュエスケープする必要がある
- `'\\'`はさらにコードに書くためにそれぞれの`'\'`をバックスラッシュエスケープする必要があるため、`'\\\\'`となる
- `'r'`を文字列リテラルの先頭に書くことでバックスラッシュは特殊文字扱いされなくなるため、`'\\\\'`は`'r"\\"'`とすることができる

```python
"\n"
r"\n"
```

- `"\n"`は改行文字を表す
- `r"\n"`は2つの文字`'\'`と`'n'`を含む文字列となる

```python
"ab*"
r"ab*"
```

```python
"\\\\section"
r"\\section"
```

```python
"\\w+\\s+\\1"
r"\w+\s+\1"
```

- 他にも上記のようにそれぞれ`r''`を使うことができる

# 特殊文字

## 一覧

以下のような特殊文字が存在する．それぞれ詳しくみていく．

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

## []

- マッチさせたい文字の集合を表す
- 文字を個別にリストするか、または二つの文字を '-' でつなげて文字を範囲で与えることもできる

```python
[abc]
[a-c]
```

- `'a'`, `'b'`, `'c'`のどれかにマッチする

```python
[a-z]
```

- 小文字のアルファベットのどれか一文字にマッチする

```python
[akm$]
```

- `'a'`, `'k'`, `'m'` または `'$'` にマッチ
- `'$'`のような特殊文字も、`[]`内では普通の文字として扱われる

```python
[^5]
```

- `'5'` 以外の文字にマッチ
- `[]`内にある`^`は補集合を表す
- 補集合を取る ことで、文字クラス内のリストにない文字に対してマッチさせられる

```python
[5^]
```

- `'5'` や `'^'` にマッチ
- `^`が`[]`内の先頭以外にある場合は特別な意味を持たなくなる

```python
\[
```

```python
\\
```

- それぞれ`[` , `\`にマッチ
- バックスラッシュを使うことで特殊文字をエスケープできる

```python
\d
[0-9]
```

- 任意の十進数とマッチ
- 特殊シーケンスの一種

```python
\D
[^0-9]
```

- 任意の非数字文字とマッチ
- 特殊シーケンスの一種

```python
\s
```

- 任意の空白文字とマッチ
- 特殊シーケンスの一種

```python
\S
```

- 任意の非空白文字とマッチ
- 特殊シーケンスの一種

```python
\w
[a-zA-Z0-9_]
```

- 任意の英数文字および下線とマッチ
- 特殊シーケンスの一種

```python
\W
[^a-zA-Z0-9_]
```

- 任意の非英数文字とマッチ
- 特殊シーケンスの一種

```python
[\s,.]
```

- 空白文字や `','` または `'.'` にマッチ
- 特殊シーケンスは`[]`に入れることができる

```python
.
```

- 改行文字を除く任意の文字にマッチ
- 改行文字に対してもマッチさせる代替モードもある

```python
a/{1,3}b
```

- `'a/b'`, `'a//b'`, `'a///b'` にマッチ
- `{m, n}`で最低m回、最大でn回の直前文字の繰り返しにマッチする

```python
a/{2,}b
```

- `'a//b'`, `'a///b'`, `'a//////////b'`などにマッチ
- `{m, n}`のnを省略した形で、その場合は上限が無限大になり、m回以上の直前文字の繰り返しとマッチする

```python
a/{,3}b
```

- `'ab'`, `'a/b'`, `'a//b'`, `'a///b'` にマッチ
- `{m, n}`のmを省略した形で、その場合は下限が0になり、最低0回、最大でn回の直前文字の繰り返しにマッチする

```python
ca*t
ca{0,}t
```

- `'ct'`, `'cat'`, `'caaat'`などにマッチ
- 直前文字の0回以上の繰り返しにマッチ

```python
a[bcd]*b
a[bcd]{0,}b
```

- `'ab'`, `'acb'`, `'abcb'`, `'adbb'`などにマッチ
- `[bcd]*`とは`'b'`, `'c'`, `'d'`のいづれかの文字の0回以上の繰り返しを意味する

```python
ca+t
ca{1,}t
```

- `'cat'`, `'caaat'`などにマッチ
- 直前文字の0回以上の繰り返しにマッチ
- `'ct'`にはマッチしないことに注意

```python
home-?brew
home-{0,1}brew
```

- `'homebrew'` , `'home-brew'` にマッチ
- 直前文字の0または1回の繰り返しにマッチ
- 2回以上の繰り返しにはマッチしない

```python
Crow|Servo
```

- `'Crow'`または`'Servo'`にマッチ
- `A`, `B`がそれぞれ特殊文字でない文字列のとき、`A|B`は`A`または`B`にマッチする

```python
^From
```

- `'From Here to Eternity'`などの行頭`'From'`にマッチ
- `^`は行の先頭を意味する

```python
c$
```

- `'abc'`, `'abc\n'`などの行末`'c'`にマッチ
- `$`は行の末尾にマッチする
- 行の末尾とは、文字列の末尾か、または改行文字の直前と定義されている

```python
c\z
```

- `'abc'`などの行末`'c'`にマッチ
- `\z`は文字の末尾とのみマッチする
- `'abc\n'`の`'c'`とはマッチしない

```python
r'\bclass\b'
```

- `'no class at all'`や`'@class@'`などの単語`'class'`とマッチ
- `'classify'`などの`'class'`とはマッチしない
- 単語は英数文字のシーケンスとして定義されている
- 空白または非英数文字が単語の区切りになる
- raw stringを利用していない場合、Python は`\b`をバックスペースに変換し、正規表現は期待するものとマッチしなくなる

```python
\Bclass\B
```

- `'classify'`などの`'class'`とマッチする
- `'no class at all'`や`'@class@'`などの単語`'class'`とはマッチしない
- `\b`と逆で、現在の位置が単語の境界でないときにのみマッチ

# 使い方

```python
>>> import re
>>> p = re.compile('[a-z]+')
>>> p.match('tempo')
<re.Match object; span=(0, 5), match='tempo'>
>>> m = p.search('@@@tempo')
>>> m
<re.Match object; span=(3, 8), match='tempo'>
>>> re.search('[a-z]+', 'tempo')
<re.Match object; span=(0, 5), match='tempo'>
>>> m.group()
'tempo'
>>> m.start()
3
>>> m.end()
8
>>> m.span()
(3, 8)
>>> p.findall('@abc1@def2@ghi3@')
['abc', 'def', 'ghi']
```

- `match()`は文字列の開始位置がマッチするかどうかだけをチェックする
- `search()`は文字列全体をチェックする
- `re.search('検索文字列', '対象文字列')`という形もある
- `group()`は正規表現にマッチした文字列を返す
- `start()`はマッチの開始位置を返す
- `end()`はマッチの終了位置を返す
- `span()`はマッチの位置 (start, end) を含むタプルを返す
- `findall()`はマッチした文字列のリストを返す

```python
p = re.compile('[a-z]+')
m = p.match('tempo')
if m:
    print(m.group())
else:
    print('No match')
```

- Pythonファイルで実行するときは、変数にmatchオブジェクトを代入させたあと、変数が空かどうかを調べるという形が一般的