ゼロからPythonを学んでいく本連載、前回よりPythonを使ってファイルを読み書きする方法を解説している。今回は、前回の内容を踏まえて、もう少し実践的なファイルの読み書きの方法について見ていこう。

巨大なテキストファイルを扱うには?

今回、実践的なファイル処理の例として、巨大なテキストファイルを処理したい場面を考えてみよう。世の中には、巨大なサイズのテキストファイルが存在する。そうした巨大なテキストファイルを、何も考えず、エディタで開こうとすると、メモリエラーで読み出せなかったり、開くのにものすごく時間がかかることがある。

例えば、筆者は、以下のURLでフリーの英和辞書のメンテナンスおよび、配布を行っている。この辞書データは、単語数は46,725個の単語で、ファイルサイズが4.5MBほどあるテキストファイルだ。

パブリックドメインの英和辞書 (ejdic-hand) [URL] https://kujirahand.com/web-tools/EJDictFreeDL.php

最近のPCでは問題ないだろうが、これをメモリの少ない古いPCで読み書きしようとすると、メモリ不足となり、開くのに時間がかかるか、最悪フリーズしてしまう。しかし、PCの搭載メモリが少ない時代であっても、この程度のファイルサイズであれば、問題なく処理することができた。一体どのように処理していたのか。それは、データを一度に全部メモリに読み込むのではなく、必要に応じて少しずつ読み込み処理するようにしていたのだ。

つまり、こうした巨大なテキストファイルを効率よく扱うには、全部を一気にメモリに読み込むのではなく、一行ずつデータを読み出して処理していくようにすれば良い。どれだけ巨大なファイルであっても、一行ずつ読み進めるならば、時間はかかるものの、フリーズすることなく最後まで安定して処理を行うことができる。

英和辞書からzooから始まる語句を抽出しよう

それでは、実際的なプログラムの例として、英和辞書のデータを一行ずつ読んで検索するプログラムを作ってみよう。今回も、本連載の2回目で紹介した、Jupyterノートブック上でプログラムを実行する。

それで、上記のWebサイトから、テキスト形式の英和辞書データをダウンロードしてプログラムから利用する。そのために、ファイルをダウンロードしたら、ZIPファイルを解凍し、辞書データ「ejdic-hand-utf8.txt」を、Jupyterノートブックの起動フォルダにコピーしておこう。

辞書ファイルをJupyterの起動フォルダにコピーしておこう

プログラムの前に、この英和辞書のデータ形式を紹介しておこう。この辞書には、一行に一つの英単語が記されている。そして、各行は、「英単語 (タブ) 日本語の意味」といういわゆるタブ区切りデータとなっている。

 [辞書データの形式]
 (英単語) (タブ) (日本語の意味)
 (英単語) (タブ) (日本語の意味)
 (英単語) (タブ) (日本語の意味)
 ...

これを念頭におきつつ、以下のプログラムをJupyterで実行してみよう。これは、テキストファイルを一行ずつ検索し、"zoo"から始まる単語の行があれば、画面に表示するというプログラムだ。

 # 英和辞書のデータを一行ずつ読む
 word = "zoo" # 検索単語を指定
 # ファイルを開く --- (*1)
 fp = open("ejdic-hand-utf8.txt", "r", encoding="utf-8")
 # 一行ずつ読み取って処理する --- (*2)
 for line in fp:
     if line.startswith(word):
         print(line)
 fp.close() # ファイルを閉じる --- (*3)

Jupyterで実行してみると、以下のように表示される。もちろん、冒頭にある変数wordの内容を"zoo"から"tree"や"leaf"などへと変更することで、別の単語を調べることもできる。つまり、(コメントを除くと)たった6行のプログラムで、英和辞書検索ツールを作ることができたということになる。

辞書ファイルからzooから始まる単語を検索したところ

プログラムの流れとしては、( *1 )でファイルを開き、( *2 )で読込処理を行って、( *3 )でファイルを閉じるというものになっている。

それで、このプログラムでポイントとなるのは、( *2 )の部分だ。ここでは、for line in fp:と書いている。この構文を使うと、ファイルの一行ずつにつき、字下げ(インデント)している部分を繰り返し実行する。それで、fpの部分にファイルオブジェクトを指定すると、一行読むごとに、変数lineにその内容が代入され、字下げされたブロックが実行される。

プログラムをもっと単純にすると以下のようになるだろう。

 fp = open(ファイル名, "rt")
 for line in fp:
     ここに一行ごとに行う処理を記述

