R

ReflexKR

Reflex公式ドキュメント
目で見て覚える正規表現
9限目

時間 6:40–7:30

金庫のルール:先読みと後読み

CASE-009

グレップ・アンド・コー データ探偵事務所

依頼書

金庫のルール

依頼人

ハン・ビョル所長

証拠品

事務所の古い金庫、新しい暗証番号の候補6つ、金庫の中の帳簿。

解決条件

新しい暗証番号のルール(8文字以上、数字・小文字・記号を1つずつ)を1つのパターンで検査し、帳簿の金額に3桁ごとのカンマを入れる。

所長が部屋の隅にある古い金庫の前にしゃがみ込みます。「バックトラッカーが金庫の話を持ち出したのは、ただの脅しだよ。それでも暗証番号は今日変える。今度はルールをきちんと守って」

新しい暗証番号のルールは3つ。順番は問いません。

규칙 1. 8자 이상
규칙 2. 숫자, 소문자, 특수문자가 각각 하나 이상
규칙 3. 이 모든 걸 정규식 하나로 검사한다

후보:
vault2026!
vault2026
VAULT2026!
v!2
longpassword!
safe#2026box

所長のメモは韓国語です。ルール1は「8文字以上」、ルール2は「数字、小文字、記号がそれぞれ1つ以上」、ルール3は「これらすべてを1つの正規表現で検査する」。その下の「후보」は「候補」です。

カードに書かれていた言葉がよみがえります。「すべて同じ位置で確かめられるのならね」。今日学ぶ文法は、まさにそれです。

9-1. 文字ではなく位置を確かめる文法 (6分)

4限目の ^ $ \b を思い出してください。これらは文字を1つも食べずに、「ここが先頭か」「ここが単語の境界か」という位置だけを確かめていました。こうしたマッチを幅ゼロ(zero-width)のマッチと呼びます。

re.findall(r"\b", "hi yo")      # ['', '', '', '']
re.sub(r"\b", "|", "hi yo")     # '|hi| |yo|'

\b が見つけたのは空文字列4つ、つまり位置4つです。今日学ぶ先読み・後読みは、この「位置の確認」を自分の好きな条件で作る文法です。

9-2. 後ろに何が来るかを確かめる:(?=...) (8分)

X(?=Y) は「後ろに Y が来る X」を探します。Y は確かめるだけで、マッチには含めません。これを先読み(lookahead)と呼びます。

re.findall(r"\d+(?=원)", "3000원짜리 5개, 합계 15000원")   # ['3000', '15000']

文字列は韓国語で「3000ウォンのものを5個、合計15000ウォン」という意味です(원 はウォン、개 は「個」)。5개 の 5 は後ろに 원 がないので外れ、見つかった数字には 원 が含まれていません。「ウォンで終わる数字」を探しつつ、数字だけを取り出したいときにぴったりです。

証拠分析室

re.search(r"", text)

戻り値

\d+ だけだと個数の 5 まで捕まります。後ろに 원 が来る数字だけが残るように、先読みを付けてみましょう。

9-3. 後ろに来てはいけないもの:(?!...) (6分)

X(?!Y) は逆に、「後ろに Y が来ない X」です。否定先読みと呼びます。

re.findall(r"\b(?!tmp)\w+", "tmp_log report tmpfile final")   # ['report', 'final']
re.findall(r"\d{3}(?!\d)", "12345 678")                       # ['345', '678']
  • 1行目は「tmp で始まらない単語」です。単語の境界のすぐ後ろが tmp でないことを確かめています。
  • 2行目は「後ろにもう数字が続かない3桁」、つまり数字のかたまりの最後の3桁です。

9-4. 複数のルールは先読みをつなげる (8分)

暗証番号のルールのように「これも、あれも、それもなければならない」という条件は、先読みを横に並べて書きます。

pw = r"(?=.*\d)(?=.*[a-z])(?=.*[^\w\s]).{8,}"
かけら確かめること
(?=.*\d)どこかに数字がある
(?=.*[a-z])どこかに小文字がある
(?=.*[^\w\s])どこかに記号(文字・数字・アンダースコア・空白以外のもの)がある
.{8,}実際に8文字以上を食べる

3つの先読みは、どれも**同じ位置(先頭)**で検査します。文字を食べないので、1つ終わっても位置はそのまま。次の条件もまた先頭から始まります。だから、つなげると「かつ(AND)」になるのです。

cands = ["vault2026!", "vault2026", "VAULT2026!", "v!2", "longpassword!", "safe#2026box"]
[c for c in cands if re.fullmatch(pw, c)]   # ['vault2026!', 'safe#2026box']

証拠分析室

re.search(r"", text)

戻り値

.{8,} は長さしか見ていません。数字・小文字・記号の条件を、先読みにして前に付けてみましょう。

ドットのひとこと

先読みは、前をちらっと見るだけで足は動かさないんです。ぼくも窓枠に座って外をながめるだけで、越えはしませんからね。だから何回のぞいても、その場から動いていないんですよ。

9-5. 前に何があったか:(?<=...) (?<!...) (8分)

向きを逆にすると後読み(lookbehind)です。(?<=Y)X は「前に Y がある X」、(?<!Y)X は「前に Y がない X」です。

re.findall(r"(?<=\$)\d+", "$30 and 40 and $5")      # ['30', '5']
re.findall(r"(?<!\$)\b\d+", "$30 and 40 and $5")    # ['40']

