文字列から特定の部分文字列を見つける時、Pythonではfind()とindex()を使います。
どちらも似ていますが、見つからない時の挙動が異なります。
本記事では、基本から検索範囲の指定、複数一致の列挙、例外の扱い、大小文字の違いへの対処まで、初心者でも着実に理解できるよう段階的に解説します。
Pythonのfind()とindex()の基本
基本の使い方(部分文字列の位置を取得)
最初の一致位置(先頭から数えた0始まりのインデックス)を返すのがfind()とindex()の基本動作です。
見つかった位置の数値で返るため、そのままスライスや置換の基点にできます。
以下は"banana"から"na"を探す例です。
# 基本の使い方: 最初の一致位置を取得
s = "banana"
print("s =", s)
print("find('na'):", s.find("na")) # 最初の一致はインデックス2
print("index('na'):", s.index("na")) # 使い方はfindと同じ。戻り値も同じ
print("find('xyz'):", s.find("xyz")) # 見つからない場合の挙動は後述
# 右側から探すメソッドもあります(後述)
print("rfind('na'):", s.rfind("na"))
print("rindex('na'):", s.rindex("na"))
s = banana
find('na'): 2
index('na'): 2
find('xyz'): -1
rfind('na'): 4
rindex('na'): 4
インデックスは0始まりであることに注意してください。
例えば"banana"の"b"は0番目、"a"は1番目という数え方です。
戻り値の違い(-1とValueError)
最大の違いは、見つからなかった時にfind()は-1を返し、index()はValueErrorを送出する点です。
用途に応じて使い分けると安全です。
以下の比較表を目安にしてください。
| メソッド | 検索方向 | 見つからない時 | 典型的な用途 |
|---|---|---|---|
| find(sub[, start[, end]]) | 左から | -1を返す | 条件分岐で存在確認しやすい |
| index(sub[, start[, end]]) | 左から | ValueErrorを送出 | 絶対に存在すると仮定したい時 |
| rfind(sub[, start[, end]]) | 右から | -1を返す | 最後の一致位置が欲しい時 |
| rindex(sub[, start[, end]]) | 右から | ValueErrorを送出 | 最後の一致が必須の時 |
例外処理が不要ならfind()、必ず存在しないと困る前提ならindex()を選ぶのが基本指針です。
rfind()・rindex()で後ろから検索
最後に出現する一致位置が必要な時はrfind()またはrindex()を使います。
動作は左からの版と同じで、rfind()は-1、rindex()はValueErrorを採ります。
# 後ろから検索する例
s = "abracadabra"
print("最後の'a'の位置(rfind):", s.rfind("a")) # 10
print("最後の'ra'の位置(rfind):", s.rfind("ra")) # 9
最後の'a'の位置(rfind): 10
最後の'ra'の位置(rfind): 9
- 関連記事:スライスで文字列の一部を取得する
start/endで検索範囲を指定
startで開始位置をずらす
2番目以降の一致を探したい時はstart引数で検索の開始位置を後ろへずらします。
これにより、先頭で見つかった一致を飛ばして検索できます。
# startで検索開始位置を指定
s = "abracadabra"
print("s =", s)
print("find('a', 3):", s.find("a", 3)) # 3番目以降で最初の'a' -> 3
print("find('a', 4):", s.find("a", 4)) # 4番目以降で最初の'a' -> 5
s = abracadabra
find('a', 3): 3
find('a', 4): 5
見つかった位置の直後から再検索することで、複数一致の列挙にもつなげられます(後述)。
endで終端を区切る
endは終端インデックスの直前までを検索する、いわゆる排他的上限です。
スライスと同じ感覚で覚えると混乱しません。
# endは排他的(直前まで)
s = "abracadabra"
print("find('a', 0, 1):", s.find("a", 0, 1)) # インデックス0は含まれる -> 0
print("find('a', 1, 2):", s.find("a", 1, 2)) # 'b'のみの範囲 -> -1
print("find('ra', 0, 4):", s.find("ra", 0, 4)) # "abra"内 -> 2
print("find('ra', 0, 3):", s.find("ra", 0, 3)) # "abr"内 -> -1
find('a', 0, 1): 0
find('a', 1, 2): -1
find('ra', 0, 4): 2
find('ra', 0, 3): -1
開始位置と同様にrfind()やrindex()にもstart/endは効きます。
スライス不要の区間検索
部分列にしたい区間が決まっているなら、s[start:end].find(...)のようなスライスは不要で、find(sub, start, end)で直接指定できます。
余計な一時文字列を作らないため、可読性と効率が上がります。
# スライス不要で区間検索
s = "abracadabra"
print("find('cad', 0, 7):", s.find("cad", 0, 7))
print("s[:7].find('cad'):", s[:7].find("cad")) # 結果は同じ
find('cad', 0, 7): 4
s[:7].find('cad'): 4
- 関連記事:スライスで文字列の一部を取得する
複数一致の見つけ方
findを反復して次の一致を探す
基本パターンは、見つかった位置の直後からfind()を繰り返す方法です。
これにより、非オーバーラップで順に列挙できます。
# 非オーバーラップで複数一致を列挙
s = "banana"
sub = "na"
positions = []
start = 0
while True:
i = s.find(sub, start) # start以降で検索
if i == -1:
break
positions.append(i)
start = i + len(sub) # 見つかった部分の直後から再開(非オーバーラップ)
print(positions) # 期待: [2, 4]
[2, 4]
start = i + len(sub)とすることで、同じ一致を二重に数えないのがポイントです。
すべてのインデックスを列挙する
関数化しておくと再利用しやすく、テストやロギングにも便利です。
次は、1文字ずつずらして走査し、すべての一致位置を返す実装です。
# オーバーラップも含めてすべての一致位置を列挙
def find_all(text: str, sub: str) -> list[int]:
"""text中に現れるsubの開始インデックスを(重なりも含めて)昇順で返す"""
result: list[int] = []
i = -1
while True:
i = text.find(sub, i + 1) # 前回位置の次の位置から再検索
if i == -1:
break
result.append(i)
return result
print(find_all("banana", "na")) # [2, 4]
print(find_all("aaaa", "aa")) # [0, 1, 2] (オーバーラップ例)
[2, 4]
[0, 1, 2]
1文字ずつ開始位置を進めるため、重なり合う一致も見逃しません。
オーバーラップの一致を扱う
非オーバーラップで数えたい場合は+ len(sub)で飛ばし、重なりも数えたい場合は+ 1で1文字ずつ進めます。
違いは出力から一目瞭然です。
# 非オーバーラップ版とオーバーラップ版の比較
def find_all_non_overlap(text: str, sub: str) -> list[int]:
result = []
start = 0
while True:
i = text.find(sub, start)
if i == -1:
break
result.append(i)
start = i + len(sub) # 非オーバーラップなので長さ分進める
return result
def find_all_overlap(text: str, sub: str) -> list[int]:
result = []
start = 0
while True:
i = text.find(sub, start)
if i == -1:
break
result.append(i)
start = i + 1 # オーバーラップを許すので1文字だけ進める
return result
print("非オーバーラップ:", find_all_non_overlap("aaaa", "aa"))
print("オーバーラップ:", find_all_overlap("aaaa", "aa"))
非オーバーラップ: [0, 2]
オーバーラップ: [0, 1, 2]
用途に応じて「数え方」を明確に決めてから関数を選ぶと混乱しません。
エラーと例外の対処
見つからない時の処理(findは-1)
find()は見つからないと-1なので、条件分岐で安全に扱えます。
次のように-1判定で存在確認ができます。
# findは-1で「未検出」を表す
s = "python"
pos = s.find("Java")
if pos == -1:
print("見つかりませんでした")
else:
print(f"見つかった位置: {pos}")
見つかりませんでした
数値の-1はスライスの負のインデックスと紛らわしいため、必ず明示的に比較してから使うと安全です。
indexのValueErrorをtry/exceptで扱う
index()は見つからないとValueErrorを送出するため、例外処理で包みます。
存在が前提の場面で「前提が崩れた」ことを即座に検知できる利点があります。
# indexは見つからないとValueError
s = "python"
try:
pos = s.index("Java")
print(f"見つかった位置: {pos}")
except ValueError as e:
print("ValueErrorを捕捉:", e)
ValueErrorを捕捉: substring not found
入力検証やアサーションの代替としてindex()を使うと、異常系が明確になります。
大文字小文字の違いに対処(lowerで正規化)
大小文字を無視して検索したい時は、両者をlower()やcasefold()で正規化してからfind()します。
特に国際化対応ではcasefold()がより強力です。
# lower()を使ったケース非依存検索
text = "Hello World"
needle = "world"
pos = text.lower().find(needle.lower())
print("位置:", pos) # 6
if pos != -1:
# 元の文字列から該当部分を取り出せる
print("一致部分:", text[pos:pos+len(needle)])
# casefold()はより広い言語で堅牢
print("lowerでの一致:", "straße".lower().find("STRASSE".lower())) # -1 (合致しないことがある)
print("casefoldでの一致:", "straße".casefold().find("STRASSE".casefold())) # 0 (一致)
位置: 6
一致部分: World
lowerでの一致: -1
casefoldでの一致: 0
UI表示やログでは元の表記を保ちたいため、検索だけ正規化し、表示は原文を使うのが実務的です。
- 関連記事:大文字小文字変換 – upper, lower, capitalize, title
- 関連記事:文字列が特定の文字で始まる/終わるかを判定
- 関連記事:raiseの使い方 – 意図的な例外発生
まとめ
文字列検索の基本はfind()とindex()で、違いは「見つからない時」の挙動にあります。
開始位置startと終端endを使えばスライスなしで区間検索ができ、rfind()/rindex()で最後の一致を簡単に得られます。
複数一致はfind()の反復で列挙でき、非オーバーラップとオーバーラップのどちらを数えるかを方針として決めて実装します。
エラー処理ではfind()の-1判定とindex()のValueError捕捉を適切に使い分け、大小文字の違いはlower()やcasefold()で正規化して対応します。
これらを押さえておけば、実務の文字列処理で迷う場面は大きく減らせます。
- 関連記事:replaceで文字列を置換する
