PythonのCSVに空行が増えるのはなぜ?newline=”と改行入りセルを実験する

Python

この記事について
この記事は、生成AIを活用した自動生成フローで作成しています。Python公式のcsvモジュール仕様と改行処理の説明を確認し、架空の備品データで安全に比較できる手順にまとめました。

検証ステータス:📘 Python公式仕様確認済み・掲載コードの実機実行未実施

PythonのCSVに空行が増えるのはなぜ?newline=”と改行入りセルを実験する

CSVをPythonで書き出したら、表計算ソフトで開くと行と行の間に空行が現れた。別のCSVでは、備考欄に入れた改行が行の区切りとして扱われた。こうした不具合は「CSVだから改行が一つ」という思い込みから生まれます。Python標準のcsvモジュールでファイルを開くときは、原則としてnewline=''を指定します。改行を自動変換する層とCSVの書式を管理する層を分けるためです。

対応環境と今回の実験

項目条件
言語Python 3系、標準ライブラリcsvとio
OSWindows、macOS、Linuxを想定
権限インストール追加不要。最初はメモリ内で完結
入力架空の備品名と備考。個人情報は使わない
確認Python公式仕様を確認。実行結果は期待値

実験ではディスクに書き込まず、io.StringIOという文字列の入出力先を使います。これはファイルに似た読み書きの窓口をメモリ上に作る仕組みです。いきなり実際の売上台帳や顧客CSVを上書きしないため、手元で試す練習に向いています。Windowsで問題になりやすい現象を説明しますが、StringIOの結果だけでWindowsのファイル上の改行変換を完全に再現したと主張するものではありません。

まず安全に試す:改行入りの備考を一つ入れる

以下をcsv_newline_demo.pyとして保存し、Python 3で実行してください。コードは文字列を作って読み戻すだけで、実ファイルは作成しません。

import csv
import io

rows = [
    ["品番", "備考"],
    ["A-001", "在庫あり"],
    ["B-002", "一階で確認\n二階へ移動"],
]

buffer = io.StringIO(newline="")
writer = csv.writer(buffer)
writer.writerows(rows)
raw = buffer.getvalue()

print("RAW:", repr(raw))
buffer.seek(0)
restored = list(csv.reader(buffer))
print("ROWS:", len(restored))
print("NOTE:", repr(restored[2][1]))

期待する観察点は、RAWの中で各レコードが改行で区切られ、改行を含む備考欄だけが二重引用符で囲まれていることです。標準のexcel方言では、書き出すレコード末尾は通常\r\nです。一方、備考欄の文字列には\nが含まれます。これらは別の役割の改行です。読み戻したROWSは見出しを含む3レコード、NOTEには途中の改行が残る想定です。

ここで重要なのは、画面上の「物理的な改行の数」とCSVとしての「レコード数」は一致しない場合があることです。備考欄の改行はレコードを増やすための区切りではありません。CSVパーサーは引用符の内側を一つのフィールドとして読むため、改行を含むセルも一つの値として復元できます。テキストエディターで行数を数えて「CSVが壊れた」と判断する前に、読み手がどの規則で解析するかを確かめましょう。

なぜnewline=”が必要なのか

Pythonでテキストファイルを開くopen()には、改行を処理する機能があります。通常のテキスト入出力では便利ですが、CSVのwriter自身もレコード終端を出力します。両方が改行の変換を担当すると、環境によって想定外の組み合わせになります。Python公式文書は、CSVファイルをreaderやwriterへ渡す場合、ファイルをnewline=''で開くよう指定しています。

特にWindowsでは、CSVライターが出した\r\nの\nに対してテキスト層がさらに\rを補い、\r\r\nのような並びになる問題が知られています。読み取り側でも、引用符内にある改行を勝手に正規化すると元データの改行情報が変わり得ます。newline=''は「改行を消す」指定ではなく、「テキスト層に余計な変換をさせず、CSV側に扱わせる」指定です。

次の図は、二つの役割の違いだけを示しています。データの文字コードや列の型は別の問題です。

flowchart TD
    A["文字列の表データ"] --> B["csv.writerが引用符とレコード終端を決める"]
    B --> C{"openのnewline設定"}
    C -->|"空文字"| D["テキスト層は改行を変換しない"]
    C -->|"既定値"| E["OSによって追加の改行変換"]
    D --> F["意図したCSVのバイト列"]
    E --> G["Windowsでは余計なCRが入り得る"]

この図で注目するのは、CSVの書式を決める処理が一度済んだ後に、別の層で変換が発生するかどうかです。newline=''を使ってもCSVの引用符やレコード区切りがなくなるわけではありません。二重処理を避けることが目的です。

一つだけ条件を変える:引用符が必要な文字を増やす

先ほどのrowsの最後を、["B-002", "棚A, 棚B"]に変更してみましょう。備考欄の改行をカンマに置き換えるだけです。CSVではカンマが列の区切りなので、csv.writerは値全体を引用符で囲むことが期待されます。カンマを含む値も一つのフィールドとして復元できることをrestored[2][1]で確かめられます。

