このブログを検索

第11章 ファイル

難易度:◆◆◆◇◇

11.1.  没頭しよう

あるアプリケーションをインストールする前、私のWindowsノートPCには38,493のファイルがありました。Python3をインストールすると3,000ファイルが加わりました。

ファイルは、メジャーなOSすべてにおける主要な記憶概念です。考え方が浸透していて、ほとんどの人は代替のものを想像することができないでしょう。メタファー的な言い方をすると、あなたのコンピュータはファイルに溺れているのです。

11.2. テキストファイルから読み出す

ファイルから読む前に、ファイルを開く必要があります。Pythonでファイルを開くのは、この上なく簡単です。
a_file = open('examples/chinese.txt', encoding='utf-8')
Pythonにはビルトインのopen()関数があって、ファイル名を引数として受け取ります。ここでのファイル名はexamples/chinese.txtです。このファイル名には興味深いことが5つあります。
  1. ファイルの名前だけではなく、ディレクトリパスとファイル名の組み合わせになっています。ファイルを開く関数は、2つの引数一ディレクトリパスとファイル名一を受け取ることもできるでしょうが、open()関数では1つしか受け取りません。Pythonでは、"ファイル名"が必要なときはいつでも、ディレクトリを何層でも、あるいはすべてのパスでも含めることができます。
  2. ディレクトリパスはフォワードスラッシュを使っていますが、私はどのOSが対象か述べていませんでした。Windowsでは、サブディレクトリを示すときにバックスラッシュを使っていて、Max OSやLinuxはフォワードスラッシュを使っています。Pythonでは、Windowsであってもフォワードスラッシュがいつもきちんと動作します。
  3. ディレクトリがスラッシュやドライブ名で始まらないときは、相対パスと呼ばれます。何に対して相対か、と聞きたくなるかもしれませんね?我慢ですよ、バッタ君。(リンク)
  4. それは、文字列に対してです。現代のOSは(たとえWindowsでも!)、ファイルやディレクトリの名前を記憶するためにUnicodeを使っています。Python3は、ASCII形式でないパス名も完全にサポートしています。
  5. ローカルディスクである必要はなく、ネットワークドライブ上でもよいのです。"ファイル"は完全に仮想上のファイルシステムにあるかもしれません。コンピュータがファイルだとみなしてアクセスすれば、Pythonはそれを開くことができます。
open()関数の呼び出しはファイル名だけでは終わりません。もう1つの引数があって、エンコーディングと呼ばれています。おや、これはとても馴染み深いものですね。

11.2.1.  文字エンコーディングが問題になる

バイトは単なるバイトですが、文字は概念です。文字列はUnicode文字のシーケンスです。一方、ディスク上のファイルはUnicode文字のシーケンスではありません。実は、ディスク上のファイルはバイトのシーケンスなのです。

では、「テキストファイル」をディスクから読むと、Pythonはどのようにバイトのシーケンスを文字シーケンスに変換するのでしょうか?指定した文字エンコーディングのアルゴリズムに従ってバイトをデコードして、Unicode文字のシーケンス(=文字列)を返します。
# この例はWindowsで作られたもので、他の環境では
# 下記の理由で挙動が異なる可能性があります。
>>> file = open('examples/chinese.txt')
>>> a_string = file.read()
Traceback (most recent call last):
  File "", line 1, in 
  File "C:\Python31\lib\encodings\cp1252.py", line 23, in decode
    return codecs.charmap_decode(input,self.errors,decoding_table)[0]
UnicodeDecodeError: 'charmap' codec can't decode byte 0x8f in position 28: character maps to 
>>>
何が起こったのでしょうか?文字エンコーディングを指定しなかったので、Pythonはデフォルトのエンコーディングを使いました。デフォルトエンコーディングは何でしょうか?Tracebackを詳しく読むと、cp1252.pyで止まっていることがわかります。これはPythonがCP-1252をデフォルトとして使っていることを意味しています。
(CP-1252はMicrosoft Windowsで動くコンピュータで共通のエンコーディングです)。CP-1252文字セットはこのファイルにある文字をサポートしていないので、読もうとしても失敗して醜いUnicodeDecodeErrorが出ます。

ちょっと待ってください、もっと悪いことがあります!デフォルトエンコーディングはプラットフォームに依存するので、このコードはあなたのコンピュータで動くかもしれません(デフォルトエンコーディングがutf-8である場合)。ところが、他の人に渡したときには失敗します(デフォルトエンコーディングが違っていて、CP-1252だったりするとき)。

