文字列の中から特定の単語や記号を探す処理は、ログ解析や入力チェック、テキスト整形などで頻繁に登場します。
本記事ではPythonのfind()
とindex()
を中心に、違い、start
やend
引数の使い方、右から検索するrfind
やrindex
、そして実務での使い分けまでを丁寧に解説します。
Pythonの文字列検索 find()とindex()の基本
役割と戻り値の違い
find()
とindex()
はいずれも部分文字列を探して先頭の位置を返しますが、見つからなかった場合の挙動が異なります。
find()
は-1を返し、index()
はValueError
を送出します。
右から探すペアとしてrfind()
とrindex()
もあります。
以下に主な違いをまとめます。
メソッド | 見つかった場合 | 見つからない場合 | 検索方向 | 用途の目安 |
---|---|---|---|---|
find(sub[, start[, end]]) | 最初の一致位置の整数 | -1 | 左→右 | 失敗を許容して進めたい場合 |
index(sub[, start[, end]]) | 最初の一致位置の整数 | ValueError | 左→右 | 見つからないのを例外として扱いたい場合 |
rfind(sub[, start[, end]]) | 最後の一致位置の整数 | -1 | 右→左(位置は左起点) | 最後の一致が必要だが失敗は許容 |
rindex(sub[, start[, end]]) | 最後の一致位置の整数 | ValueError | 右→左(位置は左起点) | 最後の一致が必須で失敗は例外 |
in
演算子で存在有無だけを調べることもできますが、本記事では位置が必要な場面を中心に扱います。
シンタックスと引数(start, end)
それぞれのシグネチャは以下の通りです。
s.find(sub[, start[, end]])
s.index(sub[, start[, end]])
start
は検索を始める位置のインデックス、end
は検索を終える位置のインデックスです。
どちらもスライスと同じくstart
は含む、end
は含みません。
また、負の値も指定でき、右からのオフセットとして解釈されます。
# 文字列中のインデックスとstart/endの挙動を確認する例
s = "abracadabra"
# 01234567890 ← インデックス
print(s.find("abra")) # 先頭から最初の一致
print(s.find("abra", 1)) # 位置1以降で検索
print(s.find("a", 3, 9)) # [3, 9) の範囲で検索
print(s.find("bra", -5)) # 右から5文字目以降で検索
0
7
3
8
上の例では、end
は含まれないため[start, end)
の半開区間になる点が重要です。
右から検索するrfindとrindex
末尾側に近い一致を取りたいときはrfind()
やrindex()
を使います。
返される位置は左端からのインデックスであり、見つからない場合の挙動はfind()
とindex()
に準じます。
s = "one two two three two"
print(s.rfind("two")) # 最後の"two"の開始位置
print(s.rindex("two")) # 同上。見つからない場合はValueError
18
18
find()の使い方
一致位置を取得する基本
最初の一致の開始インデックスを整数で受け取れます。
# 基本的なfindの使用例
text = "Python makes coding fun"
pos = text.find("coding")
print(f"coding の開始位置は {pos} です")
coding の開始位置は 13 です
見つからない時は-1を返す
find()
は見つからない場合に-1を返します。
存在有無を判定するだけならin
演算子が簡潔ですが、位置が必要な場合は-1との比較を必ず行います。
# 見つからない場合は-1
text = "Python makes coding fun"
pos = text.find("Java")
if pos == -1:
print("Java は見つかりませんでした")
else:
print(f"Java は位置 {pos} にあります")
Java は見つかりませんでした
位置を使って部分文字列を取り出す
find()
で得た位置とスライスを組み合わせると、可変長の部分文字列を安全に取り出せます。
# URLからドメインを取り出す例
url = "https://example.com/path/index.html"
scheme_sep = "://"
sep_pos = url.find(scheme_sep)
if sep_pos == -1:
raise ValueError("URLにスキーム区切り(://)がありません")
start = sep_pos + len(scheme_sep) # ドメインの開始位置
end = url.find("/", start) # ドメインの終了位置(最初のスラッシュ)
if end == -1:
end = len(url) # スラッシュが無ければ末尾まで
domain = url[start:end]
print(domain)
example.com
このように、start
をlen(見つけた区切り)
だけ進め、次の区切りの位置をfind()
で求めるのが定石です。
index()の使い方
一致位置を取得する基本
index()
はfind()
と同じ位置を返しますが、見つからないときに例外を送出します。
必ず存在する前提のデータを扱うときに向いています。
# 基本的なindexの使用例
text = "Python makes coding fun"
pos = text.index("coding")
print(f"coding の開始位置は {pos} です")
coding の開始位置は 13 です
見つからない時はValueError
見つからない場合はValueError: substring not found
となります。
未処理のままにするとプログラムはそこで停止します。
# 見つからない場合のindexは例外を送出
text = "Python makes coding fun"
print(text.index("Java")) # ここで例外
Traceback (most recent call last):
File "sample.py", line 3, in <module>
print(text.index("Java")) # ここで例外
~~~~~~~~~~^^^^^^^^
ValueError: substring not found
try-exceptでのエラー処理
index()
の失敗を業務的なエラーメッセージに置き換えたい場合は、try-except
でValueError
を捕捉します。
# 設定行 key=value の形式を検証する例
def parse_kv(line: str) -> tuple[str, str]:
try:
eq = line.index("=") # 必須
except ValueError:
# 形式エラーとして説明的な例外を投げ直す
raise ValueError(f"不正な形式です: {line!r}. 'key=value' 形式にしてください")
key = line[:eq].strip()
value = line[eq + 1 :].strip()
return key, value
print(parse_kv("timeout = 30"))
try:
print(parse_kv("invalid line"))
except ValueError as e:
print(f"エラー: {e}")
('timeout', '30')
エラー: 不正な形式です: 'invalid line'. 'key=value' 形式にしてください
index()
は存在必須の前提や、異常系を例外としてハンドリングしたい場合に適しています。
find()とindex()の使い分け
失敗を許容するならfind
オプショナルな文字列や、見つからなくても処理を継続したい場合はfind()
が適しています。
-1を基準に分岐し、見つかったときだけ処理します。
# "NOTE:" があれば抽出し、なければ何もしない
line = "ID=42; USER=alice; NOTE: urgent"
pos = line.find("NOTE:")
if pos != -1:
note = line[pos + len("NOTE:") :].strip()
print(f"注記: {note}")
else:
print("注記はありません")
注記: urgent
厳密なエラー制御ならindex
存在しなければ不正データとみなす場合は、index()
で例外を使って確実に検知します。
# ログレコードに必須の区切り "--" が無ければ例外
record = "2025-08-31 12:34:56 -- OK"
try:
sep = record.index("--")
left = record[:sep].strip()
right = record[sep + 2 :].strip()
print(left, right)
except ValueError:
# 監視対象などでは例外のまま上位に伝播させるのも有効です
raise RuntimeError("レコード形式が不正です")
2025-08-31 12:34:56 OK
部分範囲検索で効率化する
巨大な文字列や繰り返し検索では、start
やend
を使って探索範囲を絞ると無駄な走査を減らせます。
複数一致を左から順に列挙する場合もstart
を更新していくのが定石です。
# 複数一致の位置を列挙する。毎回 start を進めて部分範囲検索
text = "bananarama"
sub = "ana"
start = 0
positions = []
while True:
pos = text.find(sub, start)
if pos == -1:
break
positions.append(pos)
start = pos + 1 # 重なりを許すなら +1、許さないなら +len(sub)
print(positions) # [1, 3]
[1, 3]
end
も併用すれば、例えば特定のセクション内だけを検索する、といった効率化も可能です。
-1の扱いミスを防ぐ
find()
の戻り値をそのまま真偽値として使うのはバグの温床です。
Pythonでは-1
は真と評価されるため、見つからないのに条件が真になってしまいます。
# 悪い例: 見つからない(-1)のに真と判定される
s = "abc"
if s.find("z"): # -1 は True として扱われる
print("found?") # 実際は見つかっていないのに出力される
else:
print("not found")
# 良い例1: -1と比較する
if s.find("z") != -1:
print("found")
else:
print("not found")
# 良い例2: 存在有無だけなら in 演算子を使う
print("z" in s) # False
found?
not found
False
存在チェックだけならin
演算子、位置が必要ならfind()
と-1
比較、存在必須ならindex()
とtry-except
という方針でミスを減らせます。
まとめ
find()
とindex()
はどちらも一致位置を返しますが、見つからない場合の挙動が異なります。
失敗を許容したいならfind()
で-1を扱い、存在必須ならindex()
で例外処理を行うのが安全です。
start
やend
で検索範囲を絞ると無駄な走査を減らせ、rfind()
やrindex()
で末尾に近い一致を簡単に得られます。
また、find()
の戻り値をそのまま真偽値に使うと-1が真になる落とし穴があるため、!= -1
で比較するかin
演算子を用いると良いです。
ここで解説した基本と注意点を押さえておけば、ログ解析や文字列パースなどの現場で堅牢かつ効率的なコードが書けます。