読者です 読者をやめる 読者になる 読者になる

StatsBeginner: 初学者の統計学習ノート

初学者が統計学、機械学習、R、Pythonの勉強の過程をメモっていくノート。

作業&勉強メモ: Pythonで指定したディレクトリ配下のディレクトリ名とファイル名を取得する

プログラミング Python

 ちょっとした作業のメモです。わたし初心者ですのでヘンなことをやってる可能性あります。
 
 
 指定したディレクトリ配下のディレクトリ名やファイル名を取得しようと思い、↓のページを参考に作業しました。


qiita.com


 ただ、以下のような変更を行いました。

  • リストが欲しかったのでリストを返すようにした。
  • 指定したディレクトリそのものは要素として必要ないので、出力しないようにした。
  • オプションで、フルパスを返すか、指定したディレクトリからの相対パスだけ返すか選択できるようにした。
  • オプションで、"."で始まるシステムファイルやシステムフォルダを除くかどうか選択できるようにした。


 os.walk()の公式な説明は、
 16.1. os — 雑多なオペレーティングシステムインタフェース — Python 3.3.6 ドキュメント
 に載っている。

 

留意点

 こんなんでいいんだろうかと自分でも思っている留意点をいくつか挙げておきます。
 
 
 まず、(os.walkが返してくる)ジェネレータとかいうものの操作方法がよくわからなかったので、関数定義の中にまた関数定義を入れているのだが、もっと簡単な方法ないのだろうか。
 ジェネレータについて、以下の記事は斜め読みしてフムフムとおもた。
 Python のジェネレータ (1) - 動作を試す | すぐに忘れる脳みそのためのメモ
 関数として定義されるがオブジェクトのように動くみたいなことが書かれているので、関数定義の中に関数定義でもこれは仕方のないことなのかな?


 相対パスだけ取りたい場合ですが、os.walk()のfilenamesだけ取ると指定したディレクトリ配下のディレクトリ構造が無視されてファイル名だけ返る。どうすればいいかよく分からなかったので、とりあえず、
 yield os.path.join()
でフルパスを得た後で、指定ディレクトリまでのパスをテキストとして削除することにした。下の階層で同じパスが出てこないとも限らないので、左端からみていって1回だけ削除することにしている(re.sub()のcount=1)。
 このやり方だと、ディレクトリを指定するときに最後に"/"を書かない場合、返ってくるファイル名等が"/"で始まるようになってしまうので、最後に.lstrip('/')で取り除いている。

 フルパスについてはos.walk()が返す"dirpath"と"filenames"を連結し、指定ディレクトリからの相対パスはos.path.relpath()で取ることにした。


 システムファイルの指定は'/.'が含まれるという条件でいいのかは自信がない。


 わたしMacなんですが、.appとかはファイルではなくディレクトリとして認識されて中身が全部一覧化されます。オプションで一覧化させないようにするための処理はまた後で付け足したほうがいいのかもしれない。

コード(修正あり)

 以下のようなコードを書きました。コメント欄で戴いた指摘を反映して修正しました。
 なお、Macでやってますが、Windowsでやるときはパスの書き方が違うので修正が必要なので後述します。
 あと、私はPython3でやっています。

def listdir_all(path, fullpath=False, sysfile=False):
    import os
    
    # 指定ディレクトリの配下にあるディレクトリやファイルのパスを生成する
    def gene(filedir):
        if fullpath == True:
            for dirpath, dirnames, filenames in os.walk(filedir):
                yield dirpath
                for name in filenames:
                    yield os.path.join(dirpath, name)
        else:
            for dirpath, dirnames, filenames in os.walk(filedir):
                yield os.path.relpath(dirpath, filedir)
                for name in filenames:
                    full = os.path.join(dirpath, name)
                    yield os.path.relpath(full, filedir)
    
    # リストにする
    list = [file for file in gene(path)]
    
    # 1個目は指定ディレクトリ自身であり不要なので消す
    list = list[1:]
    
    # .で始まるシステムファイルを除去する
    if sysfile == False:
        for file in list:
            if '/.' in file:
                list.remove(file)
    
    # フルパス又は指定ディレクトリからの相対パスを返す
    return(list)


 これでインタラクティブシェルで、以下のように操作すると取得できました。