👉 デフォルトエンコーディングを調べるには、localeモジュールをインポートして、locale.getpreferredencoding()を呼び出します。私の場合、Windowsノートでは'cp1252'を返しますが、上の階にあるLiux Boxでは'UTF8'を返します。自分の家の中でも一貫性を保てないのです!あなたの環境では(Windowsでも)、どのバージョンのOSをインストールしていて、どの地域・言語設定で構成されているかに依存します。こういうわけで、ファイルを開くときにエンコーディングを調べることは非常に重要なのです。

11.2.2. ストリームオブジェクト

Pythonがopen()というビルトイン関数を持っていることは、これまでに学びました。open()関数はストリームオブジェクトを返します。ストリームオブジェクトは、メソッドと属性を持っていて、文字列の情報を取得したり流れを操作することができるのです。
>>> a_file = open('examples/chinese.txt', encoding='utf-8')
>>> a_file.name                                              # ①
'examples/chinese.txt'
>>> a_file.encoding                                          # ②
'utf-8'
>>> a_file.mode                                              # ③
'r'
①name属性は、ファイルを開くときにopen()関数に渡した名前に対応します。絶対パス名でなくてもかまいません。

②同様に、encoding属性はopen()関数に渡したエンコーディングに対応します。もしファイルを開いたときにエンコーディングを指定しなければ(だめな開発者だ!)、この属性はlocale.getpreferredencoding()を反映します。

③mode属性はファイルをどのモードで開いたかを教えてくれます。オプションでmodeパラメータをopen()関数に渡すことができます。

このファイルを開いたときはモードを指定しなかったので、Pythonは'r'をデフォルトとして使っています。'r'は「読み取り専用でテキストモードを開く」ということです。この章のあとで出てきますが、ファイルモードは目的によって数種類が使われます。別のモードでは、ファイルの書き込み、追加、バイナリモード(文字列ではなくてバイトで扱う)で開くことができます。

👉 open()関数のドキュメントにはすべてのファイルモードがリストされています。

11.2.3. テキストファイルからデータを読む

読むためにファイルを開いたら、次はそれを読みたくなるでしょう。
>>> a_file = open('examples/chinese.txt', encoding='utf-8')
>>> a_file.read()                                            # ①
'Dive Into Python 是为有经验的程序员编写的一本 Python 书。\n'
>>> a_file.read()                                            # ②
''
①(正しいエンコーディングで)ファイルを開いたら、ストリームオブジェクトのread()メソッドを呼んで読むことができます。結果は文字列になります。

②ある意味驚きですが、もう1度ファイルを読んでも例外は出ません。Pythonはend-of-fileの先を読んだとしてもエラーにならず、空白の文字列を返すだけです。

もう1度ファイルを読みたいときは、どうすればよいでしょうか?
# 前の例からの続きです
>>> a_file.read()                      # ①
''
>>> a_file.seek(0)                     # ②
0
>>> a_file.read(16)                    # ③
'Dive Into Python'
>>> a_file.read(1)                     # ④
' '
>>> a_file.read(1)
'是'
>>> a_file.tell()                      # ⑤
20
①ファイルの末尾まで来ているので、read()メソッドをもう一度呼び出したとしても、ストリームオブジェクトは空白の文字列を返すだけです。

②seek()メソッドは指定したファイル中のバイト位置に移動します。

③read()メソッドは、オプションでパラメータを受け取って、読む文字数を決めます。

④しようと思えば、1文字ずつ読むこともできます。

⑤16+1+1= …20?

もう1度やってみましょう。
# 前の例からの続きです
>>> a_file.seek(17)                    # ①
17
>>> a_file.read(1)                     # ②
'是'
>>> a_file.tell()                      # ③
20
①17番目のバイトに移ります。

②1文字読みます。

③今は20番目のバイト上にいます。

ついて来れていますか?seek()とtell()メソッドは常にバイトをカウントしますが、このファイルはテキストとして開かれているので、read()メソッドは文字をカウントします。

中国語をurf-8でエンコードするには複数のバイトが必要です。ファイル中の英語の文字にはそれぞれ1バイトだけ必要なので、seek()とread()メソッドが同じようにカウントしていると誤解してしまったかもしれません。しかし、それが正解なのは限られた文字に対してのみです。

ちょっと待って・・・もっと難しくなります!
>>> a_file.seek(18)                         # ①
18
>>> a_file.read(1)                          # ②
Traceback (most recent call last):
  File "", line 1, in 
    a_file.read(1)
  File "C:\Python31\lib\codecs.py", line 300, in decode
    (result, consumed) = self._buffer_decode(data, self.errors, final)
