ファイルとディレクトリのパスの処理

実用的なファイルシステム操作の詳細については、ブログ記事をご覧ください:すべての開発者が知っておくべき 10 の必須ファイルシステム操作

Python でパス操作を扱う主要なモジュールは 2 つあります。 1 つは os.path モジュール、もう 1 つは pathlib モジュールです。

Pathlib と OS モジュール

pathlibは、ファイル名の取得、ファイル拡張子の取得、ファイルの読み書き(手動で開くことなく)など、上記にリストされているものよりもはるかに多くの機能を提供します。さらに詳しく知りたい場合は、公式ドキュメントを参照してください。

Linux と Windows のパス

Windows では、パスはフォルダ名の区切り文字としてバックスラッシュ (\) を使用して記述されます。macOS、Linux、BSD などの Unix ベースのオペレーティングシステムでは、パスの区切り文字としてフォワードスラッシュ (/) が使用されます。コードが異なるプラットフォームで動作する必要がある場合、パスの結合は頭痛の種になることがあります。

幸いなことに、Python の pathlib モジュールはこれを処理する簡単な方法を提供します。

*nix での pathlib の使用:

# pathlib.Path: クロスプラットフォームのパス処理
from pathlib import Path

print(Path('usr').joinpath('bin').joinpath('spam'))  # パスコンポーネントを結合
usr/bin/spam

pathlib は、/ 演算子を使用して joinpath のショートカットも提供します:

# Path 演算子 (/): パスを結合する便利な方法 (クロスプラットフォーム)
from pathlib import Path

print(Path('usr') / 'bin' / 'spam')  # joinpath() の代わりに / 演算子を使用
usr/bin/spam

パス区切り文字が Windows と Unix ベースのオペレーティングシステムで異なることに注意してください。これが、パスを結合するために文字列を結合する代わりに pathlib を使用したい理由です。

クイズ

ログインしてこのクイズに回答し、学習の進捗を追跡できます

Python で pathlib を使用してパスを結合する正しい方法はどれですか?
A. Path('usr') + 'bin' + 'spam'
B. Path('usr') / 'bin' / 'spam'
C. Path('usr').join('bin').join('spam')
D. Path('usr/bin/spam')

同じディレクトリの下に異なるファイルパスを作成する必要がある場合、パスの結合は役立ちます。

*nix での pathlib の使用:

# Path.home(): ユーザーのホームディレクトリを取得し、ファイル名と結合
my_files = ['accounts.txt', 'details.csv', 'invite.docx']
home = Path.home()  # ホームディレクトリのパスを取得
for filename in my_files:
    print(home / filename)  # ホームパスと各ファイル名を結合
/home/labex/project/accounts.txt
/home/labex/project/details.csv
/home/labex/project/invite.docx

ユーザーホームディレクトリの展開

os.path.expanduser() を使用して ~ をユーザーのホームディレクトリに展開します:

import os.path

# ~ をユーザーのホームディレクトリに展開
print(os.path.expanduser('~'))
/home/labex/project
# ~/Documents を完全なパスに展開
print(os.path.expanduser('~/Documents'))
/home/labex/project/Documents
# ~ を含むパスでも機能する
print(os.path.expanduser('~/myfile.txt'))
/home/labex/project/myfile.txt

現在の作業ディレクトリ

pathlib を使用して現在の作業ディレクトリを取得できます:

# Path.cwd(): 現在の作業ディレクトリを取得
from pathlib import Path

print(Path.cwd())  # Path オブジェクトとして現在の作業ディレクトリを返す
/home/labex/project

新しいフォルダの作成

*nix での pathlib の使用:

from pathlib import Path
cwd = Path.cwd()
(cwd / 'delicious' / 'walnut' / 'waffles').mkdir()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.6/pathlib.py", line 1226, in mkdir
    self._accessor.mkdir(self, mode)
  File "/usr/lib/python3.6/pathlib.py", line 387, in wrapped
    return strfunc(str(pathobj), *args)
FileNotFoundError: [Errno 2] No such file or directory: '/home/labex/project/delicious/walnut/waffles'

おっと、厄介なエラーが発生しました!理由は、‘delicious’ ディレクトリが存在しないため、その下に ‘walnut’ や ‘waffles’ ディレクトリを作成できないからです。これを修正するには、次のようにします:

# mkdir(parents=True): 必要に応じてディレクトリとすべての親ディレクトリを作成
from pathlib import Path
cwd = Path.cwd()
(cwd / 'delicious' / 'walnut' / 'waffles').mkdir(parents=True)  # ネストされたディレクトリを作成

これで全てうまくいきました :)

絶対パスと相対パス

ファイルパスを指定するには 2 つの方法があります。

  • 絶対パス: 常にルートフォルダから始まるパス
  • 相対パス: プログラムの現在の作業ディレクトリに対して相対的なパス

また、ドット (.) とドットドット (..) フォルダもあります。これらは実際のフォルダではなく、パスで使用できる特別な名前です。単一のピリオド(ドット)は「このディレクトリ」の短縮形です。2 つのピリオド(ドットドット)は「親フォルダ」を意味します。

絶対パスの処理

pathlib を使用してパスが絶対パスであるかどうかを確認するには:

from pathlib import Path
Path('/').is_absolute()
True
Path('..').is_absolute()
False
クイズ

ログインしてこのクイズに回答し、学習の進捗を追跡できます

Path('/').is_absolute() は何を返しますか?
A. True
B. False
C. None
D. '/'