>>> filedir = '/Users/yk/Dropbox/Python/Test/Tree/sample_files'
>>> result = listdir_all(filedir, fullpath=False, sysfile=True)
>>> 
>>> for x in result:
...     print(x)
... 
20151008_戦略メモ.docx
20151008_戦略メモ_rev.pptx
パワポ追記分.ppt
下書き.txt
送付資料.zip
新組織
新組織/.DS_Store
新組織/組織のあり方.docx
新組織/組織のあり方.txt
新組織/組織イメージ図.pptx
新組織/画像素材
新組織/画像素材/IMG_0336_2.jpg
新組織/画像素材/IMG_0452_2.jpg
新組織/画像素材/最終版画像.png
新組織/画像素材/素材2.JPG
新組織/画像素材/素材4.JPG
新組織/画像素材/編集中画像.pxm
>>> 


>>> result = listdir_all(filedir, fullpath=True, sysfile=False)
>>> 
>>> for x in result:
...     print(x)
... 
/Users/yk/Dropbox/Python/Test/Tree/sample_files/20151008_戦略メモ.docx
/Users/yk/Dropbox/Python/Test/Tree/sample_files/20151008_戦略メモ_rev.pptx
/Users/yk/Dropbox/Python/Test/Tree/sample_files/パワポ追記分.ppt
/Users/yk/Dropbox/Python/Test/Tree/sample_files/下書き.txt
/Users/yk/Dropbox/Python/Test/Tree/sample_files/送付資料.zip
/Users/yk/Dropbox/Python/Test/Tree/sample_files/新組織
/Users/yk/Dropbox/Python/Test/Tree/sample_files/新組織/組織のあり方.docx
/Users/yk/Dropbox/Python/Test/Tree/sample_files/新組織/組織のあり方.txt
/Users/yk/Dropbox/Python/Test/Tree/sample_files/新組織/組織イメージ図.pptx
/Users/yk/Dropbox/Python/Test/Tree/sample_files/新組織/画像素材
/Users/yk/Dropbox/Python/Test/Tree/sample_files/新組織/画像素材/IMG_0336_2.jpg
/Users/yk/Dropbox/Python/Test/Tree/sample_files/新組織/画像素材/IMG_0452_2.jpg
/Users/yk/Dropbox/Python/Test/Tree/sample_files/新組織/画像素材/最終版画像.png
/Users/yk/Dropbox/Python/Test/Tree/sample_files/新組織/画像素材/素材2.JPG
/Users/yk/Dropbox/Python/Test/Tree/sample_files/新組織/画像素材/素材4.JPG
/Users/yk/Dropbox/Python/Test/Tree/sample_files/新組織/画像素材/編集中画像.pxm
>>> 

 

追記:Windowsの場合

 Windowsの場合、とにかくパスの問題がめんどくさいです。
 とりあえず、


cocodrips.hateblo.jp


 ↑の記事を参考にして、無理やりな方法で動かしてみました。
 関数定義は次のとおりです。