UnicodeDecodeError: 'utf8' codec can't decode byte 0x98 in position 0: unexpected code byte
①18番目のバイトに行って1文字読みます。

②なぜ失敗するのでしょうか?それは、18番目のバイトに文字がないからです。最も近いのは、17番目のバイトから始まる文字です(そこから3バイト続きます)。文字の途中から読もうとすると、UnicodeDecodeErrorが出て失敗します。

11.2.4. ファイルを閉じる

ファイルを開くとシステムリソースを消費しますし、ファイルモードによっては他のプログラムがファイルにアクセスできなくなるかもしれません。使い終わったらすぐにファイルを閉じることが重要です。
# 前の例からの続きです
>>> a_file.close()

さて、簡単に閉じることができました。

ストリームオブジェクトa_fileはこのときも存在しています。つまり、close()メソッドはオブジェクトそのものを破壊しません。しかし、このことはそこまで不便ではありません。
# 前の例からの続きです
>>> a_file.read()                           # ①
Traceback (most recent call last):
  File "", line 1, in 
    a_file.read()
ValueError: I/O operation on closed file.
>>> a_file.seek(0)                          # ②
Traceback (most recent call last):
  File "", line 1, in 
    a_file.seek(0)
ValueError: I/O operation on closed file.
>>> a_file.tell()                           # ③
Traceback (most recent call last):
  File "", line 1, in 
    a_file.tell()
ValueError: I/O operation on closed file.
>>> a_file.close()                          # ④
>>> a_file.closed                           # ⑤
True
①閉じたファイルから読み込むことはできないので、IOError例外が上がります。

②閉じたファイルをseekすることもできません。

③閉じたファイルにカレントポジションはありませんので、tell()メソッドも失敗します。

④意外なことに、ファイルを閉じたストリームオブジェクトに対してclose()メソッドを呼び出しても、例外は出ません。単に何も起こらないのです。

⑤閉じたストリームオブジェクトには、便利な属性があります。このclosed属性は、ファイルが閉じていることを確認できます。

11.2.5. ファイルを自動で閉じる

ストリームオブジェクトは明確なclose()メソッドを持っていますが、コードにバグがあったり、close()を呼び出す前にクラッシュすると、どうなるでしょうか?自分のローカル環境でデバッグする限りは大した問題ではありませんが、プロダクションサーバ上では大問題になるかもしれません。

Python2には、これに対する解決策があります。Try..finally文です。これはPython3でも効果があって、他の人のコードや、Python3に移植された古いコードの中で見ることがあるかもしれません。一方、Python2.6ではもっと美しい方法が導入されました。それはwith文で、Python3でも好まれている方法です。
with open('examples/chinese.txt', encoding='utf-8') as a_file:
    a_file.seek(17)
    a_character = a_file.read(1)
    print(a_character)
このコードはopen()を呼び出しますが、a_file.close()を呼んでいません。with文はif文やforループのようにコードブロックを開始します。このコードブロックの中では、open()を呼び出して返ってきたストリームオブジェクトとして変数a_fileを扱うことができます。すべての標準ストリームオブジェクトのメソッドーseek()、read()なども何でも使うことができます。ブロックが終わると、Pythonはa_file.close()を自動的に呼び出します。

一番よいところは、いつ、どのようにwithブロックから抜けたとしても、たとえ想定外の例外で"exit"したとしても、Pythonはファイルを閉じるということです。そのとおり、コードが例外を上げてプログラム全部が停止する場合でも、ファイルは閉じられるのです。確実にです。

👉 厳密には、with文はランタイム コンテクストを生成します。この例では、ストリームオブジェクトはコンテクストマネージャとして動作します。Pythonはストリームオブジェクトa_fileを生成して、ランタイムコンテクストに入るように命令します。withブロックが終了したとき、Pythonはストリームオブジェクトにランタイムコンテクストから抜けるように命令し、ストリームオブジェクトは自分のclose()メソッドをコールするのです。詳細はAppendix B, “Classes That Can Be Used in a with Block” (リンク予定)を見てください。

with文では、ファイル特有のものは何もありません。with文はジェネリックフレームワークであって、ランタイムコンテクストを作ってオブジェクトに出入りするように命令します。もし、今対象になっているオブジェクトがストリームオブジェクトであれば、ファイルとして役に立つことをします(ファイルを自動で閉じるであるとか)。ただし、その動作はストリームオブジェクト内で定義されたものであって、with文の中では定義されません。ファイルと関係のないコンテクストマネージャを使う方法が他にも沢山あります。この章で出てきますが、自分で作ることもできます。

