表題通りの内容です。
Pythonでファイルを移動する場合はこのいずれかを利用すると思いますが、挙動がまるで違うので、それに気を付けないとエラーを頻発することになります。
ケース1:ファイル名まで明示的に記述して移動する
shutil.move
まずshutilとはshとutilをくっつけた造語で、shell utilityのことです。shellとはざっくり言ってCUIなどを使って人為的にコンピュータに命令を送ることであり、つまりmove(移動)に対するユーティリティということです。
それを踏まえ、このshutil.moveとはファイルを移動させるコマンドであり、それ以上でもそれ以下でもありません。したがって、存在しないフォルダは自動的に生成できないのです。
また、すでに存在している同一ディレクトリのファイルのみ自動で上書きされます。
import shutil, os
org_path = os.path.join("tests","test.py")
moved_path = os.path.join("tests2","test.py")
shutil.move(org_path, moved_path)
※tests2フォルダが存在しない場合は、unknown file or directoryとエラーを返す。
os.rename
対するos.renameとはOSそのものを操作するコマンドの一つであり、これはファイルを移動させているのではなく、ファイルの参照パスそのものを書き換えているのです。したがって、先程と同じような動作を実施した場合、仮にtest2フォルダが未作成であっても、自動で作ってくれます。
ただし、パス構造の書き換えというのが厄介なポイントで、移動元のフォルダが空になった場合、そのディレクトリは存在しないものとして空フォルダを自動消去してしまう挙動が発生します(shutil.moveはファイルだけの移動なので空フォルダは残ります)。
また、移動先に同じファイルが存在している場合は書き換え不可(ユニーク制御違反)なので、処理エラーとなります。
import shutil, os
org_path = os.path.join("tests","test.py")
moved_path = os.path.join("tests2","test.py")
os.rename(org_path, moved_path)
※厳密に言えば、testsフォルダをtests2フォルダに書き換えてしまう。
どちらを利用すべきか
一見、os.renameの方が便利に思えます。しかし、DjangoやFlaskなど、システムで段階的に処理する場合、元のファイル遷移元が消滅するという動作はかなりリスクがあるので、この場合はフォルダ生成が面倒でもshutil.moveを利用すべきだと考えています。
※ただし、既存のファイルを自動で上書きしてしまうという挙動は注意が必要です。その場合は、次に挙げる記述を活用できる場合があります。
ケース2:ディレクトリまでしか記述しない場合
もし、ファイル名を明示せずにディレクトリまでしか記述しない場合はshutil.moveとos.renameで大きく挙動が変化します。
shutil.move
ファイルを移動させる場合は、以下のように記述することもできます。ですが、この場合だと、既に存在しているファイルの場合、上書きできずにエラーを返すようになります。
これはshutil.moveが厳密には("移動先のファイルパス","移動元のファイル")となっているためで、移動先をディレクトリまでしか記述しない場合、ファイルの上書き処理を施さないため、制御違反となるからです。
import shutil, os
org_path = os.path.join("tests","test.py")
moved_path = os.path.join("tests2/","")
shutil.move(org_path, moved_path)
また、以下のように記述しても同じ挙動となります。
import shutil, os
org_path = os.path.join("tests","test.py")
moved_path = os.path.join("tests2","")
shutil.move(org_path, moved_path)
os.rename
この場合は大半の場合、#1と挙動は変化しません。ですが、以下のように書いた場合はファイルとディレクトリで異なるために処理エラーとなります。
import shutil, os
org_path = os.path.join("tests","test.py")
moved_path = os.path.join("tests2","")
os.rename(org_path, moved_path)