def listdir_all(filedir, fullpath=False, sysfile=False):
    # パスは/で区切っても\で区切ってもよいが、\の場合は重ねてエスケープするか、
    # raw string(r'hoge')で与えること!

    import os, re
    
    # 一旦パスの分割記号を変換
    filedir2 = filedir.replace('/', os.sep)  # /で入力された場合
    filedir3 = filedir.replace(os.sep, '/')  # 最後に使う
    
    # 指定ディレクトリの配下にあるディレクトリやファイルのフルパスを生成する関数
    def gene(path):
        for dirpath, dirnames, filenames in os.walk(path):
            yield dirpath
            for name in filenames:
                yield os.path.join(dirpath, name)
    
    # リストにする
    list = [file for file in gene(filedir2)]  # ここでは\を使う
    
    # 1個目は指定ディレクトリ自身であり不要なので消す
    list = list[1:]
    
    # \だとあとでパターンマッチが難しいので/に戻す
    list = [file.replace(os.sep, '/') for file in list]

    # .で始まるシステムファイルを除去する
    if sysfile == False:
        for file in list:
            if '/.' in file:
                list.remove(file)
    
    # フルパス又は指定ディレクトリからの相対パスを返す
    if fullpath == True:
         return(list)
    else:
        # ここでは/でマッチする
        list = [re.sub(filedir3, '', file, count=1) for file in list]
        list = [file.lstrip('/') for file in list]
        return(list)


 Windowsのパスは"\"で区切って指定するわけですが、"\"はPythonのコード上では特殊文字として扱われてしまう。
 だから、'hoge\\hoge'というふうに重ねる*1か、r'hoge\hoge'というraw stringの形式で入力する必要がある。
 なお、"/"で区切ってもじつはPythonは認識してくれる。上のコードでは、どっちで入力されても大丈夫。
 os.sepがディレクトリの分割記号を表すことを利用することもできる。
 os.walkとos.pathの組み合わせで生成されるパスは"\\"と重ねてエスケープする方式で返ってきたのだが、上述のように最後に文字列置換するやり方だと、検索でちゃんと当てるのが難しかったので、最後にos.sepを"/"に置き換えている。


 実行してみます。

>>> # これでいける
... filedir = 'D:/sample/folder1'
>>> list = listdir_all(filedir, fullpath=False, sysfile=True)
>>> print(list)
['20151001_○○に関するメモ.txt', '20151015_○○会議議事録.txt', '○○パスワード設定.txt', '○○利用ルール.txt', '20151125_△△打合せ資料', '20151125_△△打合せ資料/20151000_△△イメージ(A3).pptx', '20151125_△△打合せ資料/20151125_△△打合せ.pptx', '20151125_△△打合せ資料/△△方式比較表.xlsx', '20151125_△△打合せ資料/参考資料', '20151125_△△打合せ資料/参考資料/テンプレ.pptx', '20151125_△△打合せ資料/参考資料/名簿.xlsx']
>>> 
>>> # これでもいける
... filedir = r'D:\sample\folder1'
>>> list = listdir_all(filedir, fullpath=False, sysfile=True)
>>> print(list)
['20151001_○○に関するメモ.txt', '20151015_○○会議議事録.txt', '○○パスワード設定.txt', '○○利用ルール.txt', '20151125_△△打合せ資料', '20151125_△△打合せ資料/20151000_△△イメージ(A3).pptx', '20151125_△△打合せ資料/20151125_△△打合せ.pptx', '20151125_△△打合せ資料/△△方式比較表.xlsx', '20151125_△△打合せ資料/参考資料', '20151125_△△打合せ資料/参考資料/テンプレ.pptx', '20151125_△△打合せ資料/参考資料/名簿.xlsx']
>>> 
>>> # これもいける
... filedir = 'D:\\sample\\folder1'
>>> list = listdir_all(filedir, fullpath=False, sysfile=True)
>>> print(list)
['20151001_○○に関するメモ.txt', '20151015_○○会議議事録.txt', '○○パスワード設定.txt', '○○利用ルール.txt', '20151125_△△打合せ資料', '20151125_△△打合せ資料/20151000_△△イメージ(A3).pptx', '20151125_△△打合せ資料/20151125_△△打合せ.pptx', '20151125_△△打合せ資料/△△方式比較表.xlsx', '20151125_△△打合せ資料/参考資料', '20151125_△△打合せ資料/参考資料/テンプレ.pptx', '20151125_△△打合せ資料/参考資料/名簿.xlsx']
>>> 
>>> # これはダメ
... filedir = 'D:\sample\folder1'
>>> list = listdir_all(filedir, fullpath=False, sysfile=True)
>>> print(list)
[]
>>> 


 行けました。

注意:時間がなくて後回しにしていること

  • 上のコードだと、Windowsの場合に、一番左の"/"があったりなかったりで変になってると思うので修正する必要があるが、やってない。
  • コメント欄で凡さんが教えてくれているコードを試してみる必要があるが、やってない。

*1:1個目の\がエスケープを表し、2個目の\が\として認識される。ネットワークドライブなどでもともと\\が入ったパスを表現するときは\\\\と打たなければならない