文字列の中から 「数字だけ」 「特定の形をした部分だけ」 を取り出したり、「入力が決まった形式か」を判定したり、こうした文字の並びのパターンを扱う道具が正規表現です。本記事では、Pythonで正規表現を使うための基本構文を、ひとつずつ例とともに整理しています。
使うのは標準ライブラリのreモジュールだけで、追加インストールは不要です。
目次
はじめに
正規表現とは
正規表現(regular expression、略してregex)は、文字列パターンを記号で表す書き方です。たとえば\d+は「1個以上の数字」を意味します。「探したいもののパターンを記号で書き、その形に当てはまる箇所を取り出す」のが基本的な発想です。Pythonでは標準ライブラリのreモジュールで使えます。
本記事では、各構文について「記号の意味」と「実際に動かした例」をセットで示します。
例を実行する道具としてはre.findallを一貫して使います。re.findall(パターン, 文字列)という形で使います。文字列からパターンに合う箇所をすべて見つけてリストで返す関数です。
import re
print(re.findall(r"\d+", "A1 B22"))
# ['1', '22'] ← 数字のかたまりを全部見つけてリストにするfindallは引数flagsを使うことでフラグを設定し、機能を変えることができます。
raw文字列(r"...")を使う
パターンを書くときは文字列の前にrを付けたraw文字列(r"\d+"のような書き方)を使うのが定番です。通常の文字列だと\がエスケープシーケンス(\nは改行、\tはタブなど)として先に解釈されて、正規表現にわたる前に意味が変わってしまいます。r"..."にすれば\がそのまま正規表現にわたるので、余計なトラブルを避けられます。
構文の一覧
この記事で扱う構文を、先に一覧で示します。細かい意味や使いどころは、表のあとの各節で例とともに記述しています。例の列は、「パターン("入力文字列")→re.findall」の結果の形で読みます。
| 分類 | 記号 | 意味 | 例 |
|---|---|---|---|
| 位置 | ^ | 文字列(フラグ変更で行)の先頭 | ^a("abc")→ ['a'] |
$ | 文字列(フラグ変更で行)の末尾 | c$("abc")→ ['c'] | |
\b | 単語の境界 | \bcat\b("cat category")→ ['cat'] | |
| 1文字 | . | 任意の1文字(改行を除く) | a.c("a-c")→ ['a-c'] |
\d / \D | 数字 / 数字以外の1文字 | \d("a1")→ ['1'] | |
\w / \W | 英数字とアンダースコア / それ以外の1文字 | \w("a!")→ ['a'] | |
\s / \S | 空白 / 空白以外の1文字 | \s("a b")→ [' '] | |
| 文字クラス | […] | かっこ内のどれか1文字 | [ab]("cabd")→ ['a', 'b'] |
[a-z] | 範囲指定 | [0-9]("a1b2")→ ['1', '2'] | |
[^…] | かっこ内"以外"の1文字 | [^0-9]("a1")→ ['a'] | |
| 繰り返し | * | 直前を0回以上 | ab*("a abb")→ ['a', 'abb'] |
+ | 直前を1回以上 | ab+("a abb")→ ['abb'] | |
? | 直前を0回または1回 | ab?("a ab")→ ['a', 'ab'] | |
{n}/ {n,m} | ちょうどn回 / n〜m回 | \d{2,3}("1 2345")→ ['234'] | |
*? / +? | */+などに?を付けると最短マッチ | a.*?b("axbyb")→ ['axb'] | |
| グループ・選択 | (…) | 取り出す部分を囲む(キャプチャ) | (\d+)円("80円")→ ['80'] |
| | AまたはB | 猫|犬("猫と犬")→ ['猫', '犬'] | |
| エスケープ | \. \( など | \を付けると、メタ文字を「文字そのもの」として扱う | \.("a.b")→ ['.'] |
注意点
\b(単語の境界)は日本語の単語の区切りには使えません。\bは\wと\Wの境目を探す仕組みですが、日本語は語の間に空白がなく、ひらがな・漢字なども\w扱いのため、語の途中に境界ができないからです(空白・記号・英数字と接する位置でのみ働きます)。\d・\w・\sはPython3では既定でUnicode基準で、全角数字・日本語・全角スペースにもマッチします。半角だけに限るにはre.ASCIIを使います。*・+・{n,m}はデフォルトで最長一致(貪欲)です。取りすぎるときは*?・+?・{n,m}?(最短)にします。^・$はデフォルトで文字列全体の先頭・末尾です。各行に効かせるにはre.MULTILINEが必要です。(...)の有無・数でre.findallの戻り値が変わります(グループが1個ならその中身、2個以上ならタプルのリスト)。
位置を表す(アンカー)
扱う記号
^ $ \b
「行の先頭」「単語の境目」など、文字そのものではなく「位置」を表す記号があります。これをアンカーと呼びます。
先頭と末尾
^は文字列全体(フラグ変更で行)の先頭、$は末尾を表します。
print(re.findall(r"^.", "abc")) # ['a'] 先頭の1文字
print(re.findall(r".$", "abc")) # ['c'] 末尾の1文字複数行のテキストで「各行の先頭・末尾」に効かせたいときはre.MULTILINEというフラグを渡します。
text = "one\ntwo"
print(re.findall(r"^.", text)) # ['o'] 文字列の先頭だけ
print(re.findall(r"^.", text, re.MULTILINE)) # ['o', 't'] 各行の先頭このように、^・$の対象は、フラグで行単位にも文字列単位にも切り替わります。
単語境界
\bは単語の境界(語を構成する文字と、それ以外との境目)を表します。「単語そのもの」を狙うときに使います。
print(re.findall(r"\bcat\b", "the cat category")) # ['cat']\bcat\bは前後が単語の境界になっているcatだけにマッチするので、categoryの中のcatは拾いません。なお、日本語の単語の区切りには使えません。日本語の場合は空白・記号・英数字と接する位置でのみ働きます。
1文字にマッチする
扱う記号
. \d \D \w \W \s \S
次は「任意の1文字」「数字1文字」のように、1文字に当てはまる記号です。
.は任意の1文字にマッチします(既定では改行を除く)。
print(re.findall(r"a.c", "abc a c a-c")) # ['abc', 'a c', 'a-c']a.cは「a、任意の1文字、c」なので、間の1文字が何であっても拾えます。
数字・空白などには、それぞれ専用の記号と、その否定(大文字版)があります。
print(re.findall(r"\d+", "A1 B22")) # ['1', '22'] \d:数字
print(re.findall(r"\D+", "A1 B22")) # ['A', ' B'] \D:数字以外print(re.findall(r"\w+", "id_42 あ!")) # ['id_42', 'あ'] \w:英数字とアンダースコア(日本語なども含む)
print(re.findall(r"\W+", "id_42 あ!")) # [' ', '!'] \W:それ以外print(re.findall(r"\S+", "a b\tc")) # ['a', 'b', 'c'] \S:空白以外(\s は空白)\sはスペース・タブ・改行などの空白文字に、その否定\Sは空白以外にマッチします。上の例では、空白(スペースとタブ)で区切られたかたまりを\S+で取り出しています。
なお、Python3では\d・\w・\sはデフォルトでUnicode基準になります。そのため\dは半角数字だけでなく全角数字(0〜9)にもマッチし、\wは日本語の文字もマッチ対象にします。半角だけに限定する方法は、フラグre.ASCIIを使います。
文字クラス([...])
扱う記号
[...] [a-z] [^...]
「このうちのどれか1文字」を自分で列挙したいときは、角かっこで囲む文字クラスを使います。
print(re.findall(r"[aeiou]", "regular")) # ['e', 'u', 'a'] かっこ内のどれか1文字[0-9]や[a-z]のように、ハイフンで範囲を指定できます。[0-9]は半角に限れば\dとほぼ同じ用途です(\dは全角も含みます)。
print(re.findall(r"[0-9]+", "tel 03-1234")) # ['03', '1234']先頭に^を置くと「かっこ内"以外"の1文字」という否定の意味になります。
print(re.findall(r"[^0-9]+", "03-1234")) # ['-'] 数字以外のかたまり文字クラスの中では、.や)などの記号は特別な意味を失い、ただの文字として扱われます。たとえば[.()]は「ピリオド・開きかっこ・閉じかっこのどれか」という意味で、エスケープは不要です。
繰り返し(量指定子)
扱う記号
* + ? {n} {n,m} *?/+?
直前の1個が「何回繰り返すか」を表す記号を量指定子と言います。*は0回以上、+は1回以上です。
print(re.findall(r"ab*", "a ab abb")) # ['a', 'ab', 'abb'] * は0回以上("a" も拾う)
print(re.findall(r"ab+", "a ab abb")) # ['ab', 'abb'] + は1回以上("a" は拾わない)両者の違いは「直前(ここではb)が0個でも許すか」です。ab*はbが0個のa単独も拾い、ab+はbが最低1個ないとマッチしません。
?は0回または1回(あってもなくてもよい)を表します。
print(re.findall(r"colou?r", "color colour")) # ['color', 'colour']uに?を付けたことで、colorとcolourの両方にマッチします。
回数を数で指定するには{n}(ちょうどn回)、{n,m}(n回以上m回以下)を使います。
print(re.findall(r"\d{2,4}", "1 22 333 55555")) # ['22', '333', '5555']\d{2,4}は2〜4桁の数字にマッチするので、1桁の1は拾わず、5桁の55555は先頭4桁の5555までを拾います。このように、{n,m}はできるだけ長い方にマッチします。
貪欲マッチと最短マッチ
*・+・{n,m}などの繰り返しは、既定で「できるだけ長く」マッチします。これを貪欲マッチと言います。直後に?を付けて*?・+?・{n,m}?のようにすると、「できるだけ短く」に変わります(最短マッチ)。
print(re.findall(r"<.+>", "<a><b>")) # ['<a><b>'] 貪欲:最初の < から最後の > まで
print(re.findall(r"<.+?>", "<a><b>")) # ['<a>', '<b>'] 最短:< と > の最小ペアで区切る<.+>は貪欲なので、<a>で止まらず最後の>まで一気に取ってしまいます。<.+?>にすると、最も近い>で区切るので<a>と<b>に分かれます。「取り出す範囲が想定より広い」ときは、まず最短マッチにすることを考えます。
回数を範囲で指定する{n,m}も同じです。{n,m}は上限のm回まで、{n,m}?は下限のn回で止まります。
print(re.findall(r"a{2,4}", "aaaaa")) # ['aaaa'] 貪欲:上限の4回まで取る
print(re.findall(r"a{2,4}?", "aaaaa")) # ['aa', 'aa'] 最短:下限の2回で止めるグループと選択
扱う記号
(...) |
丸かっこ(...)は、パターンの一部をひとまとめにしたり、マッチした一部を取り出したりするのに使います。これをキャプチャグループと言います。re.findallでは、グループで囲んだ部分だけが結果として返ります。
print(re.findall(r"\d+円", "120円と80円")) # ['120円', '80円'] グループなし → マッチ全体
print(re.findall(r"(\d+)円", "120円と80円")) # ['120', '80'] (...) で囲った部分だけ(\d+)円のように「円という目印を手がかりにしつつ、数字部分だけを取り出す」といった使い方ができます。
グループを2個以上にすると、戻り値の形が変わります。各マッチが「(グループ1, グループ2)」のタプルになり、そのリストが返ります。
print(re.findall(r"(\d+)年(\d+)月", "2020年4月と2021年12月")) # [('2020', '4'), ('2021', '12')]
print(re.findall(r"(\d+)年(\d+)月", "2020年4月と2021年")) # [('2020', '4')]グループが複数ある場合、すべてのグループのパターンに一致した箇所だけが、タプルで返されます。
縦棒|は「AまたはB」という選択を表します。
print(re.findall(r"赤|青|黄", "赤と青の旗")) # ['赤', '青']赤|青|黄は、赤・青・黄のいずれかにマッチします。
エスケープ(記号を文字として扱う)
扱う記号
\
.や+、(などは正規表現で特別な意味を持つ記号(メタ文字)です。これらを「文字そのもの」として扱いたいときは、前に\を付けてエスケープします。
print(re.findall(r"\d+\.\d+", "v1.5 と 2.0")) # ['1.5', '2.0']\.はピリオドそのものを表します(.のままだと「任意の1文字」になってしまいます)。\d+\.\d+で「数字・ピリオド・数字」、つまりバージョン番号のような形にマッチします。
まとめ
正規表現の基本構文として、以下の構文をre.findallで例を示しながら確認しました。
- 位置(
^$\b) - 1文字(
.、\d/\D、\w/\W、\s/\S) - 文字クラス(
[...][^...]) - 繰り返し(
*+?{n,m}、貪欲と最短) - グループ
(...)と選択| - エスケープ
\.
ここで紹介した基本構文を組み合わせると、ちょっとした抽出や判定の多くは表現することができます。
なお、次の記事「正規表現のグループ : キャプチャ・名前付き・後方参照(Python)」で正規表現のグループを取り扱います。