ジェネレータをうまく作れるようになりたい
今晩の目的、「同名のファイルが存在しても、重ならないファイル名でコピーをする関数」の作成。仮名 copy_safe 。a\spam.txt, b\spam.txt がすでにあるときに、copy_safe(r'a\spam.txt', r'b\spam.txt') すると b\spam(0).txt という名前でコピーするというもの。
試作したときは一つの関数だったけれど、「別名を作る」と「コピーする」は別の機能だからと考え直したら分けることに成功。すっきり。ファイル名を変えつつ生成し続けるジェネレータがあればいいことに、気づくのが遅すぎた。頭固いオレ。
itertools.count で連番生成
今後は悩まないためにメモ。 C:\a\spam.txt を与えると C:\a\spam.txt, C:\a\spam(0).txt, C:\a\spam(1).txt … という文字列を生成するジェネレータ。i=0, while True: i+=1 … でも連番は作れるけれど、こういうときのために itertools.count がある。 range や xrange との違いは長さを事前に決めなくてよいところ。
import itertools def path_generator(path, limit=1000): yield path name, ext = os.path.splitext(path) for i in itertools.count(): if i >= limit: break yield u''.join((name, '(%d)' % i, ext))
止まらない場合の警戒はこのジェネレータ自身がおこなうようにしてある(デフォルトでは数は999まで増える、出力総数は1001個)。でも無限に生成するようにしておいて、呼び出し側で止めるほうがいいのかも?
既存ファイルを上書きしないコピー、ムーブ(Windows編)
ファイル名生成はできたのでファイル操作のほうに着手。まずは、 Windows 。同名のファイルが存在しても名前を変えつつコピーする関数は CopyFileW の第3引数に True を渡しておくと既存ファイルを上書きしないのを利用して作る。同様にムーブする関数も MoveFileExW の第3引数に MOVEFILE_COPY_ALLOWED を与えるとやはり既存ファイルを上書きしないのを利用して作る。
他の OS だとどうすべきなのかという課題は明日以降に。何も見つからなければ「os.path.isfile で検査して shutil.copy2, os.rename で操作」といった感じで逃げる。アトミックな操作になってないから、運が悪いとファイルが消失するな…。 POSIX だとファイルの置換がアトミックにできたはずだから、一時ファイルを作る方法と同じでいいのかな? O_EXCL で open しておくとか link 作っておくとか。
あと、失敗時の例外が IOError なのは変な気がする。外部要因によるエラーそのものではなく、処理を1001回試みたけれどダメだったのであきらめたというエラーだから。これも明日以降。
import os if os.name == 'nt': import win32file import pywintypes def copy_safe(old, new): for new_path in path_generator(new): try: win32file.CopyFileW( old, new_path, True) break except pywintypes.error, e: pass else: raise IOError() def move_safe(old, new): for new_path in path_generator(new): try: win32file.MoveFileExW( old, new_path, win32file.MOVEFILE_COPY_ALLOWED) break except pywintypes.error, e: pass else: raise IOError()
ちなみに、 for 文にも else 節がつけられる。 else 節は break したときには実行されない。