copytreeのignoreをinclude/excludeで指定する(python)
pythonにてディレクトリを再帰的にコピーする場合、shutil.copytree
を使用できます。このとき引数ignore
を使用すると指定したglobパターンにマッチするファイル、ディレクトリを除いてコピーができますが、指定の仕方がちょっと特殊(shutil.ignore_patterns()
にてコールバック関数を生成)だったので、
- 無視したいglobパターンをリストで指定(
ignore_patterns()
は可変長引数を取るため、iterableで渡せない) - 除くファイルではなく含むファイルを指定
みたいなことができないようです。事務作業するときにちょっと困ったので、自分なりにignore_patterns()
に代わる関数を書いてみました。
環境は以下。
- python==3.8.9
結論
以下コード参照
in_patterns
にコピーしたい(=include)、ex_patterns
にコピーしたくない(=exclude)ファイル/ディレクトリのglobパターンのリストを指定(どちらか1つでもよい)CopyTreeIgnore
をインスタンス化 (IgnPtn
)shutil.copytree()
のignore
引数にIgnPtn.ignore()
を渡す
すると、shutil.copytree()
に指定したsrc
のディレクトリを再帰的に探索し、include/excludeの設定を反映してコピーされます。ちなみにcopytree()
は深さ優先探索です。再帰だから当たり前かもしれませんが。
補足説明
ignore関数
ignore指定する場合、公式ドキュメントにはignore_patterns()
を使用してコールバック関数を生成せよと書いてあります。例えば*.txt
, *.jpg
にマッチするファイルを除いてディレクトリをコピーしたいとき、
# 無視するファイル、ディレクトリ名をglobパターンにて指定 # 可変長引数で受けるため、iterableな型は受け付けない ignptn = shutil.ignore_patterns('*.txt', '*.jpg') # src path src = 'path/to/src/directory' # dst path dst = 'path/to/dst/direcotry' # 引数にignptnを指定 shutil.copytree(src, dst, ignore=ignptn)
のように使用します。ignore_patterns()
が生成する関数(ignore関数と呼ぶこととします)は、
- 引数を2つ(str, list[str])とる
- 無視するファイルのリスト(list[str])を返す
関数です。copytree()
は対象ディレクトリを再帰的に探索し、それぞれのディレクトリ内でこのignore関数を呼び出します。ignore関数の戻り値に含まれるファイル・ディレクトリは無視してコピーします。
ignore関数をコードにおこすと、引数・戻り値は以下の通りです。
def ignore(directory: str, files: List[str]) -> List[str]: ''' directory:再帰探索中のディレクトリの絶対パス files:direcotry内のファイル一覧 戻り値:無視するファイルのパスのリスト(相対パス指定する場合はdirectoryを基準とする) '''
ignore_patterns()
によるignore関数は「files
内を探索し、指定のglobパターンとマッチする文字列を戻り値のリストに加える」ということをしています。
つまり、各ディレクトリ内でコピーしたいファイル以外のリストを返すような関数を書いてやれば、任意のファイルを除く/含むようにコピーすることが可能です。あとはそれっぽく実装するだけですね。
CopyTreeIgnore.ignore()
はinclude/excludeを鑑みた結果、無視するファイル一覧をリストとして返しますが、重複がないようにset
による集合演算を使用しています。「include設定によって無視されるファイル群」(_create_include_set()
で生成)と「exclude設定によって無視されるファイル群」(_create_exclude_set()
で生成)を求め、和集合を取っています。別に重複のせいで著しくパフォーマンスが落ちることはないと思いますが、なんとなくすっきりするので。
callbacks引数
CopytreeIgnore()
はcallbacks
という引数を取るようにしています。この引数に関数オブジェクトを与えてやることで、ignore()
とは別にディレクトリを探索する毎に任意の関数を呼び出せるようにしています。まあinclude/excludeでほぼ事足りるのですが、ちょっとトリッキーなことをしたいときに別関数を実装して引数指定してやることで拡張可能にしました。自分の場合、
- 同名の別拡張子ファイルが同ディレクトリに存在している場合、片方だけをコピーする
- 特定の階層以下を無視する(ignore指定では名前が競合する場合などに有用)
- 他ソフトの動作待ちのために適宜sleepさせる
- コピーのログを出力する
みたいなことをしたかったので。。
まとめ
shutil.copytree()
の引数ignore
に渡すコールバック関数を拡張したクラスCopyTreeIgnore
を実装しました。shutil
でも意外と痒いところに手が届かないことがあるんですね。そもそもshutil
はファイル操作の為の関数群なので多くを求めすぎかもしれませんが…