【Python/openpyxl】openpyxlで遭遇したMemoryErrorへの対処方法
TL;DR
- openpyxlはxlsxファイルをメモリ内に展開してあれこれする
- 書き込み専用モードにすれば、メモリへの展開をいい感じにしてくれる
- とりま
wb = openpyxl.Workbook(write_only=True)
を使っとけばOK
環境
Tools | Versions |
---|---|
openpyxl | 3.0.3 |
Python | 3.6.9 |
発端
tsvファイルをxlsxファイルに変換するPythonスクリプトを書いた。
実際に動かしてみると、数十万件のデータを扱うと MemoryError
例外が投げられる。
どうやら、一般的なopenpyxlの使い方だと、大量のデータを扱えないようだ。
結果
元処理のイメージは以下。
import openpyxl def read_date(file_path): # ... ファイルからデータを読み込む処理 # ... 二次元リストで返却 return data if __name__ == '__main__': # ... input_data = read_data(in_file_path) wb = openpyxl.Workbook() # ☆ ws = wb.create_sheet(sheet_name) for row in input_data: data = [] for col in row: cell = openpyxl.cell.Cell(ws, value=col) data.append(cell) ws.append(data) wb.save(out_file_path) wb.close()
大量のデータを扱えるように改修したイメージは以下。
lxml
のパッケージが必要みたいだけど、エラーや警告が出たら入れればいいと思う。
import openpyxl def read_date(file_path): # ... ファイルからデータを読み込む処理 # ... 二次元リストで返却 return data if __name__ == '__main__': # ... input_data = read_data(in_file_path) wb = openpyxl.Workbook(write_only=True) # ☆ここを書き換えた ws = wb.create_sheet(sheet_name) for row in input_data: data = [] for col in row: cell = openpyxl.cell.Cell(ws, value=col) data.append(cell) ws.append(data) wb.save(out_file_path) wb.close()
ここ を読むと、どうやら書き込み専用でWorkbookを開けば問題は解決する模様。
公式ドキュメント
によれば、write_only=True
にすれば、
It is able to export unlimited amount of data (even more than Excel can handle actually), while keeping memory usage under 10Mb.
メモリ使用量を10MB以下に抑えながら、上限なくデータを出力できます。(意訳)
らしい。
他にも公式ドキュメントに記載されている注意点としては以下。
(書き込み専用で)開いたxlsxファイルは、ひとつもシートを持ってないのでcreate_sheet
メソッドで作ってやる必要がある。
試してみる。
import openpyxl wb = openpyxl.Workbook(write_only=True) print(wb.sheetnames) # => []
データの書き込みに使えるのは、rowsのappend
メソッドだけ。他は使えない。
>>> import openpyxl >>> wb = openpyxl.Workbook(write_only=True) >>> ws = wb.create_sheet('hoge') >>> print(wb.sheetnames) ['hoge'] >>> ws.append([1,2,3]) >>> ws.cell(row=2, col=1).value = 123 Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'WriteOnlyWorksheet' object has no attribute 'cell' >>> >>> wb_general = openpyxl.Workbook() >>> ws_general = wb_general.create_sheet('general') >>> print(type(ws_general).__name__) Worksheet >>> print(type(ws).__name__) WriteOnlyWorksheet
作成したws
インスタンスにcell
なんてアトリビュートねーですけど、と怒られる。
write_only=True
を付けるかどうかで、そもそも作成されるWorksheetのクラスが違うらしい。
(書き込み専用で)開いたxlsxファイルは、1回しかsave
メソッドは呼べない。save
メソッドを呼んだ後に再度save
メソッド呼んだり、Worksheetに変更を加えると、例外を飛ばします。
>>> import openpyxl >>> wb = openpyxl.Workbook(write_only=True) >>> ws = wb.create_sheet('hoge') >>> print(wb.sheetnames) ['hoge'] >>> ws.append([1,2,3]) >>> wb.save('hoge.xlsx') >>> wb.save('hoge.xlsx') Traceback (most recent call last): File "<stdin>", line 1, in <module> ... 中略 ... FileNotFoundError: [Errno 2] No such file or directory:
発展的話題
公式ドキュメント にある、
It is able to export unlimited amount of data (even more than Excel can handle actually), while keeping memory usage under 10Mb.
メモリ使用量を10MB以下に抑えながら、上限なくデータを出力できます。(意訳)
を信じるならば、Excelでの上限である、
機能 | 最大数 |
---|---|
ワークシートの行と列の合計数 | 1,048,576 行、16,384 列 |
(Excel の仕様と制限より作成)
を超えて、Worksheetにデータを書き込めるということでしょうか……。
書き込めたとしても、そのWorkbookはExcelでは二度と開けない気がする。
(今度試してみましょう)