それで、このプログラムでは、一行ごとに、startswithメソッドを使って、辞書内の英単語が、zooで始まっているかを判断している。そして、zooで始まっていれば、printで画面に出力する。

(番外編) readlineメソッドを使う方法について

また、open関数で得られるファイルオブジェクトには、一行分のテキストを読み出す、readlineメソッドもある。これを使って、以下のようなプログラムを作ることもできる。(これは、先ほどのプログラムと全く同じ動作をする。)

 word = "zoo" # 検索単語を指定
 fp = open("ejdic-hand-utf8.txt", "r", encoding="utf-8")
 # 一行ずつ読み取る
 while True:
     line = fp.readline() # 一行読む --- (*a)
     if not line: break # 末尾なら繰り返しを抜ける --- (*b)
     if line.startswith(word): print(line) # 見つけたら表示
 fp.close()

ただし、この場合、while構文を使うことになるため、明示的に一行読み込むコード( *aの部分 )や、読込の終了判定のコード( *bの部分 )を加える必要があるため、for .. in ..構文を使った方が簡潔になる。

特定の単語だけ抽出して別のファイルに保存してみよう

次に、よくある場面として、既存ファイルの一部分だけを抽出して、別のファイルに保存するというプログラムを作ってみよう。いわゆるフィルタ処理だが、こうした処理を行うには、正規表現が欠かせない。正規表現とは、ワイルドカードを高機能にしたものだ。文字列を特定の意味を持つメタ文字で表現するのだが、これを使うことで、文字列を検索したり、特定の部分を取り出したり、置換したりすることができる。

例えば、正規表現でドット「.」は、任意の一文字を表す。それで「a.m」という正規表現を書くと、「aim(目的)」「arm(腕)」という単語がマッチする。もし、「a...m」と書くと、aから始まってmで終わる5文字の単語を取り出すことができる。例えば「alarm(目覚まし)」とか「album(アルバム)」という単語を見つけることができる。

このように正規表現を使うと、非常に面白いのだが、今回は、ファイル処理を解説することが目的なので、このくらいにしておこう。

ここでは、英語辞書から「q」から始まる4文字の英単語だけを「q-list.txt」という別のファイルに保存するプログラムを作ってみよう。以下がそのプログラムだ。

 import re # 正規表現を使う --- (*1)

 # 辞書ファイルを開く --- (*2)
 fdic = open("ejdic-hand-utf8.txt", "rt", encoding="utf-8")
 # 書き込み先ファイルを開く
 fw = open("q-list.txt", "wt")

 # 一行ずつ読んで、qから始まる4文字の単語を調べる --- (*3)
 for line in fdic:
     if re.match(r"q[a-z]{3}\s", line):
         fw.write(line)
         print(line.strip())

 # ファイルを閉じる --- (*4)
 fw.close()
 fdic.close()

プログラムを実行すると、英和辞書を一行ずつ調べ、その中にあるqから始まる4文字の単語だけをファイルへ保存する。

英和辞書の中にあるqから始まる4文字の単語を抽出しファイルへ保存する

それでは、プログラムを見ていこう。プログラムの( *1 )の部分では、正規表現を使うためにreモジュールを取り込んでいる。Pythonでは様々な機能を使う際に、「import (モジュール名)」と書く決まりになっている。

プログラムの( *2 )の部分では、辞書データを読込専用で開き、保存先の「q-list.txt」を書き込み専用で開く。

そして、プログラムの( *3 )の部分では、一行ずつ辞書を読みつつ、qから始まる4文字の単語かどうかを正規表現によって調べる。そして、条件に合致する単語であれば、ファイルに保存する。

ここで、qから始まる4文字の単語を表す正規表現は「q[a-z]{3}\s」だ。[a-z]と書くと、a-zまでのいずれかの1文字を表す。そして、[a-z]{3}と書くと、a-zまでのいずれかの3文字という意味になる。

最後の( *4 )では、二つのファイルを閉じている。ファイルは開いて、処理をしたら、最後に閉じるという決まりだ。

まとめ

以上、今回は、テキスト形式の英語辞書データを題材にして、ファイル処理の方法を紹介した。テキスト処理のプログラミングを書くときは、今回のように、一行ずつ処理することが基本だ。なんと言っても、ファイルの入出力は、プログラミングに欠かせないものなので押さえておこう。

自由型プログラマー。くじらはんどにて、プログラミングの楽しさを伝える活動をしている。代表作に、日本語プログラミング言語「なでしこ」 、テキスト音楽「サクラ」など。2001年オンラインソフト大賞入賞、2004年度未踏ユース スーパークリエータ認定、2010年 OSS貢献者章受賞。技術書も多く執筆している。