静かなる名辞

pythonとプログラミングのこと

【python】pythonで悲しい思いをしないためのファイルパス操作の基本

 コマンドライン引数からパスを受け取って処理する、というプログラムはありがちなんだけど、適当に書くと適当なパスを渡したとき悲しい動作になったりするんだ(´・ω・`)

 気をつけないといけないポイントを色々まとめておこう。一応linux環境で、python3.5以降のそれなりに新しいpythonを想定。

コマンドライン引数を処理するときの作法

 コマンドライン引数自体はsys.argvで取れる。これはリストになっていて、0番目はソースコード名、1番目以降は引数の中身になってるんだって。
 
 だからって、「引数は1個だからsys.argv[1]を読めば良いんだな!」とか思って書くと、うっかり引数を渡し忘れたときIndexErrorを投げられて悲しい気持ちになる。

 こんな感じで大丈夫か?

import sys
if len(sys.argv) < 2:
    # 実際には然るべき処理を書く
    sys.exit("引数足りねえよ!")
else:
    file_path = sys.argv[1]

とりあえず絶対パス

 入力は大抵は相対パスなんだけど、相対パスのまま扱うと何かと事故の原因になりがち。なので、受け取ったパスは絶対パスに変換しとく。

import os
path = os.path.abspath(file_path)

 ちなみに、abspathには「相対パスだから『../』とか指定するにゃん!」とか「ディレクトリだから最後に『/』を付けて入力するにゃん!」とかやっても平然とぜんぶ消してくれたりする、素晴らしい機能がある。ファイルパス操作では、テキスト処理的にごりごり書いて用を済ませることも多いけど、フォーマットを綺麗に統一してくれるのはそういうとき地味に嬉しい。

おまえは実在するのか?

 まぁ、問答無用でopen()してopen()からエラーを投げてもらっても別に良いんだけど・・・。悲しくならない?*1

# ファイルの場合
if not os.path.isfile(path):
    sys.exit("そんなファイルねーよ!")

# ディレクトリの場合
if not os.path.isdir(path):
    sys.exit("そんなディレクトリねーよ!")

単体ファイルの拡張子チェック

 ちょっと悩んでから、「split(".")すりゃあええ」と割り切ることが多い。絶対パス変換、存在チェックを先にやるか、これを先にやるかは気分次第なところがあるかも。

# .txtを想定している場合
if path.split(".")[-1] != ".txt":
    sys.exit("拡張子がちげーよ!")

ディレクトリの中にあるファイルを丸ごと取ってきたいとき

 とりあえずディレクトリかどうかの確認までは終わったと仮定し、ディレクトリ内のファイルを丸ごと取ってくる方法を考える。

 無難な方法は、os.listdirを使うことだろうか。

name_list = os.listdir(path)

 ただし、こいつはパスじゃなくて、名前(上位階層を含まない)しか返さない。/home/hoge/hoge.txtがあって、os.listdir("/home/hoge")したら["hoge.txt"]が返るみたいな感じなので、ファイルを開くときは自分で上位階層のパスを足してやらないといけなくて面倒。

for file_name in name_list:
    # こんな感じで処理する
    with open(path + "/" + file_name,  ......

 あと、テキストファイルだろうが、バイナリ実行ファイルだろうが、ディレクトリだろうが、ぜんぶひっくるめて返しやがる。なので、自分で判定処理を書かないといけないのも面倒。

for file_name in name_list:
    # こんな感じで処理する
    if file_name.split(".")[-1] == ".txt":
        with open(path + "/" + file_name, ......
    else:
        pass

 そこで、globという素晴らしい標準ライブラリを使うことができる。返り値が絶対パスのリストなのは使いやすいし、なんとワイルドカード正規表現でパターンマッチもできるという。

import glob
file_name_list = glob.glob(path + "/*.txt")
for file_name in file_name_list:
    # けっこう楽
    with open(file_name, ...

 listdirでやってた苦労はなんだったのだろうか。

もっと色々知りたい

 再帰的にディレクトリを辿って全部のファイルを見たいな~、って方にはこちらのqiitaの記事が参考になると思います。
Pythonでフォルダ内のファイルリストを取得する - Qiita

 あと、pathlibなる高水準の標準ライブラリもあるそうです。
11.1. pathlib — オブジェクト指向のファイルシステムパス — Python 3.6.3 ドキュメント
 使いこなせば強そうですが、個人的にはこの手の、ガッツリオブジェクト指向で抽象化しました的なライブラリは苦手です。覚えるルールが多くてしんどいんです。そういうの好きな人はこれ使えば、たぶんこの記事に書いたことぜんぶできると思います。

*1:でも自分で使うだけのプログラムだと、ヘタにオレオレなエラー処理するより、処理系の吐くエラーメッセージ読んだ方がわかりやすかったりして・・・