引数としてファイル名を取り、ファイルの内容を処理するレガシー関数を使用したレガシーコードがいくつかあります。コードの実際の複製を以下に示します。
私がやりたいのは、このレガシー関数を使用するために生成したコンテンツをディスクに書き込む必要がないため、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
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()
)。
独自のオープン関数を定義できます
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
これは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'