11.2.6. データを1度に1行読む

テキストファイルの"行"が何かというと、文字をタイプしてENTERを押すと新しい行に来る、という想像の通りのものです。テキスト行は文字の連続ですが、そのデリミタは・・・何でしょうか?
実は、複雑なのです。キャリッジ・リターン文字を使うものもあれば、ラインフィード文字を使うものもありますし、両方とも使うものもあります。

Pythonでは、自動的に行の末尾をデリミタとするので一安心です。もし「このファイルを1行ずつ読み込みたい」というのであれば、Pythonはどの行末を使えばよいかを理解して、上手くやってくれます。

👉 行末をどう判断するかを厳密にする必要があるならば、追加でnewlineパラメータをopen()を関数に渡せばよいでしょう。詳しくはopen()を関数のドキュメントを見てください。


では、実際にはどうするのでしょうか?ファイルを1行ずつ読む、それだけです。非常にシンプルで、美しいものです。
line_number = 0
with open('examples/favorite-people.txt', encoding='utf-8') as a_file:  # ①
    for a_line in a_file:                                               # ②
        line_number += 1
        print('{:>4} {}'.format(line_number, a_line.rstrip()))          # ③
①withを使って、ファイルを安全に開いてPythonに閉じてもらいましょう。

②ファイルを1行ずつ読むためには、forループを使います。それだけです。ストリームオブジェクトは、read()のような明確なメソッドも持っていますが、イテレータとしても使えるため、値を求められると1行ずつ出力するのです。

③文字列メソッドformat()を使って、行の番号と行そのものを出力することができます。フォーマット指定子{:>4}は「4文字以内でこの引数を出力せよ」という意味です。変数a_lineはすべての行や改行を含んでいます。文字列メソッドrstrip()はあとに続く空白(改行を含む)を削除します。
you@localhost:~/diveintopython3$ python3 examples/oneline.py
   1 Dora
   2 Ethan
   3 Wesley
   4 John
   5 Anne
   6 Mike
   7 Chris
   8 Sarah
   9 Alex
  10 Lizzie
このエラーが出ていませんか?
you@localhost:~/diveintopython3$ python3 examples/oneline.py
Traceback (most recent call last):
  File "examples/oneline.py", line 4, in 
    print('{:>4} {}'.format(line_number, a_line.rstrip()))
ValueError: zero length field name in format
もし出ているのであれば、Python3.0を使っているのでしょう。Python3.1にアップグレードしましょう。

Python3.0では文字列フォーマットをサポートしていますが、明示的に数字を入れたフォーマット指定子のみが対象になっています。Python3.1ではフォーマット指定子で引数インデックスを省略することができます。Python3.0対応のバージョンを比較のために書いておきます。
print('{0:>4} {1}'.format(line_number, a_line.rstrip()))

11.3. テキストファイルに書き込む

ファイルに書き込む方法は、読み込みとほぼ同じです。まず、ファイルを開いてストリームオブジェクトを取り出し、次にストリームオブジェクト上でメソッドを使ってファイルにデータを書き込み、ファイルを閉じます。

ファイルを開いて書き込むときは、open()関数を使って書き込みモードを指定することができます。書き込みモードには2種類あります。

  • "Write"モードはファイルを上書きします。open()関数にmode='w'を渡します。
  • "Append"モードはファイルの末尾にデータを追加します。open()関数にmode='a'を渡します。

どちらのモードでもファイルがない場合は自動で作成するので、「ファイルがない場合に最初に開くための空白ファイルを新しく作成する」といった面倒なことは決してありません。ただ単にファイルを開いて書き込み始めます。

書き込みが終わったなら、すぐにファイルを閉じるべきです。ファイルハンドルを開放して、データが確実にディスクに書き込まれたことを確かめるためです。ファイルからデータを読んだときと同様、ストリームオブジェクトのclose()メソッドが使えますし、with文でPythonにファイルを閉じてもらってもよいでしょう。どちらの方法を私がお勧めするかは、きっとお分かりのことでしょう。
>>> with open('test.log', mode='w', encoding='utf-8') as a_file:  # ①
...     a_file.write('test succeeded')                            # ②
>>> with open('test.log', encoding='utf-8') as a_file:
...     print(a_file.read())
test succeeded
>>> with open('test.log', mode='a', encoding='utf-8') as a_file:  # ③
...     a_file.write('and again')
>>> with open('test.log', encoding='utf-8') as a_file:
...     print(a_file.read())
test succeededand again                                           # ④
①新しくtest.logファイルを作成して(あるいはファイルを上書きして)、ファイルを書き込みモードで開きます。このmode='w'というパラメータは、書き込むために開くという意味です。そう、危険なものなのです。ファイルの中身(ある場合)がどうなっても気にしないのであれば問題ありません。なぜなら、データは消えてしまうからです。

