web-dev-qa-db-ja.com

StringIOおよび「with」ステートメントとの互換性(コンテキストマネージャー)

引数としてファイル名を取り、ファイルの内容を処理するレガシー関数を使用したレガシーコードがいくつかあります。コードの実際の複製を以下に示します。

私がやりたいのは、このレガシー関数を使用するために生成したコンテンツをディスクに書き込む必要がないため、StringIOを使用して物理ファイル名の代わりにオブジェクトを作成できます。ただし、以下に示すように、これは機能しません。

StringIOがこれに対応する方法だと思いました。このレガシー関数を使用し、ディスク上のファイルではないがレガシー関数によってそのように扱うことができる引数に何かを渡す方法があるかどうか、誰にも教えてもらえますか?レガシー関数には、withパラメーター値の処理を行うfilenameコンテキストマネージャーがあります。

私がグーグルで出会った1つのことは: http://bugs.python.org/issue1286 ですが、それは私を助けませんでした...

コード

from pprint import pprint
import StringIO

    # Legacy Function
def processFile(filename):
    with open(filename, 'r') as fh:
        return fh.readlines()

    # This works
print 'This is the output of FileOnDisk.txt'
pprint(processFile('c:/temp/FileOnDisk.txt'))
print

    # This fails
plink_data = StringIO.StringIO('StringIO data.')
print 'This is the error.'
pprint(processFile(plink_data))

出力

これはFileOnDisk.txtの出力です:

['This file is on disk.\n']

これはエラーです:

Traceback (most recent call last):
  File "C:\temp\test.py", line 20, in <module>
    pprint(processFile(plink_data))
  File "C:\temp\test.py", line 6, in processFile
    with open(filename, 'r') as fh:
TypeError: coercing to Unicode: need string or buffer, instance found
42
mpettis

StringIOインスタンスは、既に開いているファイルです。一方、openコマンドは、開いているファイルを返すためにファイル名のみを取ります。 StringIOインスタンスはファイル名として適切ではありません。

また、StringIOインスタンスを閉じる必要がないため、コンテキストマネージャーとして使用する必要もありません。

レガシーコードで使用できるのがファイル名のみである場合、StringIOインスタンスは使用できません。代わりに tempfile module を使用して、一時ファイル名を生成します。

次に、contextmanagerを使用して、一時ファイルを確実にクリーンアップする例を示します。

_import os
import tempfile
from contextlib import contextmanager

@contextmanager
def tempinput(data):
    temp = tempfile.NamedTemporaryFile(delete=False)
    temp.write(data)
    temp.close()
    try:
        yield temp.name
    finally:
        os.unlink(temp.name)

with tempinput('Some data.\nSome more data.') as tempfilename:
    processFile(tempfilename)
_

新しいPython 3インフラストラクチャioモジュールによって提供される(Python 2および3)で利用可能)に切り替えることもできます。 _io.BytesIO_ は_StringIO.StringIO_/_cStringIO.StringIO_のより堅牢な代替品です。このオブジェクトは、コンテキストマネージャーとしての使用をサポートします(ただし、open())。

61
Martijn Pieters

独自のオープン関数を定義できます

fopen = open
def open(fname,mode):
    if hasattr(fname,"readlines"): return fname
    else: return fopen(fname,mode)

ただし、終了後に__exit__を呼び出したい場合、StringIOにはexitメソッドがありません...

このオープンで使用するカスタムクラスを定義できます。

class MyStringIO:
     def __init__(self,txt):
         self.text = txt
     def readlines(self):
          return self.text.splitlines()
     def __exit__(self):
          pass
6
Joran Beasley

これはpython doc contextmanager のドキュメントに基づいています

StringIOを単純なコンテキストでラップするだけで、exitが呼び出されると、yieldポイントに戻り、StringIOを適切に閉じます。これにより、一時ファイルを作成する必要がなくなりますが、文字列が大きい場合、StringIOがその文字列をバッファリングするため、メモリを使い果たします。文字列データが長くならないことがわかっているほとんどの場合にうまく機能します

from contextlib import contextmanager

@contextmanager
def buildStringIO(strData):
    from cStringIO import StringIO
    try:
        fi = StringIO(strData)
        yield fi
    finally:
        fi.close()

その後、次のことができます。

with buildStringIO('foobar') as f:
    print(f.read()) # will print 'foobar'
3
hjd