os.walkを便利に使える関数strollを書いた

  • リスト指定と正規表現マッチに対応し、ディレクトリパスとファイル名を返すようにして、関数名をnicewalkからstroll*1に変更(短い単語の方が良かった)

  • ディレクトリの扱いを明確化。rootはカレントディレクトリのディレクトリ名で、pathはカレントディレクトリのパス。ignorecaseを追加。

  • Python 3.5以上ではglobにrecursiveオプションが付いたため、"**"とrecursive=Trueを与えることで再帰的に辿ることができるようになった。そのため、以下はPython3.4以下が対象。

os.walk()はディレクトリを再帰的に辿りながら、ファイルを読み込んだり書き込む処理をする際に非常に便利だ。globだとディレクトリを再帰的に辿ることができず、ディレクトリ階層を意識しないといけない*2し、ディレクトリとファイルの違いをワイルドカードで示す必要があるので柔軟さに欠ける。またos.walk()では、取得したディレクトリやファイルのリストに対して、インプレースでの変更が可能になっているため、任意の条件で自由にソートやフィルタを行うことができる。そこで、各ディレクトリ・ファイルに対する条件を簡単に盛り込めるstrollを書いた。

reduceのこの使い方は好きで、リストをループでチェックするよりワンラインで書ける方が見やすくて良い。一度慣れてしまえば、それほど考えこまなくても流用できるので、いろんなところで多用している。リストが空の場合には、reduceに与える初期値がそのまま判定結果になってしまうが、それが嫌ならリストを三項演算で判定して初期値を与えれば良い。

gist.github.com

$ python --version
Python 3.4.3

$ cat main.py 
from stroll import stroll
import os

print()
print("=== ex1 (mtime) ===")
for filepath in stroll("basedir",sortby=os.path.getmtime):
    print( filepath )

print()
print("=== ex2 (mtime/reverse) ===")
for filepath in stroll("basedir",sortby=os.path.getmtime,reverse=True):
    print( filepath )

print()
print("=== ex3 (no_file) ===")
for filepath in stroll("basedir",no_file=["1$","2$"],):
    print( filepath )

print()
print("=== ex4 (yes_path) ===")
for filepath in stroll("basedir",yes_path=["1$"],):
    print( filepath )

print()
print("=== ex5 (no_root/len/reverse) ===")
for filepath in stroll("basedir",no_root=["2$"],sortby=len,reverse=True):
    print( filepath )

print()
print("=== ex6 (yes_file/yes_root) ===")
for filepath in stroll("basedir",yes_file=["2$"],yes_root=["2$"]):
    print( file path )
$ find basedir
basedir
basedir/dir1
basedir/dir1/file11
basedir/dir1/file111
basedir/dir1/file1111
basedir/dir1/file12
basedir/dir1/file13
basedir/dir2
basedir/dir2/file21
basedir/dir2/file22
basedir/dir2/file222
basedir/dir2/file2222
basedir/dir2/file23
basedir/dir3
basedir/dir3/file31
basedir/dir3/file32
basedir/dir3/file33
basedir/dir3/file333
basedir/dir3/file3333
basedir/file0

$ ls -Rlrt --time-style=+"%m/%d_%H:%M:%S" basedir/ | awk '{print $5 " " $6 " " $7}'
  
0 02/09_21:24:50 file0
238 02/09_21:25:29 dir1
238 02/09_21:25:33 dir2
238 02/09_21:25:38 dir3
  
0 02/09_21:24:57 file11
0 02/09_21:24:58 file12
0 02/09_21:24:59 file13
0 02/09_21:25:28 file111
0 02/09_21:25:29 file1111
  
0 02/09_21:25:06 file22
0 02/09_21:25:06 file21
0 02/09_21:25:07 file23
0 02/09_21:25:32 file222
0 02/09_21:25:33 file2222
  
0 02/09_21:25:10 file31
0 02/09_21:25:11 file32
0 02/09_21:25:12 file33
0 02/09_21:25:37 file333
0 02/09_21:25:38 file3333

$ python main.py 

=== ex1 (mtime) ===
basedir
('basedir', 'file0')
basedir/dir1
('basedir/dir1', 'file11')
('basedir/dir1', 'file12')
('basedir/dir1', 'file13')
('basedir/dir1', 'file111')
('basedir/dir1', 'file1111')
basedir/dir2
('basedir/dir2', 'file21')
('basedir/dir2', 'file22')
('basedir/dir2', 'file23')
('basedir/dir2', 'file222')
('basedir/dir2', 'file2222')
basedir/dir3
('basedir/dir3', 'file31')
('basedir/dir3', 'file32')
('basedir/dir3', 'file33')
('basedir/dir3', 'file333')
('basedir/dir3', 'file3333')

=== ex2 (mtime/reverse) ===
basedir
('basedir', 'file0')
basedir/dir3
('basedir/dir3', 'file3333')
('basedir/dir3', 'file333')
('basedir/dir3', 'file33')
('basedir/dir3', 'file32')
('basedir/dir3', 'file31')
basedir/dir2
('basedir/dir2', 'file2222')
('basedir/dir2', 'file222')
('basedir/dir2', 'file23')
('basedir/dir2', 'file21')
('basedir/dir2', 'file22')
basedir/dir1
('basedir/dir1', 'file1111')
('basedir/dir1', 'file111')
('basedir/dir1', 'file13')
('basedir/dir1', 'file12')
('basedir/dir1', 'file11')

=== ex3 (no_file) ===
basedir
('basedir', 'file0')
basedir/dir1
('basedir/dir1', 'file13')
basedir/dir2
('basedir/dir2', 'file23')
basedir/dir3
('basedir/dir3', 'file33')
('basedir/dir3', 'file333')
('basedir/dir3', 'file3333')

=== ex4 (yes_path) ===
basedir/dir1
('basedir/dir1', 'file11')
('basedir/dir1', 'file111')
('basedir/dir1', 'file1111')
('basedir/dir1', 'file12')
('basedir/dir1', 'file13')

=== ex5 (no_root/len/reverse) ===
basedir
('basedir', 'file0')
basedir/dir1
('basedir/dir1', 'file1111')
('basedir/dir1', 'file111')
('basedir/dir1', 'file11')
('basedir/dir1', 'file12')
('basedir/dir1', 'file13')
basedir/dir3
('basedir/dir3', 'file3333')
('basedir/dir3', 'file333')
('basedir/dir3', 'file31')
('basedir/dir3', 'file32')
('basedir/dir3', 'file33')

=== ex6 (yes_file/yes_root) ===
basedir/dir2
('basedir/dir2', 'file22')
('basedir/dir2', 'file222')
('basedir/dir2', 'file2222')

*1:to walk somewhere in a slow relaxed way

*2:rubyのDir.glob()は**/を指定することで再帰的にディレクトリ階層を辿ることができる