②open()関数が返したストリームオブジェクトのwrite()メソッドを使って、新しく開いたファイルにデータを追加することもできます。

③とても楽しいですね。もう1度やってみましょう。今回はmode='a'を使って上書きではなく追加にしてみましょう。追加モードでは、ファイルの既存の内容に悪さをすることは決してありません。

④ 元から書いてあった行と、今2行目に追加した行がファイルtest.logの中にあります。改行やラインフィードは含まれていないことに注意しましょう。どちらのときも明記しなかったので、ファイルには含まれていないのです。改行は'\r'で、ラインフィードは'\n'で表すことができます。どちらもない場合は、書き込んだすべてのものが1行になります。

11.3.1. 再び文字エンコーディング

書き込むためにファイルを開くとき、open()関数にエンコーディングパラメータが渡されていることに気がつきましたか?重要なことです。これは省略できません(※)!この章の最初でも見たように、ファイルは文字列ではなくバイトを保持しているのです。Pythonにどのエンコーディングを使ってバイトストリームを読んで文字列に変換するか伝えるからこそ、"文字列"をテキストファイルから読むことが可能になります。

逆にファイルにテキストを書き込むときにも、同様の問題を含んでいます。文字をファイルに書き込むことはできません。なぜなら、文字というものは概念だからです。ファイルに書き込むために、文字列をどのようなバイトシーケンスに変換するかをPythonが知っている必要があります。正しく変換するための唯一の方法は、書き込みモードでファイルを開くときにエンコーディングパラメータを明示することです。

(※訳注: 省略するとプラットフォーム依存のencodingが使用される)

11.4. バイナリファイル

すべてのファイルがテキストを含むわけではありません。犬の写真の場合もあります。
>>> an_image = open('examples/beauregard.jpg', mode='rb')                # ①
>>> an_image.mode                                                        # ②
'rb'
>>> an_image.name                                                        # ③
'examples/beauregard.jpg'
>>> an_image.encoding                                                    # ④
Traceback (most recent call last):
  File "", line 1, in 
AttributeError: '_io.BufferedReader' object has no attribute 'encoding'
①バイナリモードでファイルを開くことは、シンプルですが効果的です。テキストモードとの唯一の違いは、パラメータが'b'を含んでいることです。

②ファイルをバイナリモードで開いて得たストリームオブジェクトにも、多くの属性があります。mode属性はopen()関数に渡したモードパラメータを返します。

③テキストのストリームオブジェクトと同様に、バイナリのストリームオブジェクトはname属性を持っています。

④ただし、この違いがあります。バイナリ ストリームオブジェクトには、encoding属性がありません。わかりますか?文字列ではなく、バイトを読んで(書き込んで)いるため、Pythonでは何も変換できないのです。バイナリファイル、または入力したそのままを取得するので、変換する必要がありません。

バイトを読んでいることをすでに言いましたよね?そういうことなのです。
# continued from the previous example
>>> an_image.tell()
0
>>> data = an_image.read(3)  # ①
>>> data
b'\xff\xd8\xff'
>>> type(data)               # ②

>>> an_image.tell()          # ③
3
>>> an_image.seek(0)
0
>>> data = an_image.read()
>>> len(data)
3150
①テキストファイルのように、バイナリファイルを少しずつ読むことができますが、極めて大きな違いがあります・・・

②バイトを読み込んでいるのであって、文字列ではありません。バイナリモードでファイルを開いたので、read()メソッドが受け取るのは、読むバイト数で文字数ではありません。

③つまり、read()メソッドに渡した数字とtell()メソッドで得られる位置インデックスは必ず一致します。read()メソッドはバイトを読み、seek()、tell()メソッドは読んだバイト数を返します。これらは、バイナリファイルでは常に一致します。

11.5. ファイルではないソースからのストリームオブジェクト

ライブラリを記述して、ライブラリ関数の1つがファイルからデータを読み込もうとしていることを想像してみましょう。ファイル名を受け取るだけで、関数はファイルを開いて、読んで、exitする前にファイルを閉じます。これは、やってはいけないことです。 代わりに、APIが任意のストリームオブジェクトを受け取るべきなのです。

