Pythonの拡張子判定で.tar.gzを見落とす?pathlibのsuffixとsuffixesを比較

Python

この記事について
この記事は、生成AIを活用した自動生成フローで作成しています。Python公式ドキュメントのpathlib仕様を参考に、複合拡張子の判定方法と安全な確認例を整理しました。

検証ステータス:📘 公式仕様を確認・掲載コードの実行は未確認
以下のファイル名は架空のものです。実ファイルの展開や削除は行いません。

Pythonの拡張子判定で.tar.gzを見落とす?pathlibのsuffixとsuffixesを比較

バックアップファイルを整理するとき、report.tar.gzを「gzip形式の圧縮済みtar」として扱いたいのに、Path.suffixでは.gzしか返ってこないことがあります。コードが壊れているわけではありません。Pythonが「最後の拡張子」と「連続する拡張子」を区別しているためです。

拡張子判定は簡単に見えますが、業務の自動処理では意外に事故の原因になります。例えば、.tar.gzだけを許可したつもりで.gzを一律に許可すると、圧縮されているだけの別ファイルまで処理対象に入るかもしれません。逆に、suffix == ".tar.gz"という条件は一致せず、正しいバックアップを取りこぼします。 ファイルを作成・展開せず、架空の名前を使ってPython標準ライブラリのpathlibを観察します。最後の拡張子、すべての拡張子、ファイル名の末尾という三つの見方を比較し、実務でどの方法を採用するか判断できるようにします。

対象環境と安全な実験

項目条件
言語Python 3.11以降を想定
使用ライブラリ標準ライブラリ pathlib
入力架空のファイル名のみ
ファイル操作なし。読み込み・展開・削除をしない
実行環境Pythonが使えるローカル端末
未確認Windows/macOS/Linux実機での掲載コード実行

Pathはパスを表すオブジェクトです。今回の例では文字列からPathを作るだけなので、指定した名前のファイルが実在しなくても構いません。バックアップの本物の保存場所を入力する必要もありません。

最小コード:末尾がどう分かれるか

次のコードをcheck_suffix.pyとして保存し、Python 3で実行します。結果はファイル名から求めるもので、実際のファイルは開きません。

from pathlib import Path

names = [
    "report.tar.gz",
    "report.zip",
    "report.csv.gz",
    "report",
    "archive.TAR.GZ",
]

for name in names:
    path = Path(name)
    print(name, "=>", repr(path.suffix), path.suffixes)

期待する観察点は、report.tar.gzのsuffixが.gzであり、suffixesが['.tar', '.gz']になることです。report.csv.gzも最後の拡張子だけなら同じ.gzですが、前の要素は.csvになります。reportには拡張子がありません。大文字のarchive.TAR.GZは、その大文字小文字が保持されます。

ここで、suffixesが返すのは「拡張子らしい部分の配列」であり、圧縮形式を解析した結果ではない点に注意してください。実体がgzipなのか、tarの中身が正しいのかは、名前からは判断できません。まずは名前のルールだけを扱う検査です。

なぜsuffixは最後だけなのか

suffixはパスの最終要素に付く最後の拡張子を表します。一方、suffixesは複数の拡張子をリストとして返します。したがってreport.tar.gzを一つの拡張子として返すのではなく、.tarと.gzを別々の要素として扱います。

この区別を知っておくと、コードの誤りを発見しやすくなります。例えばPath(name).suffix == ".tar.gz"は、通常のreport.tar.gzには一致しません。.gzだけを比較している場合は、report.csv.gzも同じグループに入ります。どちらも目的によっては正しい判定ですが、「tarをgzip圧縮したファイルだけ」という要件には合いません。

次の図は、同じ名前を二つのAPIがどう解釈するかを示しています。左側が最後の拡張子、右側が拡張子の並びです。図の目的は処理内部の再現ではなく、返り値の形の違いを一目で確認することです。

flowchart TD
    A["report.tar.gz"] --> B["Path.suffix"]
    A --> C["Path.suffixes"]
    B --> D[".gz"]
    C --> E[".tar と .gz のリスト"]
    D --> F["gzip拡張子の候補"]
    E --> G["複合拡張子を判定可能"]

1条件変更:tar.gzだけを許可する

