正規表現を使うべき場面
Python の正規表現は、テキストのパターンを記述するための小さな専用言語のようなものです。強力ですが、つい使いすぎてしまいがちな機能でもあります。
re モジュールに手を伸ばす前に、まずは普通の文字列メソッドで十分じゃないか考えてみましょう。.split()、.replace()、.startswith()、"target" in text といったものは、同じことを正規表現でやるよりも速く、読みやすく、バグも入りにくいです。正規表現の出番は、探したいパターンが ある程度の構造を持ちつつも 固定の文字列操作では対応しきれないくらい柔軟なとき。たとえばメールアドレス、電話番号、形式の決まったログ行、HTML や Markdown の断片など、「この手のやつを見つけたい」という検索です。
基本の記法
正規表現の パターン は、「何を探しているか」を表す文字列です。よく使うパーツを並べてみます。
.— 任意の 1 文字\d— 数字 1 文字(0〜9)\w— 「単語」を構成する文字(英字・数字・アンダースコア)\s— 空白文字^— 文字列の先頭$— 文字列の末尾[abc]— a、b、c のいずれか 1 文字[^abc]— a、b、c 以外の任意の 1 文字a|b— a または b*— 直前の要素の 0 回以上の繰り返し+— 1 回以上の繰り返し?— 0 回または 1 回{3}— ちょうど 3 回{2,5}— 2 回以上 5 回以下( ... )— グループ化(中身をキャプチャする)
実務で使う正規表現のほとんどは、これだけで書けてしまいます。
主な関数
re.search の使い方
re.search(pattern, text) は、文字列のどこかに最初に現れるマッチを探します。マッチオブジェクトか、見つからなければ None を返します。
正規表現のパターンには必ず raw 文字列(r"...")を使いましょう。そうしないと、Python がバックスラッシュを文字列エスケープとして解釈してしまい、書いたつもりのパターンとは別物になってしまいます。
re.match(pattern, text) は search と似ていますが、文字列の先頭でのみマッチを試みます。
ほとんどの場合は search を使えば事足ります。
re.findall(pattern, text) は、重ならないマッチをすべてリストで返してくれます。
findall が返すのは文字列であって、マッチオブジェクトではない点に注意してください。パターンにグループが1つだけ含まれる場合はそのグループの中身が返り、複数のグループがある場合はタプルのリストになります。
グループ化によるキャプチャ
パターン内の丸括弧 () は、その中にマッチした部分をキャプチャします。
名前付きグループ を使うと、コードがぐっと読みやすくなります。
グループが2つ以上になると、名前を付けておいた方が抽出するコードがぐっと読みやすくなります。
re.sub で置換する
re.sub(pattern, replacement, text) は、マッチした部分をすべて置き換えます。
これで数字以外をすべて取り除けます。置換側ではキャプチャしたグループを \1、\2 のように参照できます。raw 文字列を使う場合は \g<1> と書いた方が分かりやすいでしょう。
置換部分には関数を渡すこともできます。単純な置き換えでは表現できない変換をしたいときに便利です。
パターンをコンパイルして使い回す
同じパターンを何度も使う場合、特にループの中で繰り返し使うようなときは、一度コンパイルしておくのがおすすめです。
コンパイル済みのパターンでも、search、match、findall、sub といった同じメソッドが使えます。ただし第一引数にパターンを渡す必要はありません。少しだけ高速で、コードも読みやすくなることが多いです。
フラグ
よく使う修飾子は、flags= 引数で渡すか、| で組み合わせて指定します。
re.IGNORECASE(re.I) — 大文字・小文字を区別せずにマッチします。re.MULTILINE(re.M) —^と$が文字列全体の先頭・末尾だけでなく、各行の先頭・末尾にもマッチするようになります。re.DOTALL(re.S) —.が改行にもマッチするようになります(デフォルトではマッチしません)。re.VERBOSE(re.X) — パターン内に空白や#によるコメントを書けるようになり、可読性が向上します。
re.VERBOSE は複雑なパターンを書くときに特に便利です。
複数行でコメントを添えた正規表現は、意味不明な一行パターンに比べてメンテナンスが圧倒的にラクになります。
貪欲マッチと最小マッチ
量指定子(*、+、?、{n,})は、デフォルトでは 貪欲(greedy) に動作し、マッチできる範囲を可能な限り長く取ろうとします。末尾に ? を付けると 最小マッチ(lazy) に切り替わります。
貪欲マッチの方は <b> から </i> までまるごと拾ってしまっていて、たぶん欲しかった結果とは違いますよね。最小マッチの .+? なら最初の > の時点で止まってくれます。
(余談ですが、本番で HTML を正規表現でパースするのはやめましょう。html.parser か BeautifulSoup を使ってください。上の例はあくまで貪欲マッチの挙動を示すためのものです。)
実践的なサンプル
シンプルなログ形式の行をパースしてみます。
たった数行でこれだけのことができてしまいます。
ちょっとしたコツ
- パターンは必ず raw 文字列(
r"...")で書く。 - まずは動く最小のパターンから始め、あとから絞り込む。
- グループが2つ以上になったら名前付きグループを使う。
- 繰り返し使うパターンは
compileしておく。 - 正規表現は普通の文字列メソッドより遅い。
.split()で足りるなら.split()を使う。
次は: エラーとデバッグ
これで実データを扱うひと通りのツアー(ファイル、JSON、CSV、HTTP、日付、そして正規表現)は終わりです。とはいえ、実際のプログラムを書けば遅かれ早かれエラーに遭遇します。そして Python のトレースバックをきちんと読めるようになることは、デバッグ力を伸ばすうえで何よりも効きます。最終章では例外処理と、現場でよく出会う代表的なエラーについて見ていきます。
よくある質問
Pythonにおける正規表現とは?
正規表現(regex)は、文字列のパターンを記述するための小さな言語です。Pythonでは標準ライブラリの re モジュールを使って、パターン検索・抽出・置換ができます。ただし、単純な文字列操作なら .split() や .replace() などの文字列メソッドで十分。正規表現はあくまで、構造のあるパターンをマッチさせたいときの切り札として使うのがおすすめです。
re.match と re.search の違いは?
re.match は文字列の先頭からしかマッチを試みません。一方 re.search は文字列全体を走査して、どこかにマッチがあれば最初の一件を返します。迷ったときは search を使うのが無難です。多くの人が直感的に期待する挙動はこちらのほうです。
正規表現のパターンは raw 文字列(r"...")で書くべき?
はい、基本的に raw 文字列で書くのがおすすめです。正規表現には \d・\s・\b のようにバックスラッシュを含む記号が頻出しますが、通常の文字列だとPythonがエスケープシーケンスとして解釈してしまいます。r'\d+' のように r を付けておけば文字列がそのまま扱われるので、パターンが読みやすくなりバグも減ります。