文字列から特定の部分文字列を見つける時、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で文字列を置換する