後読みには制約が1つあります。Python の re では、振り返る長さが決まっていなければなりません。

re.compile(r"(?<=a+)b")      # PatternError: look-behind requires fixed-width pattern
re.compile(r"(?<=ab|cd)e")   # OK:選択肢はどちらも2文字
re.compile(r"(?<=a|bc)d")    # PatternError: look-behind requires fixed-width pattern

選択肢が複数あっても、長さがすべて同じなら大丈夫です。長さが違うなら、後読みを2つに分けて (?:(?<=a)|(?<=bc))d のように別々に囲みます。

証拠分析室

re.search(r"", text)

戻り値

\$\d+ は $ まで取り出してしまいます。戻り値が ['30', '5'] になるように、\$ を後読みに移してみましょう。

9-6. 金庫の帳簿:3桁ごとのカンマ (4分)

金庫の中の帳簿は、数字が 1234567 のようにくっついていて読みにくいです。カンマを入れる位置を探して、そこにカンマを入れればよいのです。位置を探す仕事なので、先読み・後読みがうってつけです。

re.sub(r"(?<=\d)(?=(?:\d{3})+(?!\d))", ",", "1234567 and 89 and 1000")
# '1,234,567 and 89 and 1,000'

読み方はこうです。「前に数字があり((?<=\d))、後ろに3桁の数字のまとまりが1つ以上続いて((?:\d{3})+)、そこで数字が終わる((?!\d))位置」。その位置は文字ではないので、sub は文字を消さずにカンマだけを差し込みます。

先読みの中にグループを入れると、重なり合うマッチも見つけられます。位置だけを1つずつずらしながら、その場所の2文字をグループで取り出すコツです。

re.findall(r"(?=(\w\w))", "abc")   # ['ab', 'bc']

証拠分析室

re.search(r"", text)

戻り値

置換文字列はカンマ1つです。パターンが文字ではなく位置を探すからこそ、数字が消えずに済みます。

事件解決 (10分)

検査器を仕上げて、候補をふるいにかけます。

pw = re.compile(r"(?=.*\d)(?=.*[a-z])(?=.*[^\w\s]).{8,}")
for c in cands:
    print(f"{c:<14} {'통과' if pw.fullmatch(c) else '탈락'}")
# vault2026!     통과
# vault2026      탈락
# VAULT2026!     탈락
# v!2            탈락
# longpassword!  탈락
# safe#2026box   통과

事務所の道具なので、表示は韓国語のままです(통과 は「合格」、탈락 は「不合格」)。

所長は、合格した2つのどちらも選びません。「候補リストに書かれた時点で、もう暗証番号じゃない」。そう言って、ルールを満たす新しい暗証番号を誰にも見られないよう自分で決め、検査器だけを事務所の共用道具箱に入れておきます。

事件解決報告書 — 金庫の暗証番号のルールを、3つの先読みで検査するツールを作成。候補6つのうち、ルールを満たしたのは2つだけ。暗証番号は所長が別途変更した。帳簿の金額には3桁ごとのカンマを入れて整理した。

練習問題

  1. ルールに「大文字も1つ以上」を追加してみましょう。どの候補が生き残るでしょうか。
  2. "price: 1200won, tax: 120won" から、won の前の数字だけを先読みで取り出してみましょう。
  3. re.findall(r"(?<!\d)\d{2}(?!\d)", "1 22 333 44") の結果を予想してみましょう。なぜ 333 の一部は出てこないのでしょうか。

バックトラッカーの痕跡

(a+)+$

金庫はそのままにしておこう。代わりに、警察署のカン班長に手紙を3通送った。同じ人間が書いたと証明できるかな。癖は文字に残る。

所長の手帳

  • 先読み・後読みは幅ゼロのマッチ。文字を食べずに位置だけを確かめる(^ $ \b と同じ仲間)。

  • X(?=Y) は後ろに Y が来る X、X(?!Y) は後ろに Y が来ない X。

  • (?<=Y)X は前に Y がある X、(?<!Y)X は前に Y がない X。後読みは固定長のみ。

  • 先読みをつなげると AND:(?=.*\d)(?=.*[a-z])(?=.*[^\w\s]).{8,}

  • 位置を探して sub すると、文字を消さずに差し込める:3桁ごとのカンマ。

  • (?=(\w\w)) のように先読みの中のグループで、重なり合うマッチを取り出せる。

← 前へ

8限目 · バックトラッカーの挑戦状:欲張り・怠惰・強欲

次へ →

10限目 · 同じ手口:後方参照と条件付きパターン

新しい授業・講座のお知らせを受け取る

この講座に授業が追加されたり、続きの講座が公開されたりしたらメールでお知らせします。

収集項目:メールアドレス・目的:この講座の新しい授業・公開のお知らせ・保有期間:解除のご依頼まで。他の目的には使わず、第三者にも提供しません。お知らせメールに返信いただければすぐに削除します。

質問と回答

この回について気になることを書き込んでください。すべての言語のページの質問がここに集まります。

リンクや書式なしで、そのままの文字で表示されます。

R

ReflexKR

触って学ぶPythonとReflex。すべての例はReflex 0.10.0で実際に動かして確認しています。

ReflexKRはバナー広告なし・無料で運営しています。役に立ったら、コーヒー一杯で応援してください。 ☕ コーヒーで応援する

アフィリエイトと支援について
한국어English日本語简体中文EspañolDeutsch

© 2026 ReflexKR · Since 2026 · CC BY-NC 4.0

Built with Reflex