最もシンプルなストリームオブジェクトの例は、read()メソッドから出てくるもので、オプションでサイズパラメータを受け取って文字列を返します。サイズパラメータなしでread()メソッドを呼び出すと、入力ソースから読めるものをすべて読んで、1つの値として返します。 サイズパラメータを入れて呼び出すと、入力ソースからそのサイズパラメータの分だけデータを返します。もう1度呼び出されると、先ほど残した部分を見つけて続きのデータを返します。

これはまさに実ファイルを開くと得られるストリームオブジェクトと同じように見えます。違いは、実ファイルに限定されていないということです。"read"される入力ソースは何でもよいのです。Webページ、メモリの中の文字列、他のプログラムの出力でも構いません。関数がストリームオブジェクトを受け取って、そのままオブジェクトのread()メソッドを呼び出す限り、どんな入力ソースであっても、それぞれに対して特別なコードを必要とせずに1つのファイルのように扱うことができるのです。
>>> import io                                  # ①
>>> a_file = io.StringIO(a_string)             # ②
>>> a_file.read()                              # ③
'PapayaWhip is the new black.'
>>> a_file.read()                              # ④
''
>>> a_file.seek(0)                             # ⑤
0
>>> a_file.read(10)                            # ⑥
'PapayaWhip'
>>> a_file.tell()
10
>>> a_file.seek(18)
18
>>> a_file.read()
'new black.'
①ioモジュールがStringIOクラスを定義するので、メモリ上の文字列をファイルとして扱えるようになります。

②ストリームオブジェクトを文字列から作るためには、io.StringIO()のクラスを作って"file"データとして使いたい文字列を渡します。ストリームオブジェクトを扱っているので、ストリーム的なことを何でもすることができます。

③read()メソッドを呼び出す"file"のすべてを"read"します。StringIOオブジェクトの場合、そのまま元の文字列を返します。

④実ファイルと同様に、もう1度read()メソッドを呼び出すと空の文字列を返します。

⑤StringIOオブジェクトのseek()メソッドを使うと、明示的に文字列の最初を探すことができます。これはまさにrealファイルの中を探しているかのようです。

⑥サイズパラメータをread()メソッドに渡すことで、文の中の文字列を読むこともできます。
👉 io.StringIOで文字列をテキストファイルのように扱うことができます。また、io.ByteIOというクラスによってバイトアレイをバイナリファイルのように扱うことができます。

11.5.1. 圧縮されたファイルを扱う

Pythonの標準ライブラリには、圧縮ファイルの読み書きをサポートするモジュールがあります。圧縮のスキームは数多くありますが、Windows以外のシステムで人気があるのはgzipとbzipの2つです (PKZIPやGNU Tarアーカイブも目にしたことがあるかもしれませんね。Pythonにはそれらに対応したモジュールもあります。)

gzipモジュールでは、ストリームオブジェクトを作ってgzip圧縮フ ァイルを読み書きすることができます。ストリームオブジェクトは(読むために開いたのであれば) read()メソッド、 (書くために開いたのであれば) write()メソッドをサポートしてくれます。つまり、これらのメソッドは通常のファイルを操作するときの方法でgzip圧縮ファイルを読み書きすることができるので、展開したデータを格納する一時ファイルを作る必要はありません。

さらに、with文もサポートしているので、用が済んだgzipファイルをPythonに自動で閉じさせることができます。
you@localhost:~$ python3

>>> import gzip
>>> with gzip.open('out.log.gz', mode='wb') as z_file:                                      # ①
...   z_file.write('A nine mile walk is no joke, especially in the rain.'.encode('utf-8'))
...
>>> exit()

you@localhost:~$ ls -l out.log.gz                                                           # ②
-rw-r--r--  1 mark mark    79 2009-07-19 14:29 out.log.gz
you@localhost:~$ gunzip out.log.gz                                                          # ③
you@localhost:~$ cat out.log                                                                # ④
A nine mile walk is no joke, especially in the rain.
①gzipファイルは必ずバイナリモードで開かなくてはなりません('b'はモード引数)。

②この例はLinux上で作ったものです。コマンドラインに不慣れな人のために説明すると、このコマンドはPythonシェルでgzip圧縮ファイルを"long listing"した結果を出力しています。このリストによると、対象ファイルは存在していて(よかったです) 、長さは79バイトです 。これは、始めたときの文字列よりも長くなっています。gzipファイルフォーマットには、ファイルのメタデータの入った固定長のへッダが含まれています。そのため、極端に小さいファイルの圧縮に向いていません。