次は判定条件を変えてみます。拡張子を一つだけ比較する代わりに、末尾の二要素を調べます。大文字の名前も受け付けたいので、比較前に小文字へ変換します。

from pathlib import Path

def is_tar_gz(name: str) -> bool:
    suffixes = [part.lower() for part in Path(name).suffixes]
    return suffixes[-2:] == [".tar", ".gz"]

for name in ["report.tar.gz", "report.csv.gz", "archive.TAR.GZ", "report.gz"]:
    print(name, is_tar_gz(name))

期待結果はreport.tar.gzとarchive.TAR.GZがTrue、残りがFalseです。ここでsuffixes[-2:]という書き方は、末尾の最大二要素を取り出します。拡張子が一つしかない場合でも例外にはならず、要素数が足りないリストとの比較はFalseになります。

一方、name.lower().endswith(".tar.gz")でも単純な末尾判定はできます。短く書けますが、ファイル名全体に対する文字列操作であり、pathlibの拡張子の配列を使う方法とは意図が異なります。複数の形式を扱う処理なら、拡張子を要素として比較するほうが条件を説明しやすいことがあります。

もう一つの落とし穴:ファイル名は中身の証明ではない

backup.tar.gzという名前が付いていても、中身が本当にgzip形式とは限りません。拡張子を偽装したファイル、ダウンロード途中で壊れたファイル、別形式の内容が混ざったファイルは、名前の比較だけでは見つけられません。これはセキュリティ上も重要な境界です。

業務でアーカイブを受け取るなら、名前による一次選別と内容の検証を分けてください。名前の検査は「処理候補を絞る」役割です。実際に展開する前には、ファイルサイズ、形式、取得元、保存先、展開後のパスを検証する必要があります。特に未知のアーカイブを安易に展開しないことが大切です。

また、Path.suffixesの動作にはPythonのバージョン差があり得ます。例えば末尾が単独のドットの名前など、境界条件は使用するPythonの公式ドキュメントに合わせて確認してください。今回の主題は.tar.gzのように通常の文字を伴う複合拡張子です。

失敗した場合の切り分け

想定した名前がFalseになったら、まずrepr(name)を表示して、末尾に空白や余分な文字がないか調べます。次にPath(name).name、suffix、suffixesを順に出力します。何を入力し、どこで期待と違ったかを分けて観察できます。

大文字と小文字の違いだけなら、比較する拡張子を小文字にそろえます。ただし、文字列全体を勝手に変更して保存する必要はありません。元のファイル名は保持したまま、判定用の値だけを正規化すれば十分です。

名前にドットが複数あっても、それがすべて意味のある形式とは限りません。report.final.tar.gzならsuffixesには.finalも含まれますが、末尾二要素だけを比較すれば.tar.gzの候補として扱えます。逆にreport.tar.gz.oldは最後が.oldなので、今回の判定では対象外です。この差を利用して、一時退避ファイルの誤処理を防ぐこともできます。

業務でどう使い分けるか

単一の.csvや.zipだけを選別したいならsuffixが簡潔です。複合拡張子を区別したいならsuffixesの末尾を比較します。ファイル名の独自ルールが明確で、拡張子の解釈が不要ならendswithも選択肢になります。どれが常に優れているという話ではなく、受入条件に合う方法を選ぶことが大切です。

実際のバッチでは、対象外のファイルを黙って削除するのではなく、スキップ理由をログに残すと運用しやすくなります。「拡張子が不一致」「内容検証前」「ファイル名に余分な接尾辞あり」などを区別すれば、入力側の誤りか処理側の条件ミスかを後から追跡できます。

さらに、拡張子を通過したファイルも、内容を検査するまでは信頼済みにしない設計が必要です。ファイル名の検査とアーカイブの安全な処理を別工程にすると、仕様変更や障害調査の際にも責任範囲が明確になります。

まとめ

suffixは最後の拡張子、suffixesは拡張子の並びを返します。.tar.gzを扱うなら、何を許可するかを先に決め、必要な要素数と順序を比較してください。そして拡張子の一致をファイルの安全性や内容の正しさと混同しないことが、業務での重要なポイントです。

公式資料

文書情報

記事タイトル
Pythonの拡張子判定で.tar.gzを見落とす?pathlibのsuffixとsuffixesを比較
作成日
更新日
Source URL
https://papanda925.com/?p=18609

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

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