正規表現覚え書き(python)

完全な覚え書きメモ
参考：[https://ai-inter1.com/python-regex/#st-toc-h-1]

```python:正規表現例
import re
URL = "http://www.amazon.co.jp/dp/B07T9TCPZX"
pattern = "https?://[^/]+/"
res = re.match(pattern, URL)
print(res.group()) # パターンにマッチした文字列を取得する
print(res.span()) # マッチした文字列の開始・終了位置を取得する
print(res.start()) # 開始位置を取得する
print(res.end()) # 終了位置を取得する
# http://www.amazon.co.jp/
# (0,24)
# 0
# 24
```

# 正規表現の関数
## `match`
>先頭の文字列からパターンに一致するものを検索する。<br>先頭から続いている物のみなので、途中で一致は検知しない。

```python:match
# re.match(パターン, 検索文字列)
res = re.match("x.z","xyz") # "x.y" = x任意の1文字z
print(res)
# <re.Match object; span=(0, 3), match="xyz">
res = re.match("x.z","abcxyz") # "x.y" = x任意の1文字z
print(res)
# None
```

## `search`
>先頭からとは限らずパターンに一致するものを文中から検索、返す値は一つ目のみ。

```python:search
# re.search(パターン, 検索文字列)
res = re.search("x.z","axyz")
print(res)
# <re.Match object; span=(1, 4), match='xyz'>
```

## `findall`
>パターンに一致したものを全てリストで返す、開始・終了位置は取得不可能。

```python:findall
# re.findall(パターン, 検索文字列)
res = re.findall("x.z", "axyz bxaz xbz xcz")
print(res)
# ['xyz', 'xaz', 'xbz', 'xcz']
```

## `finditer`
>パターンに一致するものを全てmatchオブジェクト形式で取得可能。イテレータとして返り値が貰える。

```python:finditer
# re.finditer(パターン, 検索文字列)
res = re.finditer("x.z", "axyz bxaz xbz xcz")
print(res)
for i in res:
    print(i)
# <callable_iterator object at 0x0000018C3B103898>
# <re.Match object; span=(1, 4), match='xyz'>
# <re.Match object; span=(6, 9), match='xaz'>
# <re.Match object; span=(10, 13), match='xbz'>
# <re.Match object; span=(14, 17), match='xcz'>
```

## `fullmatch`
>文字列全体が一致しているかを確認。一致していた場合はmatch形式でreturn。

```python:fullmatch
# re.fullmatch(パターン, 検索文字列)
res = re.fullmatch("x.z", "xaz")
print(res)
# <re.Match object; span=(0, 3), match='xaz'>
```

## `sub`
>パターンに一致した文字列を置き換える、一致しない場合は入力値を返答。<br>置き換えは数値でも可能。

```python:sub
# re.sub(パターン, 置換する文字列,検索文字列)
res = re.sub("x.z", "OOO","abcxazdef")
print(res)
# abcOOOdef
```

# 正規表現記法(パターンの書き方)
## 任意の1文字 `.`

- 任意の1文字を表す
- `x.y = xyz`
## 文章の先頭から検索させる `^`
- 文章の頭からの検索を強制させる
- 頭から一致していない場合はNone

```python:^
res = re.search("^ab", "abcde")
print(res)
# <_sre.SRE_Match object; span=(0, 2), match='ab'>
res = re.search("^ab", "xyz abcde")
print(res)
# None
```

## 文章の末尾から検索させる `$`
- 文章の末尾からの検索を強制させる
- 末尾から一致していない場合はNone

```python:$
res = re.search("ab$", "cdeab")
print(res)
# <_sre.SRE_Match object; span=(3, 5), match='ab'>
res = re.search("ab$", "abcde")
print(res)
# None
```

## 0回もしくは1回繰り返し `?`
- 直前の文字が0回もしくは1回繰り返されている
- 要は直前の文字が有る場合と無い場合が当てはまる

```python:?
res = re.search("xy?", "x")
print(res.group())
# x
res = re.search("xy?", "xy")
print(res.group())
# xy
res = re.search("xy?", "xyy")
print(res.group())
# xy
```

## 0回以上繰り返し `*`
- 直前の文字が0回以上繰り返している場合

```python:*
res = re.search("xy*", "x")
print(res)
# x
res = re.search("xy*", "xyyyyyyyyyyyyyyy")
print(res)
# xyyyyyyyyyyyyyyy
```

## 1回以上繰り返し `+`
- 直前の文字が1回以上繰り返している場合

```python:+
res = re.search("xy+", "x")
print(res)
# None
res = re.search("xy*", "xyyyyyyyyyyyyyyy")
print(res)
# xyyyyyyyyyyyyyyy
```

## n回繰り返し `{n}`
- 直前の文字がn回以上繰り返している場合

```python:{n}
res = re.search("xy{2}", "xyy")
print(res)
# xyy
res = re.search("xy{2}", "xyyyyyyyyyyyy")
print(res)
# None
```

## n回以上繰り返し `{n,}`
- 直前の文字がn回以上繰り返している場合

```python:{n,}
res = re.search("xy{2,}", "xyy")
print(res)
# xyy
res = re.search("xy{2,}", "xyyyyyyyyyyyy")
print(res)
# xyyyyyyyyyyyy
```

## n回以上、m回まで繰り返し `{n,m}`
- 直前の文字がn回以上でm回まで繰り返している場合

```python:{n,}
res = re.search("xy{2,4}", "xyy")
print(res)
# xyy
res = re.search("xy{2,4}", "xyyyyyyyyyyyy")
print(res)
# xyyyy 残りのyは当てはまらない
```

## 文字列のグルーピング化 `(~~~)`
- 文字列をグルーピングする
- グルーピング化された文字列に繰り返しを充てることで、その文字列が繰り返されたかを確認可能。
- 例.`(ab){2} = abab`

```python:()
res = re.search("x(yz)+", "xyzyz")
print(res)
# xyzyz
res = re.search("x(yz)+", "xyzyzOyz")
print(res)
# xyzyz 0以下は連続が遮られたので一致しない
```

## 全ての数字 `[0-9]`
- 0-9のうちの数字であれば一致する

```python:[0-9]
res = re.search("a[0-9]", "a1")
print(res)
# a1
res = re.search("a[0-9]+", "a123") # 連続で数値群を表せる
print(res)
# a123
```

## 全てのアルファベット(大文字小文字含む) `[a-zA-Z]`
- 大文字小文字を含む全てのアルファベット
- 小文字のみの場合は `[a-z]`
- 大文字のみの場合は `[A-Z}`

```python:[a-zA-Z]
res = re.search("[a-zA-Z]", "a")
print(res)
# a
res = re.search("[a-zA-Z]+", "aBcDeFg") # 連続でアルファベットの文字列を表せる
print(res)
# aBcDeFg
```

## 全てのアルファベットと数字 `[a-zA-Z0-9]`
- 大文字小文字を含む全てのアルファベットと数字

```python:[a-zA-Z0-9]
res = re.search("[a-zA-Z0-9]+", "ABC123")
print(res)
# ABC123
res = re.search("[a-zA-Z]+", "ABC_123") # 連続でアルファベットの文字列を表せる
print(res)
# ABC
```

## 全てのアルファベットと数字以外の記号 `[^a-zA-Z0-9]`
- ^(ハット)を付けることで上記`全て系統`の条件を否定できる
- 上記`全て系統`を`集合`と言う
- アルファベット以外が欲しかったら`[^a-zA-Z]`の様な感じ

```python:[^a-zA-Z0-9]
res = re.search("[^a-zA-Z0-9]+", "_?")
print(res)
# _?
res = re.search("[a-zA-Z]+", "ABC_123") # 連続でアルファベットの文字列を表せる
print(res)
# ABC
```

## 集合と集合の否定 `[ ] or [^ ]`
- `[]`で囲むことで集合が作れる
- 例えばAと1と?のどれかが有れば一致としたい場合 `[A1?]`とする
- 逆にAと1と?のどれか以外であれば一致としたい場合 `[^A1?]`とする
- 集合の否定は何かが来るまでの様な形でも使える
- 例.`[^/]+`で`abcd//`を検索すると`abcd`まで抜き出せる
- なにかの連続までとしたいときは`[^/{2}]+`

```python:
res = re.search("[A1?]+", "A??11AABA")
print(res)
# A??11AA
res = re.search("[^/]+", "aiueo/kaki") # /まで
print(res)
# aiueo
res = re.search("[^/{2}]+", "aiueo//kaki") # //まで
print(res)
# aiueo
```

## ある文字列よりも前を抜き出す `(.*)(?=abc)`
- `(.*)(?=abc)`でこの文字列よりも前を抜き出す

```python:(.*)(?=abc)
res = re.search("(.*)(?=abc)", "ababba//abc")
print(res)
# ababba//
```

## ある文字列よりも後を抜き出す `(?<=abc)(.*)`
- `(?<=abc)(.*)`でこの文字列よりも前を抜き出す

```python:(?<=abc)(.*)
res = re.search("(?<=abc)(.*)", "ababcba//abc")
print(res)
# ba//abc 一番初めに来たabcから以下を取り出している
```

## ORを使いたいとき `(a|b)`
- グルーピングで囲んで条件を付けることで文字列の条件式ができる
- `あいうえお|かきくけこ|さしすせそ`の様な形も可能
- 文字列の場合は`((ab)|(ac))`
- `[ab]c`の様に`[]`で囲むことでも可能

```python:(a|b)
res = re.search("(a|b)+d", "abacd")
print(res)
# abacd
res = re.search("[ab]d", "bd")
print(res)
# bd
```

## 特殊シーケンス(よく使われる記法は以下で置き換えられる)
#### 全ての数字(`[0-9]`) = `\d`
#### 全ての数字以外(`[^0-9]`) = `\D`
#### 全ての英数字(大文字小文字)とアンダーバー(`[a-zA-Z0-9_]`) = `\w`
#### 全ての英数字(大文字小文字)とアンダーバー以外(`[^a-zA-Z0-9_]`) = `\W`
#### 空白 = `\s`
#### 空白以外 = `\S`
#### 文字列の先頭(`^`) = `\A`
#### 文字列の末尾(`$`) = `\Z`

# パターンをコンパイル
- パターンをコンパイルすることで、繰り返しパターンを描くことを省略できる
- `pattern = re.compile(パターン)`この様に`.compile()`でコンパイルする

```python:compile
# コンパイルしたものは以下の手法で使いまわせる
pattern = re.compile("xy+")
res = pattern.search("xyyyyyyyya") # コンパイルした変数.正規関数のメソッド("検索したい文章")
print(res.group())
```

# 特殊文字を調べたい場合
- パターンで使われるような特殊文字を検索したい場合は、その直前にバックスラッシュを付ける
- `\+`や`\{}`の様な感じ
- 特殊文字を含んだ文字列の場合は`(\{abc})`の様にグルーピングで囲む

# 貪欲マッチ・非貪欲マッチ
何回以上と繰り返しを設定するパターンにおいて、条件が一致する小さい文字列と、それを含む条件が一致する大きい文字列では、大きい文字列がreturnされる。(findallでも大きいものだけが検知される)
大きいものではなく最も小さいものを検索したい場合は、各繰り返し記号の直後に`?`を付ける

```python:貪欲・非貪欲
# 貪欲の場合
text = "<html><head></head><body></body></html>"
res = re.search("<.*>",text)
print(res)
# <html><head></head><body></body></html>
# 非貪欲の場合
text = "<html><head></head><body></body></html>"
res = re.search("<.*?>",text)
print(res)
# <html>
```

# フラグ引数
パターンを記述する際にフラグ引数を加える事により、検索条件をパターン以外で操作することができる。
- `res = re.関数("パターン", "検索文字列", flags="フラグ引数")`
- `pattern = re.compile("パターン",flags="フラグ引数")`
## 大文字・小文字を区別しないフラグ引数
- `re.IGNORECAE`を使うことにより、文字列を検索する際に大文字小文字を判別しなくなる

```python:IGNORECAE
res = re.search("[a-z]+","abcABC",flags=re.IGNORECASE)
print(res.group())
# abcABC
```

## 先頭・末尾からの検索を各行ごとにする
- `re.MULTILINE`を使うことにより、検索の開始位置を各行ごとにする(改行が含まれるときのみ)
- `^`や`$`を使用した場合、通常は改行があっても最も一番初めに来る文字列のみ参照される
- `re.MULTILINE`により各行の先頭・末尾からの検索が可能となる

```python:MULTILINE
text = """aiu123
kakiku123
sasisu123"""
res = re.findall("^[a-z]+", text)
print(res)
# ["aiu","kakiku","sasisu"]
```
## パターンにコメントを追加する
- `re.VERBOSE`を使うことにより、`#`以降がコメントとして認識される
- 改行を行うとコメントとしての判断はなくなる

```python:VERBOSE
# /をエスケープして検索している事に注意
pattern = re.compile("""
                      [0-9]{4}\/    #年/
                      [0-9]{1,2}\/  #月/
                      [0-9]{1,2}    #日
                      """, re.VERBOSE)
res = pattern.findall("The date is 2020/10/23")
print(res)
# ["2020/10/23"]
```

## パターンをグループ化し命名、それを参照する
- `(?P<グループ名>パターン)`で命名されたグルーピング化を行うことができる
- `res = pattern.関数("検索したい文字列)`でパターンを適用し、`res.関数("グループ名")`で命名グループに合った結果を出力する

```python:grouping
pattern = re.compile("""
                     (?P<year>[0-9]{4})\/     #年/
                     (?P<month>[0-9]{1,2})\/  #月/
                     (?P<date>[0-9]{1,2})     #日
                     """, re.VERBOSE)
res = pattern.findall("The date is 2020/10/23") #ここでパターン中のグルーピングを適用し
print(res.group("year")) # ここで命名したものを呼んでいる
print(res.group("month"))
print(res.group("date"))
# 2020
# 10
# 30
```