③gunzipコマンド(ジー・アンジップと読みます)は、ファイルを展開し、元の圧縮ファイルの.gz拡張子を抜いた名前で新しいファイルを保存します。

④catコマンドはファイルの中身を表示します。このファイルは圧縮したファイルout.log.gzにPythonShelIから書き込んだ元の文字列を含んでいます。

このエラーが出ていませんか?
>>> with gzip.open('out.log.gz', mode='wb') as z_file:
...         z_file.write('A nine mile walk is no joke, especially in the rain.'.encode('utf-8'))
...
Traceback (most recent call last):
 File "", line 1, in 
AttributeError: 'GzipFile' object has no attribute '__exit__'

もし出ているのであれば、Python3.0を使っているのでしょう。Python3.1にアップグレードしましょう。

Python3.0にはgzipモジュールがありましたが、gzipファイルオブジェクトのコンテクストマネージャとしての使用をサポートしていません。Python3.1からはgzipファイルオブジェクトをwith文の中で使うことができるようになりました。


11.6. 標準入力、出力、エラー

コマンドラインの達人であれば、すでに標準入力、標準出力、標準エラーに慣れ親しんでいることでしょう。この節は、そうでない人のために書かれています。

標準出力と標準エラー(stdout、stderrと略されるのが一般的)は、Mac OSXやLinuxといったすべてのUnix系システムに組み込まれたパイプです。print()関数を呼び出したとき、出力したいものがstdoutパイプに送られます。プログラムがクラッシュしてtracebackを出力するとき、stderrパイプに送られます。デフォルトでは、これらのパイプはそのときの作業ターミナルウィンドウに繋がっているだけです。そのためプログラムが何かを表示するとき、出力はターミナルウィンドウに出ますし、プログラムがクラッシュしたとき、tracebackはターミナルウィンドウに出ます。グラフィカルなPython shellでは、stdoutとstderrはデフォルトで「インタラクティブ ウィンドウ 」にパイプしています。
>>> for i in range(3):
...     print('PapayaWhip')                # ①
PapayaWhip
PapayaWhip
PapayaWhip
>>> import sys
>>> for i in range(3):
...     l = sys.stdout.write('is the')     # ②
is theis theis the
>>> for i in range(3):
...     l = sys.stderr.write('new black')  # ③
new blacknew blacknew black
①print()関数をループします。特に驚くことはありません。

②stdoutはsysモジュール中に定義されている、ストリームオブジェクトです。write()関数を呼び出すと、与えられた任意の文字列を出力し、出力の長さを返します。実のところ、print関数がやっているのはこういうことです。出力する文字列の最後にキャリッジリターンを付けてsys.stdout.writeを呼び出しているのです。

③最もシンプルな例では、sys.stdoutとsys.stderrでは出力を同じところに送ります。つまり、Python IDE(使っている場合)、またはターミナル(コマンドラインからPythonを走らせているとき) です。標準エラーは、標準出力のようにキャリッジリターンを付けてはくれません。キャリッジリターンが欲しいときは、キャリッジリターン文字を書く必要があります。

sys.stdoutとsyt.stderrはストリームオブジェクトなので、書き込み専用です。read()メソッドを呼び出そうとすると、必ずIOErrorが出ます。
>>> import sys
>>> sys.stdout.read()
Traceback (most recent call last):
  File "", line 1, in 
IOError: not readable

11.6.1. 標準出力にリダイレクトする

sys.stdout、sys.stderrはストリームオブジェクトで、書き込みしかサポートしていません。それにも関わらず、定数ではなく変数なのです。つまり、新しい値を — 他のストリームオブジェクトであっても — 代入して、出力をリダイレクトすることができるのです。
import sys

class RedirectStdoutTo:
    def __init__(self, out_new):
        self.out_new = out_new

    def __enter__(self):
        self.out_old = sys.stdout
        sys.stdout = self.out_new

    def __exit__(self, *args):
        sys.stdout = self.out_old

print('A')
with open('out.log', mode='w', encoding='utf-8') as a_file, RedirectStdoutTo(a_file):
    print('B')
print('C')
これを見てみましょう。
you@localhost:~/diveintopython3/examples$ python3 stdout.py
A
C
you@localhost:~/diveintopython3/examples$ cat out.log
B

このエラーが出ていませんか?
you@localhost:~/diveintopython3/examples$ python3 stdout.py
  File "stdout.py", line 15
    with open('out.log', mode='w', encoding='utf-8') as a_file, RedirectStdoutTo(a_file):
                                                              ^