さらに、"担当者が\"確認\""のように二重引用符を含む値も試せます。既定設定では値の内部の引用符を二つ重ねてCSVへ記録し、読み手が一つの引用符に戻します。これは改行処理とは異なる「フィールドのエスケープ」です。改行、カンマ、引用符はCSVの構造に影響する文字ですが、どれも文字列の中に存在してよい点が面白いところです。

ここでsplit(",")でCSVを自前解析すると、引用符の中のカンマまで区切ってしまいます。splitlines()でレコード数を数えても、引用符の中の改行を別レコードと誤認します。CSVは単なるカンマ区切りの文字列ではなく、引用符によって意味が変わる小さな書式です。読み書きとも標準のcsvモジュールを使う理由は、まさにこの規則を一箇所で扱えるからです。

実ファイルへ書く場合の最小形

メモリで理解したら、新規の検証用ファイルへ書き出せます。既存ファイル名を使うと上書きになるため、最初は別名にしてください。ファイルを開く段階でencoding="utf-8"も明示すると、OSの既定文字コードによる差を減らせます。

import csv

rows = [["品番", "備考"], ["A-001", "棚A, 棚B"]]

with open("sample-newline-test.csv", "w",
          encoding="utf-8", newline="") as f:
    csv.writer(f).writerows(rows)

with open("sample-newline-test.csv", "r",
          encoding="utf-8", newline="") as f:
    print(list(csv.reader(f)))

これはsample-newline-test.csvを作成または上書きします。実行前に同名ファイルがないことを確認してください。書いたものを同じPythonのcsv.readerで読み戻せれば、少なくとも同じ方言と文字コードの条件では復元できたことになります。ただし、それだけで他社システムやExcelの読み込み条件まで保証したことにはなりません。Excelの文字コード自動判定やCSV取り込み設定は別途確認が必要です。

失敗したとき、何を切り分けるか

空行が増えた場合は、まずOS、open()のnewline指定、書き出しライブラリ、出力したバイト列の順に確認します。見た目だけでなくrepr()やバイナリ読み取りで\r\r\nが存在するかを確認すれば、表示アプリ側の空行と、実際の余分な改行を区別できます。ただしバイナリで読み取った内容には秘密が含まれる場合があるため、調査ログへ全件を貼り付けないでください。

備考欄の途中でレコードが分かれた場合は、引用符が適切に閉じられているか、CSVを単純な文字列分割で読んでいないかを確認します。途中の引用符を勝手に削除して見かけだけ整えると、別の列へ値がずれる可能性があります。受け取り側が独自CSV仕様を採用しているなら、区切り文字や引用符、改行コードを相手の仕様書と照合します。

文字化けした場合はnewlineではなくencodingを調べます。改行コードと文字コードは別の設定です。UTF-8で保存しても、受け取り側が別の文字コードを前提にしていれば日本語は正しく読めません。ファイルの先頭にBOMを要求するシステムもあるため、UTF-8とUTF-8 BOM付きの違いを仕様に合わせて判断します。単に空行が消えたから文字コードも正しい、と結論づけないことが大切です。

列数が合わない場合はcsv.readerの各レコードの長さを検査します。ヘッダーが2列ならデータ行も2列かを確かめ、列数が異なる行を別に記録します。空文字と欠損値の違いもCSVだけでは表現しきれないことがあります。例えばNoneを書き出すと空文字になるのが標準のcsv.writerの動作で、読み戻しても元のNoneをそのまま復元できるわけではありません。

業務で使い分けるなら

定期的に備品台帳や申請一覧をCSVで連携するなら、書き出し時はnewline=''と文字コードを固定し、読み戻し時には列数・件数・重要項目の欠損を検査します。数件のサンプルで正常でも、実データにはカンマ、引用符、改行、空欄が混ざることがあります。検証用データにこれらを意図的に一件ずつ含めると、実運用で初めて壊れる事態を減らせます。

データの受け渡し条件を決めるときは、CSVの区切り文字、引用符の規則、文字コード、改行コード、ヘッダー有無、空欄の意味を相手と合意しておきます。単に「CSVで渡す」と決めただけでは、どのアプリでも同じ解釈になるとは限りません。Pythonのcsvモジュールは柔軟ですが、相手側の仕様を自動で保証する仕組みではありません。

最後に、CSVファイルをExcelで直接開く運用と、Power Queryなどで列型を指定して取り込む運用も区別しましょう。例えば商品コードの先頭ゼロを維持したい場合、改行処理だけでは解決しません。データの型をどう扱うかは別の設計判断です。今回の発見は「空行を消す魔法の引数」ではなく、「CSVの書式とテキスト入出力の責任を分ける」という考え方にあります。

参考資料

文書情報

記事タイトル
PythonのCSVに空行が増えるのはなぜ?newline=”と改行入りセルを実験する
作成日
更新日
Source URL
https://papanda925.com/?p=18552

ライセンス: 本記事のうち、当サイトが権利を有する本文・自作図表は、特記なき限り CC BY 4.0 で利用できます。生成AIを活用して作成・編集した内容を含みます。コードについて、別途ライセンス表示またはリンク先GitHubリポジトリのライセンスがある場合は、その条件を優先します。引用・第三者資料・画像・商標等は本ライセンスの対象外です。 利用ポリシー

タイトルとURLをコピーしました