Pythonのzipでデータが消える?strict=Trueで行数のずれを検出する

プログラミング・Web開発カテゴリを表すパンダのイラスト プログラミング・Web開発

この記事について
この記事は、生成AIを活用した自動生成フローで作成しています。Python公式の組込み関数zipとstrict引数の仕様を確認し、架空の備品データで行数のずれを発見する手順を整理しました。

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

Pythonのzipでデータが消える?strict=Trueで行数のずれを検出する

二つの一覧を組み合わせたとき、片方にしか存在しないデータが何も警告されずに消えてしまうことがあります。Pythonのzipは便利ですが、既定では短い側に合わせて処理を終了するためです。これは正常な仕様であり、プログラムが壊れたわけではありません。問題は、作業者が「全件を組み合わせた」と思い込むことです。備品番号と担当部署を対応付けるような仕事では、最後の一件が抜けても気づきにくく、後から照合作業が必要になります。 、架空の二つの一覧を使い、既定動作とstrict=Trueを比較します。実験で確かめるのは、組み合わせた件数だけではなく、どの段階で不整合が見つかるかです。さらに、事前の件数検査と実行中の検査の違い、長さが不明なデータ、無限に続く入力の扱いまで整理します。

二つの一覧を並べたのに、一件だけ見当たらない

備品番号が三つ、担当部署が二つあるとします。担当部署が足りない理由は、入力ミスか、一覧を作る条件の違いかもしれません。どちらにしても、最後の備品番号に対応する部署を勝手に決めることはできません。ところが通常のzipを使うと、組み合わせ可能な二件だけが返り、余った番号は出力されません。エラーが起きないため、正常終了のログだけでは欠落を判断できません。

ここで大切なのは、zipが「二つの一覧の整合性を確認する関数」ではなく、「同じ位置の要素を順番にまとめる関数」だということです。二つのリストを表の列だと考えると、先頭行同士、二行目同士が組になります。片方に三行目がなければ、その行を構成できません。欠けた値を空文字やNoneで埋める動作も既定では行いません。

安全なデータで最初の実験

対象はPython 3.10以降です。strict引数はPython 3.10で追加されました。追加パッケージ、外部通信、ファイルへの書込みは不要です。実際の社員名や資産台帳をコピーせず、以下の架空の文字列だけで確認してください。Pythonの対話環境または新規のスクリプトで、上から順番に実行できます。

numbers = ["PC-01", "PC-02", "PC-03"]
teams = ["営業", "総務"]

pairs = list(zip(numbers, teams))
print("通常:", pairs)
print("組になった件数:", len(pairs))
print("元の件数:", len(numbers), len(teams))

try:
    checked = list(zip(numbers, teams, strict=True))
    print("厳密:", checked)
except ValueError as error:
    print("不一致を検出:", type(error).__name__)

通常のzipでは、二つの組だけが表示されることが期待されます。strict=Trueを付けた側では、入力の長さが異なるためValueErrorが発生する見込みです。ここでの出力は公式仕様に基づく期待結果であり、この掲載コードを実行して得た実測ログではありません。実際に動かす場合は、元の件数と組の件数を必ず見比べてください。

エラー処理をtryとexceptで囲んだ理由は、実験を途中で停止させず、検出できた例外の種類を表示するためです。本番処理で同じように例外を捕まえて何もせず続行すると、不整合を見逃すことになります。実務では、処理を止めて入力一覧を確認するか、例外を上位の処理へ渡して異常終了として記録します。

zipの中で何が起こるか

zipは各入力から次の要素を一つずつ取り出し、組にして返します。先頭から最後まで二つの配列を丸ごと結合してから長さを比べる処理ではありません。入力を順番に消費するイテレータであり、必要になったときに次の組を作ります。そのため、データの取得元がリストではなく、ファイルを一行ずつ読むような処理でも利用できます。

次の図は、長さが違う二つの入力を処理する際の判断を示します。図の中心は、短い側が終わったときに通常モードとstrictモードで何が違うかです。

flowchart TD
    A[二つの入力から次の値を取得] --> B{両方に値があるか}
    B -->|はい| C[一組を返す]
    C --> A
    B -->|いいえ| D{strict指定}
    D -->|False| E[反復を終了]
    D -->|True| F{全入力が同時に終了したか}
    F -->|はい| E
    F -->|いいえ| G[ValueErrorで不一致を通知]

図では一組ずつ処理を進めています。strict=Trueは、長さが一致しない入力を見つけたときに例外で知らせます。ただし「入力の中身が正しい」ことまで保証する機能ではありません。例えば三件ずつあっても、担当部署の並び順が一行ずれていれば、間違った組ができる可能性があります。件数の一致と、対応関係の正しさは別の検査です。

一つだけ条件を変えて再確認する

