2

通常、HTTP 経由でリソースをダウンロードするためのこのチャンクのように、with ステートメントを使用して Python でファイルを処理します。

with (open(filename), "wb"):
    for chunk in request.iter_content(chunk_size=1024):
        if chunk:
            file.write(chunk)
            file.flush()

ただし、これはファイル名を知っていることを前提としています。を使用したいとしますtempfile.mkstemp()。この関数は、開いているファイルとパス名へのハンドルを返すためopenwithステートメントで使用するのは正しくありません。

少し調べてみたところ、mkstemp適切に使用するように注意することについて多くの警告が見つかりました。によって返された整数を捨てないでくださいと言うと、いくつかのブログ記事が叫びそうになりましたmkstemp。OS レベルのファイルハンドルが Python レベルのファイル オブジェクトと異なるという議論があります。それは問題ありませんが、それを保証する最も単純なコーディング パターンを見つけることができませんでした。

  • mkstemp書き込まれるファイルを取得するために呼び出されます
  • 書き込み後、例外が発生した場合でも、Python ファイルとその下にある os ファイルハンドルは両方ともきれいに閉じられます。with(open...これはまさに、パターンで得られる種類の動作です。

だから私の質問は、おそらく別の種類の with ステートメントを使用して、生成されたファイルを作成して書き込むための Python での良い方法はありますか、それともormkstempなどを手動で行う必要がありますか?このためのパターン。fdopenclose

4

1 に答える 1

12

これの最も単純なコーディング パターンはtry:/finally:です。

fd, pathname = tempfile.mkstemp()
try:
    dostuff(fd)
finally:
    os.close(fd)

ただし、これを複数回行う場合は、コンテキスト マネージャーでラップするのは簡単です。

@contextlib.contextmanager
def mkstemping(*args):
    fd, pathname = tempfile.mkstemp(*args)
    try:
        yield fd
    finally:
        os.close(fd)

そして、次のことができます:

with mkstemping() as fd:
    dostuff(fd)

もちろん、本当に必要な場合は、いつでも fd をファイル オブジェクトにラップすることができます (openまたはos.fdopen古いバージョンに渡すことにより)。しかし… なぜ余計な手間をかける必要があるのでしょうか? fd が必要な場合は、fd として使用します。

また、 fd が必要ないmkstemp場合は、単純で高レベルの の代わりに必要な正当な理由がない限り、低レベル API を使用しないNamedTemporaryFileでください。これを行うだけです:

with tempfile.NamedTemporaryFile(delete=False) as f:
    dostuff(f)

より単純であることに加えてwith、これは単なる OS ファイル記述子ではなく、すでに Python ファイル オブジェクトであるという利点もあります (Python 3.x では、Unicode テキスト ファイルにすることができます)。


さらに簡単な解決策は、一時ファイルを完全に回避することです。

ほとんどすべての XML パーサーには、ファイルではなく文字列を解析する方法があります。では、 の代わりにcElementTreeを呼び出すだけです。したがって、これの代わりに:fromstringparse

req = requests.get(url)
with tempfile.NamedTemporaryFile() as f:
    f.write(req.content)
    f.seek(0)
    tree = ET.parse(f)

…これを行うだけです:

req = requests.get(url)
tree = ET.fromstring(req.content)

もちろん、最初のバージョンでは XML ドキュメントと解析されたツリーをメモリ内に順番に保持するだけで済みますが、2 番目のバージョンでは両方を一度に保持する必要があるため、ピーク時のメモリ使用量が約 30% 増加する可能性があります。しかし、これが問題になることはめったにありません。

それXMLParser問題である場合、多くの XML ライブラリには、データが到着したときにデータをフィードする方法があり、多くのダウンロード ライブラリには、データをビットごとrequestsにストリーミングする方法があります。いくつかの異なる方法。例えば:

req = requests.get(url, stream=True)
parser = ET.XMLParser()
for chunk in iter(lambda: req.raw.read(8192), ''):
    parser.feed(chunk)
tree = parser.close()

…を使用するほど単純ではありませんfromstringが、一時ファイルを使用するよりも単純であり、おそらく起動がより効率的です。

の 2 引数形式の使用にiter混乱する場合 (多くの人は最初は理解に苦しむようです)、次のように書き直すことができます。

req = requests.get(url, stream=True)
parser = ET.XMLParser()
while True:
    chunk = req.raw.read(8192)
    if not chunk:
        break
    parser.feed(chunk)
tree = parser.close()
于 2014-01-03T21:26:58.130 に答える