3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Pythonをアップグレードしたら転送ファイルが0byteになった

3
Last updated at Posted at 2026-02-07

Pythonの継続的なI/Oパフォーマンス改善に伴う内部バッファサイズの拡大により、NamedTemporaryFileへの書き込み直後にファイルパスを外部処理へ渡すと、データがディスクに反映されず「中身が空(0バイト)」として扱われる事象に遭遇。

事象 : 転送ファイルが0バイトになる

Python3.12からPython3.14へアップグレードしたところ、転送ファイルが0バイトになってしまうことがあった。

from tempfile import NamedTemporaryFile

with NamedTemporaryFile('wb') as temp_file:
    temp_file.write(data)
    # ↓転送処理
    upload_file(temp_file.name) 

The problem is with flushing. The file output is buffered for efficiency reasons, so you must flush it for the changes to be actually written to the file.
引用元 : Stack Overflow: Python NamedTemporaryFile appears empty even after data is written

原因 : デフォルトバッファサイズが引き上げられた

Pythonのバージョンアップに伴うI/O最適化によるデフォルトバッファサイズの引き上げがあった。
データがメモリ内のバッファに留まる閾値が上がり、小さいサイズのファイルは反映前に転送されてしまい中身がなくなる。

例えば、100KBのファイルはPython3.13以前で問題なく転送されていたけれど、Python3.14では内容が反映される前に転送されてしまうので0バイトになってしまう。

項目 Python3.13以前 Python3.14以降
io.DEFAULT_BUFFER_SIZE 8KB 128KB
動的な最適化(Linux等)
「バッファサイズ」を賢く自動調節する仕組み
デバイスのブロックサイズ max(min(blocksize,8MiB),128KB)

gh-117151: Increase io.DEFAULT_BUFFER_SIZE from 8k to 128k and adjust open() on platforms where os.fstat() provides a st_blksize field (such as Linux) to use max(min(blocksize, 8 MiB), io.DEFAULT_BUFFER_SIZE) rather than always using the device block size. This should improve I/O performance. Patch by Romain Morotti.
引用元 : Changelog — Python 3.14.3

対応策

1. 明示的な flush() の実行

転送処理を呼び出す直前に、メモリ上のバッファ内容を強制的にディスクへ書き出す。

with NamedTemporaryFile('wb') as temp_file:
    temp_file.write(data)

    # 物理ディスクへの書き込みを強制実行
    temp_file.flush() 

    upload_file(temp_file.name)

2. delete_on_close=Falseの指定(Python3.12以降)

「クローズ時」ではなく「withブロック終了時」にファイル削除されるようにする。

To manage the named file, it extends the parameters of TemporaryFile() with delete and delete_on_close parameters that determine whether and how the named file should be automatically deleted.
tempfile --- 一時ファイルやディレクトリの作成 — Python 3.14.3 ドキュメント

from tempfile import NamedTemporaryFile

# delete_on_close=False により close() 時の削除を抑止
with NamedTemporaryFile('wb', delete_on_close=False) as temp_file:
    temp_file.write(data)

    # クローズによってバッファを完全にフラッシュ
    temp_file.close() 

    # 書き込み完了済みのパスを外部処理へ渡す
    upload_file(temp_file.name)

# withブロック終了時にファイルは自動削除

3. delete=False による手動管理

自動削除自体を無効化し、クローズ後のファイルを保持する。

import os
from tempfile import NamedTemporaryFile

temp_file = NamedTemporaryFile('wb', delete=False)
try:
    temp_file.write(data)
    temp_file.close() # クローズによる書き込み完了の保証

    upload_file(temp_file.name)
finally:
    # 処理終了後に明示的に削除を実行
    if os.path.exists(temp_file.name):
        os.unlink(temp_file.name) 

補足 : openpyxlのsave()はファイルのクローズまで一気にやる

openpyxlのsave()は「書き込み後にファイルを閉じる」処理までを完結させているので、バッファサイズの影響を受けずにデータがディスクへ反映される。

data = openpyxl.Workbook()
# ...ここでExcel内容を作成する処理がある...
with NamedTemporaryFile('wb') as temp_file:
    data.save(temp_file.name)

    upload_file(temp_file.name)

temp_file.write()を使う場合は、withブロックが開いている間はファイルが「オープン」されたままで、クローズ(=自動フラッシュ)が行われないため、バッファサイズを超えないと内容が反映されない。
しかし、data.save()は「オープン」→「書き込み」→「クローズ」まで完結させるので内容はディスクに反映される。

  1. save()メソッドは内部的に
  2. ZipFileを使用してアーカイブを作成し、
  3. データを書き込んだ後、即座にarchive.close()を実行してファイルを閉じる。
3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?