SyntaxError: invalid syntax
もし出ているのであれば、Python3.0を使っているのでしょう。Python3.1にアップグレードしましょう。

Python3.0はwith文をサポートしていますが、各文は1つのコンテクストマネージャしか使えません。Python3.1は1つのwith文で複数のコンテクストマネージャを繋げることができます。

最後の部分を先に見てみましょう。
print('A')
with open('out.log', mode='w', encoding='utf-8') as a_file, RedirectStdoutTo(a_file):
    print('B')
print('C')
少し複雑なwith文になっているので、わかりやすい形に書き直してみましょう。
with open('out.log', mode='w', encoding='utf-8') as a_file:
    with RedirectStdoutTo(a_file):
        print('B')
書き直したものを見てみると判るように、実は2つのwith文が入っています。1つはもう1つのスコープ内にネストされています。"外側"のwith文は以前に出てきたものです。utf-8エンコーディングのout.logという名前のテキストファイルを書き込みモードで開いてストリームオブジェクトをa_fileという変数に代入します。しかし、奇妙なのはそれだけではありません。
with RedirectStdoutTo(a_file):
as節はどこに行ったのでしょうか。実は、with文でのasは、必須ではないのです。例えば関数を呼び出したときに返り値を無視してもよいのと同じように、with文を使ったときにwithの中身を変数に代入しないでもよいのです。今のケースでは、RedirectStdoutToコンテクストの副作用についてだけ知りたいからです。

副作用とは何でしょうか?クラスRedirectStdoutToの中を見てみましょう。このクラスはカスタムコンテクストマネージャです。どんなクラスでもコンテクストマネ ージャになれますが、そのためには2つの特別なメソッド__enter__()と__exit__()を定義します。
class RedirectStdoutTo:
    def __init__(self, out_new):    # ①
        self.out_new = out_new

    def __enter__(self):            # ②
        self.out_old = sys.stdout
        sys.stdout = self.out_new

    def __exit__(self, *args):      # ③
        sys.stdout = self.out_old
①__init__()メソッドはインスタンスが作成されたら直ぐに呼び出されます。パラメータを1つ受け取ります。コンテクストがあるときは標準出力として使うストリームオブジェクトです。このメソッドはストリームオブジェクトをインスタンス変数にそのまま保存するので、他のメソッドはあとでこのインスタンス変数を使うことができます。

②__enter__()メソッドは特別なクラスメソッドです。Pythonはコンテクストに入るときに (つまり、with文の冒頭で) これを呼び出します。このメソッドはsys.stdoutの現在の値をself.out_oldに保存し、self.out_newをsys.stdoutに代入して標準出力にリダイレクトします。

③__enter__()メソッドも、特別なクラスメソッドです。Pythonはコンテクストから抜けるときに (つまり、with文の最後で) これを呼び出します。このメソッドは保存されたself.out_oldの値をsys.stdoutに代入して標準出力に元の値を再現します。

全部合わせると:
print('A')                                                                             # ①
with open('out.log', mode='w', encoding='utf-8') as a_file, RedirectStdoutTo(a_file):  # ②
    print('B')                                                                         # ③
print('C')                                                                             # ④
①これがIDEの"インタラクティブウィンドウ"に出力されます(スクリプトをコマンドラインから走らせている場合は、コマンドラインに出力されます)。

②このwith文はコンマで分けられたリストを受け取ります。このリストはwithブロックでネストされた連なりのように振る舞います。リストの最初の部分は"outer"ブロックで、最後の部分は"inner"ブロックです。最初の部分がファイルを開き、2番目の部分がsys.stdoutを最初の部分で作られたストリームオブジェクトにリダイレクトします。

③このprint()関数はwith文によって生成されたコンテクストと共に実行されるので、スクリーンには出力されません。ファイルout.logに書き込まれます。

④withコードブロックが終了しました。コンテクストを抜けるとき、Pythonは各コンテクストマネージャに対して、することを何であれ命令完了しています。コンテクストマネージャは後入先出法です。終了時に2番目の文がsys.stdoutを元の値に戻していて、最初の文がout.logというファイルを閉じています。標準出力は元の値で読み込まれ、print()関数を呼び出すと再びスクリーンに表示されます。

標準エラーにリダイレクトする場合も全く同じ方法で、sys.stdoutの代わりにsys.stderrを使います。


11.7. さらに読むには


    Reading and writing files in the Python.org tutorial
    io module
    Stream objects
    Context manager types
    sys.stdout and sys.stderr
    FUSE on Wikipedia

0 件のコメント:

コメントを投稿