新しいファイルの作成と書き込みだけでなく、ハードドライブ上の既存のファイルの整理も、プログラムで行えます。数十、数百、ひょっとしたら数千ファイルが入っているフォルダで、コピー、リネーム(名前変更)、移動、圧縮を手動で行ったことがあるでしょう。あるいは、以下のような作業を想定してください。
こうした退屈な作業はPythonで自動化するのにうってつけです。プログラミングにより自分のコンピュータを絶対に間違えない事務員に仕立て上げられます。
Windowsではファイルパスのフォルダの区切りにバックスラッシュ(\)を使いますが、本章のコードではすべてのOSで機能するようにスラッシュ(/)を使います。
shutilモジュールには、ファイルをコピー、移動、リネーム、削除する関数があります(このモジュールの名前はshell utilitiesを縮めたものです。shellはターミナルコマンドラインの別の呼び方です)。shutil関数を使うには、最初にimport shutilを実行します。
対話型シェルで次のコードを実行して、本章で例として使用するファイルとフォルダを作成してください。
>>> from pathlib import Path
>>> h = Path.home()
>>> (h / 'spam').mkdir(exist_ok=True)
>>> with open(h / 'spam/file1.txt', 'w', encoding='utf-8') as file:
... file.write('Hello')
...
file1.txtという名前のテキストファイルが入っているspamフォルダを作成しています。本章では、このファイルとフォルダを例にして、コピー、移動、リネーム、削除を行います。shutil関数は、文字列とPathオブジェクトのどちらでも引数に取れます。
shutilモジュールには、ファイルをコピーする関数とフォルダ全体をコピーする関数があります。shutil.copy(source, destination)を呼び出すと、sourceで指定されたファイルをdestinationで指定されたフォルダにコピーします。sourceとdestinationの両方とも、文字列またはPathオブジェクトです。destinationがファイル名なら、コピーされたファイルの新しい名前になります。destinationがフォルダなら、そのフォルダ内に元の名前のままでコピーされます。この関数は、コピーしたファイルのパスを返します。
対話型シェルに次の内容を入力してshutil.copy()の動作を確認してみましょう。
>>> import shutil
>>> from pathlib import Path
>>> h = Path.home()
❶ >>> shutil.copy(h / 'spam/file1.txt', h)
'C:\\Users\\Al\\file1.txt'
❷ >>> shutil.copy(h / 'spam/file1.txt', h / 'spam/file2.txt')
WindowsPath('C:/Users/Al/spam/file2.txt')
最初のshutil.copy()呼び出しでは、C:\Users\Al\spam\file1.txtのファイルをホームフォルダC:\Users\Alにコピーしています。返り値は新しくコピーしたファイルのパスです。移動先にフォルダを指定したので(❶)、新しくコピーしたファイルは元のfile1.txtというファイルのファイル名と同じ名前になります。2回目のshutil.copy()呼び出しでは、C:\Users\Al\spam\file1.txtのファイルをfile2.txtという名前でC:\Users\Al\spamフォルダにコピーしています(❷)。
shutil.copy()は一つのファイルをコピーしますが、shutil.copytree(source, destination)を呼び出すとsourceのフォルダをその中に入っているファイルとサブフォルダを全部含めてdestinationのフォルダにコピーします。この関数は、コピーしたフォルダのパスを返します。
以下の式を対話型シェルに入力してみてください。
>>> import shutil
>>> from pathlib import Path
>>> h = Path.home()
>>> shutil.copytree(h / 'spam', h / 'spam_backup')
WindowsPath('C:/Users/Al/spam_backup')
shutil.copytree()を呼び出して、元のspamフォルダと同じ内容のspam_backupという名前の新しいフォルダを作成しています。大切なspamを安全にバックアップできました。
shutil.move(source, destination)を呼び出すと、sourceで指定したファイルやフォルダをdestinationで指定したパスに移動できます。移動先の絶対パスの文字列が返されます。
destinationがフォルダを指していたら、sourceファイルがdestinationに移動され、ファイル名はそのままになります。対話型シェルで次のように入力してみてください。
>>> import shutil
>>> from pathlib import Path
>>> h = Path.home()
>>> (h / 'spam2').mkdir()
>>> shutil.move(h / 'spam/file1.txt', h / 'spam2')
'C:\\Users\\Al\\spam2\\file1.txt'
ホームフォルダにspam2フォルダを作成してから、shutil.move()を呼び出して、C:\Users\Al\spam\file1.txtをC:\Users\Al\spam2に移動させています。C:\Users\Al\spam2内にすでにfile1.txtファイルが存在していたとしたら、上書きします。
destinationが既存のフォルダでなければ、shutil.move()がその名前にリネームします。以下の例では、sourceファイルが移動されてリネームされます。
>>> shutil.move(h / 'spam/file1.txt', h / 'spam2/new_name.txt')
'C:\\Users\\Al\\spam2\\new_name.txt'
この行は、「C:\Users\Al\spam\file1.txtをC:\Users\Al\spam2フォルダに移動して、file1.txtというファイル名をnew_name.txtにリネームする」と読めます。
一つのファイルまたは空フォルダをosモジュールの関数で削除できます。フォルダとその中身を一括して削除するなら、shutilモジュールを使います。
プログラムでこれらの関数を使うときには気をつけてください。最初はこれらの関数をコメントアウトしてprint()を呼び出し削除予定のファイルを表示することをおすすめします。これはdry runと呼ばれるやり方です。以下は拡張子.txtのファイルを削除することを意図して書いたプログラムですが、タイプミスがあり(太字の部分です)、.rxtファイルが削除されてしまいます。
import os
from pathlib import Path
for filename in Path.home().glob('*.rxt'):
os.unlink(filename)
ファイル名が.rxtで終わる重要なファイルがあったとしたら、そのファイルが完全に削除されてしまいます。いきなり削除を実行するのではなく、次のように実行してください。
import os
from pathlib import Path
for filename in Path.home().glob('*.rxt'):
#os.unlink(filename)
print('Deleting', filename)
os.unlink()の呼び出しはコメントアウトされていますから無視されます。その代わりに、削除予定のファイル名を表示しています。まずこのようなプログラムを実行するようにすれば、.txtファイルではなく.rxtファイルを削除しようとしていたことに気づけます。
ファイルのコピー、リネーム、移動でもdry runを実行したほうがよいでしょう。さらに、プログラムで操作するファイルのバックアップのコピーを作成しておくのも望ましいです。そうすれば万一の際にファイルを復元できます。プログラムが意図通りに動くことが確信できたら、print(filename)の行を削除してos.unlink(filename)の行のコメントアウトを外してください。それからプログラムを実行すれば、意図したファイルを削除します。
Python本体に付属しているshutil.rmtree()関数はファイルとフォルダを完全に削除します。バグにより意図しないファイルを削除してしまうかもしれないので、この関数は危険です。サードパーティのsend2trashモジュールを使ってファイルとフォルダを削除するほうが安全です(サードパーティのパッケージをインストールする方法は付録Aをご参照ください)。
send2trashモジュールのsend2trash()関数は、フォルダやファイルを完全に削除するのではなく、コンピュータのごみ箱に移動させるので、Python標準の削除関数よりもずっと安全です。プログラムのバグによりsend2trashで意図しないファイルやフォルダを削除してしまったとしても、あとでごみ箱から復元できます。
send2trashをインストールしたら、対話型シェルで次のコードを実行して、file1.txtをごみ箱に移動させてください。
>>> import send2trash
>>> send2trash.send2trash('file1.txt')
一般的に、ファイルやフォルダを削除するには、send2trash.send2trash()関数を使ったほうがよいでしょう。しかし、ファイルをごみ箱に移動させると、あとで復元できますが、ディスク容量は解放されません。send2trash()はファイルをごみ箱に移動させられるだけで、ごみ箱からファイルを取り出すことはできません。
あるフォルダ以下のファイルとサブフォルダの一覧を取得したければ、os.listdir()関数にフォルダ名を渡します。
>>> import os
>>> os.listdir(r'C:\Users\Al')
['.anaconda', '.android', '.cache', '.dotnet', '.eclipse', '.gitconfig',
--snip--
'__pycache__']
iterdir()メソッドを呼び出すとフォルダ内のPathオブジェクトの一覧を取得できます。
>>> from pathlib import Path
>>> home = Path.home()
>>> list(home.iterdir())
[WindowsPath('C:/Users/Al/.anaconda'), WindowsPath('C:/Users/Al/.android'),
WindowsPath('C:/Users/Al/.cache'),
--snip--
WindowsPath('C:/Users/Al/__pycache__')]
あるフォルダ以下のすべてのファイルをサブフォルダ内のファイルも含めてリネームしたいとします。ディレクトリツリーを走査して、それぞれのファイルにアクセスしたいということです。このプログラムを書くのはややこしいですが、Pythonにはこの状況に対応できるos.walk()関数があります。
対話型シェルで以下のコードを実行して、一連のフォルダとファイルを作成しましょう。
>>> from pathlib import Path
>>> h = Path.home()
>>> (h / 'spam').mkdir(exist_ok=True)
>>> (h / 'spam/eggs').mkdir(exist_ok=True)
>>> (h / 'spam/eggs2').mkdir(exist_ok=True)
>>> (h / 'spam/eggs/bacon').mkdir(exist_ok=True)
>>> for f in ['spam/file1.txt', 'spam/eggs/file2.txt', 'spam/eggs/file3.txt',
'spam/eggs/bacon/file4.txt']:
... with open(h / f, 'w', encoding='utf-8') as file:
... file.write('Hello')
...
>>> # この時点でフォルダとファイルが存在
このコードは、ホームフォルダに以下のフォルダとファイルを作成します。
以下のプログラムを実行すると、このフォルダツリーでos.walk()関数を使い、各ファイルの名前を大文字にします。
import os, shutil
from pathlib import Path
h = Path.home()
for folder_name, subfolders, filenames in os.walk(h / 'spam'):
print('The current folder is ' + folder_name)
for subfolder in subfolders:
print('SUBFOLDER OF ' + folder_name + ': ' + subfolder)
for filename in filenames:
print('FILE INSIDE ' + folder_name + ': '+ filename)
# ファイル名を大文字にリネーム
p = Path(folder_name)
shutil.move(p / filename, p / filename.upper())
print('')
os.walk()関数には、フォルダのパスを示す一つの文字列値を渡します。os.walk()をforループで使い、ディレクトリツリーを走査します。range()関数で一連の数値を走査するのと似ています。range()とは異なり、os.walk()関数はループの反復ごとに3つの値を返します。
ここで現在のフォルダはforループの現在の反復でアクセスされているフォルダを指します。os.walk()関数はプログラムの現在の作業ディレクトリを変更しません。for i in range(10):の変数名iを自由に変えられるのと同じように、先に示した3つの変数の名前を自由に変えられます。私はいつもfolder_name、subfolders、filenamesというわかりやすい名前を使うようにしています。
このプログラムを私のコンピュータで実行すると、以下の出力になりました。
The current folder is C:\Users\Al\spam
SUBFOLDER OF C:\Users\Al\spam: eggs
SUBFOLDER OF C:\Users\Al\spam: eggs2
FILE INSIDE C:\Users\Al\spam: file1.txt
The current folder is C:\Users\Al\spam\eggs
SUBFOLDER OF C:\Users\Al\spam\eggs: bacon
FILE INSIDE C:\Users\Al\spam\eggs: file2.txt
FILE INSIDE C:\Users\Al\spam\eggs: file3.txt
The current folder is C:\Users\Al\spam\eggs\bacon
FILE INSIDE C:\Users\Al\spam\eggs\bacon: file4.txt
The current folder is C:\Users\Al\spam\eggs2
os.walk()は変数subfolderとfilenameで文字列のリストを返しますから、その返り値をforループで使えます。この例でしているように、shutil.move()のような関数にフォルダやファイル名を渡せます。
(拡張子.zipの)ZIPファイルはご存知でしょう。たくさんのファイルを圧縮して一つのファイルにできます。ファイルを圧縮するとサイズが減りますから、インターネットで転送する際に便利です。また、複数のファイルとサブフォルダを一つのZIPファイルにできますから、一つのパッケージにまとめるという面でも便利です。アーカイブファイルと呼ばれることもあります。メールにも添付できます。
zipfileモジュールの関数を使うとZIPファイルの作成と展開ができます。
圧縮ZIPファイルを作成するには、第二引数に'w'を渡してZipFileオブジェクトを書き込みモードで開きます(zipfileモジュールの名前はすべて小文字ですが、ZipFileオブジェクトの名前はZ とFが大文字であることに注意してください)。open()関数に'w'を渡して書き込みモードでテキストファイルを開くのと似ています。ファイル名には、文字列とPathオブジェクトのどちらでも渡せます。
ZipFileオブジェクトのwrite()メソッドにパスを渡すと、そのパスのファイルを圧縮してZIPファイルに追加します。write()メソッドの第一引数は追加するファイル名の文字列です。第二引数は、ファイルを圧縮する際に用いるアルゴリズムを指定する、圧縮タイプのパラメータです。あらゆるデータでうまく機能するdeflate圧縮アルゴリズムを指定するなら、この値を常にzipfile.ZIP_DEFLATEDとします。この第二引数の値を渡さなければ、write()メソッドはZIPファイルに指定したファイルを無圧縮で追加します。以下の式を対話型シェルに入力してみてください。
>>> import zipfile
>>> with open('file1.txt', 'w', encoding='utf-8') as file_obj:
... file_obj.write('Hello' * 10000)
...
>>> with zipfile.ZipFile('example.zip', 'w') as example_zip:
... example_zip.write('file1.txt', compress_type=zipfile.ZIP_DEFLATED,
compresslevel=9)
このコードは、file1.txtという名前のテキストファイルを作成し、'Hello' * 10000で50,000文字を書き込んでいます(約49KBになります)。次に、file1.txtの内容を圧縮したexample.zipという名前の新しいZIPファルを作成しています(約213バイト、繰り返しの多いデータは圧縮率が高くなります)。Python3.7以降で利用できるようになったcompresslevelキーワード引数(名前付きパラメータ)には、0から9までの値を設定できます。9が最も遅いけれども最も圧縮率が高いです。このキーワード引数を指定しなければ、デフォルトの6になります。
with文のzipfile.ZipFile()関数でZIPファイルを開いています。open()関数でファイルを開くのと似ています。こうすれば、プログラム実行がwith文のブロックを抜けるときにclose()関数が自動的に呼び出されます。
ファイルへの書き込みと同じように、書き込みモードでは既存のZIPファイルの内容が上書きされて消えてしまいます。既存のZIPファイルにファイルを追加したければ、ZIPファイルを開くときにzipfile.ZipFile()の第二引数に'a'を渡して追記モードにします。
ZIPファイルの内容を読み取るには、ZIPファイルの名前を渡してzipfile.ZipFile()関数を呼び出してZipFileオブジェクトを作成します。zipfileはモジュール名で、ZipFile()は関数名です。
対話型シェルで次のように入力してみてください。
>>> import zipfile
>>> example_zip = zipfile.ZipFile('example.zip')
>>> example_zip.namelist()
['file1.txt']
>>> file1_info = example_zip.getinfo('file1.txt')
>>> file1_info.file_size
50000
>>> file1_info.compress_size
97
❶ >>> f'Compressed file is {round(file1_info.file_size / file1_info
.compress_size, 2)}x smaller!'
'Compressed file is 515.46x smaller!'
>>> example_zip.close()
ZipFileオブジェクトには、そのZIPファイルに含まれているすべてのファイルとフォルダの名前の文字列のリストを返すnamelist()メソッドがあります。これらの文字列をgetinfo() ZipFileメソッドに渡すと、そのファイルについてのZipInfoオブジェクトが得られます。ZipInfoオブジェクトには、元のファイルサイズを保持しているfile_sizeや圧縮したファイルサイズを保持しているcompress_sizeなどの固有の属性があります。ファイルサイズはバイト単位の整数値です。ZipFileオブジェクトはアーカイブファイル全体を表すのに対し、ZipInfoオブジェクトはアーカイブ内の個々のファイルについての情報を保持します。
❶のコマンドでどれくらい効率的にexample.zipが圧縮されているか、元のファイルサイズを圧縮されたファイルサイズで割って計算し、その結果を表示しています。
ZipFileオブジェクトのextractall()メソッドはZIPファイルからすべてのファイルとフォルダを抽出し、現在の作業ディレクトリに展開します。「ZIPファイルの作成と追加」の指示に従ってexample.zipという名前のZIPファイルを作成してから、対話型シェルで以下のコードを実行してください。
>>> import zipfile
>>> example_zip = zipfile.ZipFile('example.zip')
❶ >>> example_zip.extractall()
>>> example_zip.close()
このコードを実行すると、example.zipの中身が現在の作業ディレクトリに抽出されます。オプションでフォルダ名をextractall()に渡すことができ、そうすると抽出したファイルがそのフォルダに展開されます。extractall()に渡されたフォルダが存在しなければ作成されます。例えば、❶の呼び出しをexample_zip.extractall('C:\\spam)に書き換えると、example.zipから抽出したファイルを新しく作成されたC:\spamフォルダに展開します。
ZipFileオブジェクトのextract()メソッドはZIPファイルから一つのファイルを抽出します。対話型シェルで続けて以下のように入力してください。
>>> example_zip.extract('file1.txt')
'C:\\Users\\Al\\Desktop\\file1.txt'
>>> example_zip.extract('file1.txt', 'C:\\some\\new\\folders')
'C:\\some\\new\\folders\\file1.txt'
>>> example_zip.close()
extract()に渡した文字列はnamelist()で返されるリストの中に入っている文字列のどれかに一致しなければなりません。オプションでextract()の第二引数に抽出したファイルを展開するフォルダを指定できます。この第二引数のフォルダが存在しなければ作成されます。
プロジェクト5:フォルダをZIPファイルにバックアップする
C:\Users\Al\AlsPythonBookという名前のフォルダに作業中のファイルを保存しているとします。これまでの作業を失いたくないので、フォルダ全体のスナップショットをZIPファイルに保存したいです。そのスナップショットをバージョン管理したいので、ZIPファイルの名前をAlsPythonBook_1.zip、AlsPythonBook_2.zip、AlsPythonBook_3.zipのようにしたいです。手動でこれを行うこともできますが、面倒ですし、ZIPファイルの名前を間違うかもしれません。この退屈な作業をしてくれるプログラムを実行するほうがずっと簡単です。
このプロジェクト用に、新しいファイルエディタウィンドウを開いて、backup_to_zip.pyという名前で保存してください。
このプログラムのコードをbackup_to_zip()という名前の関数に書きます。こうすれば別のPythonプログラムでこの関数をコピーアンドペーストして利用できます。プログラムの最後でこの関数を呼び出してバックアップを実行します。プログラムは次のようになります。
# backup_to_zip.py - フォルダ全体をその中身ごとZIPファイルに
# コピーし、ZIPファイルの名前の数字を増やしていく
import zipfile, os
from pathlib import Path
def backup_to_zip(folder):
# folder全体を中身ごとZIPファイルにバックアップする
folder = Path(folder) # folderは文字列ではなくPathオブジェクトに統一
# 既存のZIPファイルから、
# 作成するZIPファイルのファイル名を決める
❶ number = 1
❷ while True:
zip_filename = Path(folder.parts[-1] + '_' + str(number) + '.zip')
if not zip_filename.exists():
break
number = number + 1
❸ # TODO:ZIPファイルを作成する
# TODO:フォルダ全体を走査して各フォルダ内のファイルを圧縮する
print('Done.')
backup_to_zip(Path.home() / 'spam')
まず、zipfile モジュールとosモジュールをインポートします。 次に、folder引数を一つ取るbackup_to_zip()関数を定義します。この引数はバックアップしたいフォルダの文字列またはPathオブジェクトです。この関数では、作成するZIPファイルのファイル名を決めます。それから、フォルダfolderを走査し、各サブフォルダとファイルを追加して、ZIPファイルを作成します。あとで思い出せるように、これらのステップのTODOコメントを書きます(❸)。
ZIPファイルの名前を決めるという最初の作業にはfolderの絶対パスのベースネームを利用します。バックアップするフォルダがC:\Users\Al\spamだとすると、ZIPファイルの名前はspam_N.zipになります。初回のプログラム実行時にNは1で、次の回は2になります。
spam_1.zipがすでに存在するかどうか、spam_2.zipがすでに存在するかどうかを順に確認することにより、Nを決めます。numberという名前の変数にNを格納し(❶)、ファイルがすでに存在するかを確認する exists()を呼び出しているループ内でNを増やしていきます(❷)。存在しないファイル名を見つけたらループをbreakします。これが新しく作成するZIPファイルの名前になります。
次はZIPファイルを作成しましょう。以下のようなコードになります。
# backup_to_zip.py - フォルダ全体をその中身ごとZIPファイルに
# コピーし、ZIPファイルの名前の数字を増やしていく
--snip--
# ZIPファイルを作成する
print(f'Creating {zip_filename}...')
backup_zip = zipfile.ZipFile(zip_filename, 'w')
# TODO:フォルダ全体を走査して各フォルダ内のファイルを圧縮する
print('Done.')
backup_to_zip(Path.home() / 'spam')
変数zip_filenameに新しいZIPファイルの名前が格納されていますから、zipfile.ZipFile()を呼び出して実際にZIPファイルを作成できます。第二引数に'w'を渡してZIPファイルを書き込みモードで開きます。コードを書き終えたのでTODOコメントを削除します。
対象フォルダ内のすべてのファイルとサブフォルダをリストアップするためにos.walk()関数を呼び出す必要があります。以下のようなコードになります。
# backup_to_zip.py - フォルダ全体をその中身ごとZIPファイルに
# コピーし、ZIPファイルの名前の数字を増やしていく
--snip--
# フォルダ全体を走査して各フォルダ内のファイルを圧縮する
❶ for folder_name, subfolders, filenames in os.walk(folder):
folder_name = Path(folder_name)
print(f'Adding files in folder {folder_name}...')
# このフォルダ内のすべてのファイルをZIPファイルに追加
❷ for filename in filenames:
print(f'Adding file {filename}...')
backup_zip.write(folder_name / filename)
backup_zip.close()
print('Done.')
backup_to_zip(Path.home() / 'spam')
forループでos.walk()を使います(❶)。反復ごとに現在のフォルダ名とサブフォルダとファイル名が返されます。入れ子になっているfor ループではfilenamesリストの各ファイルを処理します(❷)。各ファイルをZIPファイルに追加します。
このプログラムを実行すると、次のような出力になります。
Creating spam_1.zip...
Adding files in spam...
Adding file file1.txt...
Done.
2回目のプログラム実行では、フォルダspamのすべてのファイルをspam_2.zipという名前のZIPファイルにまとめます。
ディレクトリツリーを走査してファイルを圧縮したZIPアーカイブに追加する、別のプログラムを書けます。例えば、以下の内容が考えられます。
コンピュータの熟練者であっても、マウスとキーボードでファイルを手動で操作しているでしょう。現代のファイルエクスプローラーでは少数のファイルを操作するのは簡単です。しかしファイルエクスプローラーでは数時間を要するような作業が必要になることも時にはあるでしょう。
osモジュールとshutilモジュールには、ファイルをコピー、移動、リネーム、削除する関数があります。ファイルを削除する際には、完全に削除するのではなく、ごみ箱に移動するためにsend2trashモジュールを使いたい場合があるかもしれません。さらに、ファイルを操作するプログラムを書くときには、dry runの実施を検討してください。実際にコピー、移動、リネーム、削除するコードをコメントアウトし、その代わりにprint()を呼び出すのです。プログラムを実行すれば、どのファイルが対象になるかを確かめられます。
一つのフォルダ内のファイルだけでなく、すべてのサブフォルダ内のファイルやサブフォルダ内のサブフォルダ内のファイルを操作する必要が生じる場合もあるでしょう。os.walk()関数でフォルダを走査できますから、ファイルの操作に集中できます。
zipfileモジュールを使うとPythonで.zipアーカイブの圧縮と展開ができます。osやshutilと組み合わせて使えば、ハードドライブ上の複数のファイルをzipfileで簡単にパッケージ化できます。このようにしてZIPファイルを作成すれば、ウェブサイトにアップロードしたりメールに添付したりしやすくなります。
1. shutil.copy()とshutil.copytree()の違いは何ですか?
2. ファイルのリネームにはどの関数を使いますか?
3. send2trashモジュールとshutilモジュールの削除にはどのような違いがありますか?
4. Fileオブジェクトのclose()メソッドと同じように、ZipFileオブジェクトにはclose()メソッドがあります。Fileオブジェクトのopen()メソッドに相当するZipFileのメソッドは何ですか?
以下の練習プログラムを書いてください。
フォルダツリーを走査して特定のファイル拡張子を検索し(.pdfや.jpgなど)、見つけたファイルを新しいフォルダへコピーするプログラムを書いてください。
いくつかのファイルやフォルダがハードドライブの容量を圧迫していることは珍しくありません。コンピュータの容量を解放するには、サイズの大きい不必要なファイルを特定すると効果的です。
フォルダツリーを走査して、(例えば100MB以上の)サイズの大きなファイルやフォルダを探し出すプログラムを書いてください。(osモジュールのos.path.getsize()を使えばファイルのサイズを取得できることを思い出してください。)発見したファイルの絶対パスを画面に表示してください。
spam001.txt、spam002.txtのように、指定した名前で始まるファイルをすべて見つけ、飛んでいる番号(spam001.txt とspam003.txtはあるがspam002.txtがないなど)を特定するプログラムを書いてください。飛んでいる番号を詰めるようにリネームしてください。
この例で使うファイルを以下のコードで作成します(spam042.txt、spam086.txt、spam103.txtを飛ばします)。
>>> for i in range(1, 121):
... if i not in (42, 86, 103):
... with open(f'spam{str(i).zfill(3)}.txt', 'w') as file:
... pass
...
さらなる練習として、連番のファイルの途中で番号を繰り上げてあとからその番号に新しいファイルを挿入できるようにする、別のプログラムを書いてください。
アメリカ式の日付(MM-DD-YYYY)のファイル名をヨーロッパ式の日付(DD-MM-YYYY)のファイル名にリネームするよう、上司からメールで指示されたとします。数千ファイルあります。この退屈な作業を手動で行えば一日かかるでしょう。そこで、次のようなプログラムを作成します。
1. 現在の作業ディレクトリ以下のすべてのファイルからアメリカ式の日付のファイル名を探します。サブフォルダを探すのにos.walk()関数を使います。
2. 例えばspam12-31-1900.txtなど、ファイル名のMM-DD-YYYYパターンを発見するのに正規表現を使います。月と日は必ず2桁の数字で、日付として無効なファイル名(99-99-9999.txtなど)はないと想定して構いません。
3. ファイルを見つけたら、ヨーロッパ式になるように月と日を入れ替えてリネームします。リネームにはshutil.move()関数を使います。