最初の実験では部署が二つでした。今度は部署の末尾に「開発」を一つ追加して、三つにしてください。numbersは変更しません。この状態なら通常のzipもstrict=Trueも三組を返すことが期待されます。strict=Trueが常に例外を発生させるのではなく、長さが違う場合だけ不整合を知らせることを確認できます。

numbers = ["PC-01", "PC-02", "PC-03"]
teams = ["営業", "総務", "開発"]

for number, team in zip(numbers, teams, strict=True):
    print(number, team)

ここでわざと二番目と三番目の部署を入れ替えても、strict=Trueはエラーにしません。二つの一覧の長さは同じだからです。行の対応が重要なデータでは、順番を信頼してよい条件を先に決める必要があります。共通の備品IDが両方に存在するなら、位置ではなくIDをキーにして辞書やデータベースで結び付ける方が安全な場合があります。

この比較から分かるのは、strict=Trueが「欠落を見つける道具」であって「正しい結合を保証する道具」ではないということです。短い側に合わせて切り捨てる仕様が意図的な場合もあります。例えば二つの無限に続く入力のうち、必要な範囲だけを読む処理では、長さの一致を要求する設計そのものが適切とは限りません。

事前にlenで確認する方法との違い

二つともリストであれば、組み合わせる前にlenで件数を比較できます。件数が違えば処理を始めないという設計は分かりやすく、どちらの入力が何件あるかをログにも残せます。ではstrict=Trueは不要でしょうか。そうとは限りません。zipはリスト以外のイテレータにも使えるため、事前に長さが分からない入力を処理する場面があります。全件を一度読み込むことなく、不一致を検出できる点に意味があります。

ただし、例外が出るタイミングには注意が必要です。zipの作成時点で、すべての入力を検査するわけではありません。イテレータから値を取り出していく途中で長さの違いが判明します。listで全件を取り出す例では処理中に例外が出ますが、forループで途中まで結果を外部へ書き込んでいた場合、先に書いた行は残る可能性があります。strict=Trueを付けたからといって、途中まで行った更新が自動で取り消されるわけではありません。

この性質は、画面への表示だけなら問題にならなくても、CSVの出力やデータベース更新では重要です。全件の整合性を確認してから保存したいなら、一時領域に結果を作り、検証後に確定する構成を検討します。大量データでメモリに載せられない場合は、途中の失敗を想定したトランザクションや再実行手順が必要です。

失敗条件を切り分ける

ValueErrorが出たら、まず各入力の生成条件を確認します。日付範囲、空行の除外条件、重複削除の有無が異なると、片方だけ件数が変わります。単純に不足分を埋める前に、何を一件と数えているかを合わせてください。二つの入力が同じ件数でも、同じ対象を同じ順序で並べているかは別問題です。

Python 3.9以前ではstrict引数を指定できません。その環境で同じコードを使うと、長さの検査以前に引数が受け付けられないため、まずPythonのバージョンを確認します。バージョンを上げられない場合、有限のリストならlenによる事前確認が代替になります。イテレータの場合は、取り出し方と終了条件を明示した別の検証処理が必要です。

意図的に短い側だけを使いたいなら、通常のzipを選ぶ判断もあり得ます。ただし「残りを捨てる」ことを仕様として文書化し、捨てた件数を確認できるようにする方が安全です。逆に長い側の余りを欠損値で埋めたいなら、標準ライブラリitertoolsのzip_longestという別の関数があります。こちらは不足した位置を指定した値で埋めますが、欠損を意味のある値と混同しない工夫が必要です。

業務での使い分け

二つの一覧が同じ順序で同じ件数になることを契約としているなら、strict=Trueを選び、異常時は処理を停止するのが基本です。元データを別システムへ送る処理なら、例外を捕まえた後に成功扱いせず、入力件数と発生箇所を記録します。秘密情報や個人名をそのままログへ出さず、件数と処理IDだけでも調査に役立ちます。

一覧の順番が変わり得るなら、zipではなく共通IDで照合する設計を検討します。片方の入力が短いことを許す業務なら、欠損の扱いを先に決めてzip_longestや別の結合方式を選びます。どれが正解かは、短い入力を異常とするか、正常な欠損として扱うかによって変わります。

最後にもう一度、通常のzipとstrict=Trueの違いを思い出してください。前者は組を作れるところまで進み、後者は長さの不一致を検出します。いずれも行の意味や対応の正しさまでは確認しません。処理が正常終了したという事実だけで、全件が正しく対応したとは言えないことが、今回の実験で押さえたいポイントです。

参考資料

文書情報

記事タイトル
Pythonのzipでデータが消える?strict=Trueで行数のずれを検出する
作成日
更新日
Source URL
https://papanda925.com/?p=18572

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

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