pathlib を使用して絶対パスを抽出できます:

from pathlib import Path
print(Path.cwd())
/home/labex/project
print(Path('..').resolve())
/home

相対パスの処理

pathlib を使用して、開始パスから別のパスへの相対パスを取得できます:

from pathlib import Path
print(Path('/etc/passwd').relative_to('/'))
etc/passwd

パスとファイルの有効性

ファイル/ディレクトリの存在確認

*nix での pathlib の使用:

from pathlib import Path

Path('.').exists()
True
Path('setup.py').exists()
True
Path('/etc').exists()
True
Path('nonexistentfile').exists()
False

パスがファイルであることの確認

*nix での pathlib の使用:

from pathlib import Path

Path('setup.py').is_file()
True
Path('/home').is_file()
False
Path('nonexistentfile').is_file()
False
クイズ

ログインしてこのクイズに回答し、学習の進捗を追跡できます

setup.py が存在する場合、Path('setup.py').is_file() は何を返しますか?
A. 'setup.py'
B. False
C. True
D. None

パスがディレクトリであることの確認

*nix での pathlib の使用:

from pathlib import Path

Path('/').is_dir()
True
Path('setup.py').is_dir()
False
Path('/spam').is_dir()
False

ファイルサイズをバイト単位で取得する

*nix での pathlib の使用:

from pathlib import Path

stat = Path('/bin/python3.6').stat()
print(stat) # stat にはファイルに関する他の情報も含まれています
os.stat_result(st_mode=33261, st_ino=141087, st_dev=2051, st_nlink=2, st_uid=0,
--snip--
st_gid=0, st_size=10024, st_atime=1517725562, st_mtime=1515119809, st_ctime=1517261276)
print(stat.st_size) # バイト単位のサイズ
10024

ディレクトリのリスト表示

*nix で pathlib を使用してディレクトリの内容をリスト表示:

from pathlib import Path

for f in Path('/usr/bin').iterdir():
    print(f)
...
/usr/bin/tiff2rgba
/usr/bin/iconv
/usr/bin/ldd
/usr/bin/cache_restore
/usr/bin/udiskie
/usr/bin/unix2dos
/usr/bin/t1reencode
/usr/bin/epstopdf
/usr/bin/idle3
...

ディレクトリのファイルサイズ

警告

ディレクトリ自体にもサイズがあります!したがって、上記セクションで説明したメソッドを使用して、パスがファイルかディレクトリかをチェックすることをお勧めします。

*nix での pathlib の使用:

from pathlib import Path

total_size = 0
for sub_path in Path('/usr/bin').iterdir():
    total_size += sub_path.stat().st_size

print(total_size)
1903178911

ファイルとフォルダのコピー

shutil モジュールは、ファイルのコピー、およびフォルダ全体のコピーのための関数を提供します。

import shutil

shutil.copy('/tmp/spam.txt', '/tmp/delicious')
/tmp/delicious/spam.txt
shutil.copy('/tmp/eggs.txt', '/tmp/delicious/eggs2.txt')
/tmp/delicious/eggs2.txt
クイズ

ログインしてこのクイズに回答し、学習の進捗を追跡できます

サブディレクトリとファイルをすべて含むディレクトリツリー全体をコピーするには、どの関数を使用する必要がありますか?
A. shutil.copy()
B. Path.copy()
C. os.copy()
D. shutil.copytree()

shutil.copy() は単一のファイルをコピーしますが、shutil.copytree() はフォルダ全体と、それに含まれるすべてのフォルダとファイルをコピーします:

import shutil

shutil.copytree('/tmp/bacon', '/tmp/bacon_backup')
/tmp/bacon_backup

移動と名前の変更

import shutil

shutil.move('/tmp/bacon.txt', '/tmp/eggs')
/tmp/eggs/bacon.txt

宛先パスでファイル名を指定することもできます。次の例では、ソースファイルが移動され、名前が変更されます:

shutil.move('/tmp/bacon.txt', '/tmp/eggs/new_bacon.txt')
/tmp/eggs/new_bacon.txt

eggs フォルダが存在しない場合、move() は bacon.txt を eggs という名前のファイルに名前変更します:

shutil.move('/tmp/bacon.txt', '/tmp/eggs')
/tmp/eggs

ファイルとフォルダの削除

  • Path.unlink() を呼び出すと、パス上のファイルが削除されます。
  • Path.rmdir() を呼び出すと、パス上のフォルダが削除されます。このフォルダはファイルやフォルダを含んでいてはいけません。
  • shutil.rmtree(path) を呼び出すと、パス上のフォルダが削除され、含まれるすべてのファイルとフォルダも削除されます。
クイズ

ログインしてこのクイズに回答し、学習の進捗を追跡できます

空でないディレクトリとそのすべてのコンテンツを削除できるメソッドはどれですか?
A. Path.rmdir()
B. shutil.rmtree()
C. Path.unlink()
D. os.remove()

ディレクトリツリーのウォーク

Path オブジェクトには、ファイルとディレクトリを再帰的に反復処理するための rglob() メソッドがあります。

from pathlib import Path

p = Path('/tmp/delicious')
for i in p.rglob('*'):
    print(i)
/tmp/delicious/cats
/tmp/delicious/walnut
/tmp/delicious/spam.txt
/tmp/delicious/cats/catnames.txt
/tmp/delicious/cats/zophie.jpg
/tmp/delicious/walnut/waffles
/tmp/delicious/walnut/waffles/butter.txt

関連リンク