HOWTO · Python

Python でファイル内の文字列を検索する

チュートリアルでは、Python でテキストファイル内の文字列を検索する方法を説明しています。

このページの内容

テキストファイル内の部分文字列を検索するには、真偽値で確認する場合は read() と in、該当する行を調べる場合は readlines()、文字位置が必要な場合は find() を使います。大きなファイルやバイト単位のデータには、ファイルをメモリにマッピングして検索する mmap が使えます。各例では同じ短いテキストを使っているため、結果とトレードオフを比較できます。各例は実行可能で、表示された出力は提供された実行結果と一致します。

read() と in でファイルの内容を検索する

read() メソッドは、ファイルの残りの内容を1つの文字列として返します。Pythonの in 演算子を使うと、その文字列に探しているリテラル文字列が含まれているかを確認できます。式の結果は True または False になるため、部分文字列が存在するかどうかだけを知りたい場合に便利です。

この方法は簡単ですが、検索の前にファイル全体をメモリに読み込みます。小〜中規模のテキストファイルや、内容全体も必要な場合に向いています。ファイルが大きく、全体をメモリに保持しにくい場合は、行単位の方法かメモリマッピングを使う方法を選びましょう。

次の例を実行します。

このセクションのコードと表示されるラベルや出力は英語のままです。

from pathlib import Path
from tempfile import TemporaryDirectory

with TemporaryDirectory() as directory:
    path = Path(directory) / "notes.txt"
    path.write_text("status=ready\nowner=Sam\n", encoding="utf-8")
    with path.open(encoding="utf-8") as file:
        found = "ready" in file.read()
    print(found)
True

readlines() で1行ずつ検索する

readlines() メソッドは、ファイルの各行を含むリストを返します。改行がある行には改行文字も含まれます。各行に対して in で検索し、最初に一致した時点でループを終了できます。一致した行自体が必要な場合、たとえば表示または処理するレコードを選ぶ場合に便利です。

ループは早めに終了できますが、その前に readlines() がすべての行をリストに読み込んでいます。そのため、ファイルが大きくなるほどメモリ使用量も増えます。大きなファイルを少しずつ処理するには、リストを作らずに開いたファイルを for line in file で直接反復します。これなら同じ行単位の検索をしながら、全行を一度に保持せずに済みます。

次の例を実行します。

このセクションのコードと表示されるラベルや出力は英語のままです。

from pathlib import Path
from tempfile import TemporaryDirectory

with TemporaryDirectory() as directory:
    path = Path(directory) / "notes.txt"
    path.write_text("status=ready\nowner=Sam\n", encoding="utf-8")
    with path.open(encoding="utf-8") as file:
        for line in file.readlines():
            if "owner" in line:
                print(line.rstrip())
                break
owner=Sam

find() で一致位置を取得する

「あるかどうか」だけでなく、最初に一致する文字の位置が必要な場合は、read() で読み込んだ内容に対して、文字列の find() メソッドを使います。位置は0から数えます。文字列の先頭で一致すれば 0、後方で一致すればその位置が返ります。一致がなければ find() は -1 を返します。0 はPythonでは偽とみなされるため、結果を単純に真偽値として扱わず、-1 と明示的に比較してください。

返される位置は、ファイルのバイト数ではなく、デコードされたPython文字列の文字数に基づきます。非ASCII文字を含むファイルをテキストとして開く場合は、適切なエンコーディングを指定してください。バイト位置が必要な場合や内容がバイナリの場合は、バイト列のパターンを使って mmap などで検索します。

次の例を実行します。

このセクションのコードと表示されるラベルや出力は英語のままです。

from pathlib import Path
from tempfile import TemporaryDirectory

with TemporaryDirectory() as directory:
    path = Path(directory) / "notes.txt"
    path.write_text("status=ready\nowner=Sam\n", encoding="utf-8")
    with path.open(encoding="utf-8") as file:
        contents = file.read()
    position = contents.find("ready")
    print(position)
    print(contents.find("missing"))
7
-1

mmap で大きなファイルを検索する

mmap モジュールはファイルをバイト列のように扱えるオブジェクトにマッピングします。ファイル全体を含むPython文字列を先に作らずに検索できるため、大きなファイルを扱う際に役立つことがあります。ただし、検索が必ず速くなるわけではありません。速度はファイル、ストレージ、オペレーティングシステム、アクセス方法によって変わります。マッピングはオペレーティングシステムのリソースも使うため、使い終わったら閉じてください。

b"ready" のようなバイト列のパターンでマッピングを検索します。テキストの例とは異なり、エンコードされたバイト列を調べ、バイト位置を返します。検索パターンはバイト列でなければなりません。通常の文字列である "ready" とは型が異なります。この例では、同じASCIIテキストをUTF-8のバイト列として扱い、ファイルをバイナリモードで開きます。

次の例を実行します。

このセクションのコードと表示されるラベルや出力は英語のままです。

import mmap
from pathlib import Path
from tempfile import TemporaryDirectory

with TemporaryDirectory() as directory:
    path = Path(directory) / "notes.txt"
    path.write_bytes(b"status=ready\nowner=Sam\n")
    with path.open("rb") as file:
        with mmap.mmap(file.fileno(), 0, access=mmap.ACCESS_READ) as mapped:
            print(mapped.find(b"ready"))
7

適切な方法を選ぶ

真偽値だけで十分で、テキスト全体を無理なくメモリに保持できる場合は、read() と in を使います。行のリストを調べたり保持したりする必要がある場合は readlines() が使えますが、リストにはファイル全体の行が格納される点に注意してください。大きなテキストファイルを行ごとに処理できる場合は、全行を保存せず、ファイルオブジェクトを直接反復します。

最初に一致する文字の位置が必要なら find() を使います。一致しない場合は -1 と比較し、有効な位置として 0 も扱ってください。ファイルを使ったバイト検索が適切で、バイト位置が必要な場合は mmap を選びます。検索対象をバイト列にし、メモリマッピングが常に速いとは限らないことを覚えておきましょう。これらの方法が検索するのはリテラル文字列です。パターン検索、大文字と小文字を区別しない検索、すべての一致箇所の取得が必要な場合は、その要件に合った方法を選んでください。