↓ メインコンテンツへスキップ
  1. 補講/
  2. 補講01講 応用 Python プログラム/

正規表現

正規表現とは
#

正規表現(regular expression; regex)は、文字列のパターンを表すための小さな言語 です。 「数字が 3 つ、ハイフン、数字が 4 つ」のようなパターンを 1 つの文字列で書き、それが別の文字列に含まれているかを調べたり、含まれている部分を取り出したり、置き換えたりできます。

in 演算子や find メソッドは「決まった文字列」しか探せません。 例えば「電話番号らしきもの」を探すには、"075-123-4567" も "090-9876-5432" も同じ形として扱う必要があり、正規表現の出番になります。

Python では re モジュールで正規表現を扱います。

>>> import re
>>> re.search(r"\d{3}-\d{4}", "電話番号は 075-123-4567 です")
<re.Match object; span=(10, 18), match='123-4567'>
>>> re.search(r"\d{3}-\d{4}", "電話番号はありません")   # 見つからなければ None
>>>

正規表現の文字列には、\d のようにバックスラッシュを多く使います。 Python の文字列ではバックスラッシュが特別な意味を持つので、正規表現は r"..."(raw 文字列)で書くのが決まりです。 r を付けると、バックスラッシュがそのまま正規表現に渡されます。

基本的な文法
#

よく使う記号だけを挙げます。 これだけで日常的な用途の大半は書けます。

1 文字にマッチするもの
#

書き方意味例
aその文字そのものcat は "cat" にマッチ
.改行以外の任意の 1 文字c.t は "cat"、"cut"、"c1t" にマッチ
[abc]a、b、c のどれか 1 文字[ch]at は "cat"、"hat" にマッチ
[a-z]a から z のどれか 1 文字(範囲)[0-9] は数字 1 文字
[^abc]a、b、c 以外 の 1 文字[^0-9] は数字以外の 1 文字
\d数字 1 文字([0-9] と同じ)
\w英数字かアンダースコア 1 文字(日本語の文字も含む)
\s空白文字 1 文字(スペース、タブ、改行)

繰り返し
#

直前の 1 文字(またはグループ)を何回繰り返すかを指定します。

書き方意味例
*0 回以上ab*c は "ac"、"abc"、"abbbc" にマッチ
+1 回以上ab+c は "abc"、"abbbc" にマッチ("ac" にはマッチしない)
?0 回か 1 回colou?r は "color"、"colour" にマッチ
{n}ちょうど n 回\d{4} は 4 桁の数字
{n,m}n 回以上 m 回以下\d{2,4} は 2〜4 桁の数字

位置と組み合わせ
#

書き方意味例
^文字列の先頭^Hello は "Hello world" にマッチ、"Say Hello" にはマッチしない
$文字列の末尾\.py$ は .py で終わる文字列にマッチ
A|BA または Bcat|dog は "cat" か "dog" にマッチ
(...)グループ。まとめて繰り返したり、マッチした部分を取り出したりする(ab)+ は "ababab" にマッチ

. や +、( などを「その文字そのもの」として探したいときは、\.、\+、\( のようにバックスラッシュを前に付けます。

re モジュールの使い方
#

探す: search、findall
#

re.search(パターン, 文字列) は、パターンに 最初に マッチした部分を返します。 見つからなければ None を返すので、if 文でそのまま判定できます。 マッチした文字列は group() で取り出せます。

import re

text = "会議は 2026-10-21 と 2026-11-04 に行います"
m = re.search(r"\d{4}-\d{2}-\d{2}", text)
if m:
    print(m.group())   # => 2026-10-21(最初の 1 つだけ)

すべてのマッチを取り出すには re.findall を使います。結果はリストです。

print(re.findall(r"\d{4}-\d{2}-\d{2}", text))  # => ['2026-10-21', '2026-11-04']

一部を取り出す: グループ
#

パターンの一部を (...) で囲むと、その部分だけを group(番号) で取り出せます。 番号は左の括弧から順に 1、2、3、… です(group(0) は全体)。

m = re.search(r"(\d{4})-(\d{2})-(\d{2})", text)
print(m.group(1), m.group(2), m.group(3))  # => 2026 10 21

findall にグループがあると、グループの部分だけがタプルになって返ります。

print(re.findall(r"(\d{4})-(\d{2})", text))  # => [('2026', '10'), ('2026', '11')]

置き換える: sub
#

re.sub(パターン, 置き換え後, 文字列) は、マッチした部分をすべて置き換えた新しい文字列を返します。 置き換え後の文字列では、\1、\2 でグループの内容を参照できます。

print(re.sub(r"\s+", " ", "a   b \t c"))                            # => a b c(連続する空白を 1 つに)
print(re.sub(r"(\d{4})-(\d{2})-(\d{2})", r"\1年\2月\3日", text))    # => 会議は 2026年10月21日 と 2026年11月04日 に行います

先頭からマッチさせる: match、fullmatch
#

re.match は文字列の 先頭 からマッチするかだけを調べます(^ を付けたのと同じ)。 re.fullmatch は文字列 全体 がパターンに一致するかを調べるので、入力チェックに向いています。

print(re.match(r"\d+", "123abc"))       # => <re.Match object; span=(0, 3), match='123'>
print(re.match(r"\d+", "abc123"))       # => None(先頭が数字ではない)
print(re.fullmatch(r"\d{3}-\d{4}", "123-4567"))   # => マッチする
print(re.fullmatch(r"\d{3}-\d{4}", "123-45678"))  # => None(全体が一致しない)

例: ログから情報を取り出す
#

正規表現は、決まった形の行から必要な部分を抜き出すときに威力を発揮します。 次のプログラムは、Web サーバのログのような行から、IP アドレスと状態コードを取り出します。

import re

log = """192.168.1.10 - - [21/Oct/2026:09:10:00] "GET /index.html" 200
10.0.0.5 - - [21/Oct/2026:09:10:03] "GET /missing.html" 404
192.168.1.10 - - [21/Oct/2026:09:10:07] "GET /lesson04/" 200"""

for line in log.splitlines():
    m = re.search(r'^(\d+\.\d+\.\d+\.\d+) .* "GET ([^"]+)" (\d{3})$', line)
    if m:
        print(m.group(1), m.group(2), m.group(3))

実行結果:

192.168.1.10 /index.html 200
10.0.0.5 /missing.html 404
192.168.1.10 /lesson04/ 200

注意点
#

  • 正規表現は強力ですが、読みにくくなりがちです。split や in で済むなら、そちらを使ってください。
  • . や .* は「何でも」にマッチするので、思ったより長い範囲にマッチすることがあります(例: ".*" は "a" と "b" 全体にマッチします)。困ったときは [^"]* のように「引用符以外の文字の繰り返し」と書き直すと意図通りになります。
  • パターンが正しいかどうかは、regex101(Python を選ぶ)のようなサイトで試しながら書くのが確実です。

参考
#