プロジェクト

全般

プロフィール

11章学習記録 » 履歴 » バージョン 1

Tatsuya ISHIGAKI, 2025/07/24 12:25

1 1 Tatsuya ISHIGAKI
# 第11章 ファイルとディレクトリへのアクセス
2
3
## 11.1 ファイルパス操作を直感的に行う pathlib
4
`pathlib` モジュールは、I/O を伴わない機能を提供する **純粋パス (pure path)** と、I/O を伴う機能を提供する **具象パス (concrete path)** を提供する
5
6
- クラス構成
7
  
8
  |クラス名|解説|基底クラス|
9
  |---|---|---|
10
  |`PurePath`|純粋パスクラスの基底クラス|なし|
11
  |`PurePosixPath`|非 Windows 向けの純粋パスクラス|`PurePath`|
12
  |`PureWindowsPath`|Windows 向けの純粋パスクラス|`PurePath`|
13
  |`Path`|具象パスクラスの基底クラス|`PurePath`|
14
  |`PosixPath`|非 Windows 向けの具象パスクラス|`Path` `PurePosixPath`|
15
  |`WindowsPath`|Windows 向けの具象パスクラス|`Path` `PureWindowsPath`|
16
17
- Path オブジェクト (PurePath, Path) 共通
18
  - Path オブジェクト (`PurePath` `Path` インスタンス) の生成には、`PurePath` `Path` に **文字列または他のPathオブジェクト** を渡す
19
  - 複数の引数を指定した場合は、それらを連結したオブジェクトが生成される
20
  - `PurePath` を使ってインスタンス生成するだけで、環境に合わせて `PurePosixPath` または `PureWindowsPath` のインスタンスが生成される
21
    - `Path` についても同様に、`PosixPath` `WindowsPath` が生成される
22
  - Path オブジェクト同士、Path オブジェクトと文字列は演算子 `/` が使用可能で、接続されたパスを持つ Path オブジェクトを返す
23
24
- 純粋パスを扱う PurePath
25
26
  ```python
27
  >>> from pathlib import PurePath, Path
28
  >>> PurePath("hello.txt")
29
  PureWindowsPath('hello.txt')
30
  >>> PurePath("words", "greeting", "hello.txt")  # 複数文字列指定
31
  PureWindowsPath('words/greeting/hello.txt')
32
  >>> PurePath(Path("japanese"), "greeting", "ohayo.txt")  # Pathオブジェクト指定
33
  PureWindowsPath('japanese/greeting/ohayo.txt')
34
  >>> PurePath()  # 指定無しはカレントディレクトリ
35
  PureWindowsPath('.')
36
37
  # 演算子 `/` による接続パスのオブジェクト生成
38
  >>> p = PurePath("japanese","greeting")
39
  >>> p
40
  PureWindowsPath('japanese/greeting')
41
  >>> p / "konnichiwa.txt"
42
  PureWindowsPath('japanese/greeting/konnichiwa.txt')
43
  >>> "all" / p
44
  PureWindowsPath('all/japanese/greeting')
45
  >>> q = PurePath("night", "oyasumi.txt")
46
  >>> q
47
  PureWindowsPath('night/oyasumi.txt')
48
  >>> p / q
49
  PureWindowsPath('japanese/greeting/night/oyasumi.txt')
50
  ```
51
52
  - PurePath のプロパティ
53
54
    |プロパティ名|解説|戻り値|
55
    |---|---|---|
56
    |`parts`|パスの各要素のタプル|tuple|
57
    |`drive`|ドライブを表す文字列<br>`C:` など<br>UNC共有名もドライブ扱い|str|
58
    |`root`|ルートを表す文字列<br>`/` `\\` など|str|
59
    |`anchor`|ドライブとルートを結合した文字列|str|
60
    |`parents`|上位パスのシーケンス|Path オブジェクトのシーケンス|
61
    |`parent`|直接の上位パス|Path オブジェクト|
62
    |`name`|パス要素の末尾を表す文字列|str|
63
    |`suffix`|末尾の要素の拡張子(ドット含む)<br>一番後ろの拡張子のみ|str|
64
    |`suffixes`|末尾の要素の拡張子リスト|list|
65
    |`stem`|末尾の要素から拡張子を除外した文字列<br>一番後ろの拡張子のみ除外する|str|
66
67
    ```python
68
    >>> from pathlib import PurePath, PureWindowsPath
69
    >>> p = PurePath('/spam/ham/egg.tar.gz')
70
    >>> p.parts  # 各要素を取得
71
    ('/', 'spam', 'ham', 'egg.tar.gz')
72
    >>> wp = PureWindowsPath('c:/Program Files/spam/ham.exe')
73
    >>> wp.parts
74
    ('c:\\', 'Program Files', 'spam', 'ham.exe')
75
    >>> p.drive  # ドライブを取得
76
    ''
77
    >>> wp.drive
78
    'c:'
79
    >>> p.root  # ルートを取得
80
    '/'
81
    >>> wp.root
82
    '\\'
83
    >>> wp.anchor  # ドライブとルートを結合した文字列を取得
84
    'c:\\'
85
    >>> for parent in p.parents:  # 上位のパスのシーケンスを取得
86
    ...     parent
87
    ...
88
    PurePosixPath('/spam/ham')
89
    PurePosixPath('/spam')
90
    PurePosixPath('/')
91
    >>> p.parent  # 直接の上位のパスを取得
92
    PurePosixPath('/spam/ham')
93
    >>> p.name  # 末尾の要素を取得
94
    'egg.tar.gz'
95
    >>> p.suffix  # 拡張子を取得
96
    '.gz'
97
    >>> p.suffixes  # 拡張子のリストを取得
98
    ['.tar', '.gz']
99
    >>> p.stem  # 末尾の要素から拡張子を除いたものを取得
100
    'egg.tar'
101
    ```
102
103
  - PurePath のメソッド
104
105
    |メソッド名|解説|戻り値|
106
    |---|---|---|
107
    |`is_absolute()`|絶対パスの場合 True|bool|
108
    |`is_relative_to(*other)`|`other` に対して相対なら True<br>(`other` がこのパス、またはこのパスの parents 要素と一致すれば True)|bool|
109
    |`match(pattern)`|glob 形式の `pattern` と一致すれば True|bool|
110
    |`with_name(name)`|パスの name 部分を引数 `name` で置換したパス|Path オブジェクト|
111
    |`with_stem(stem)`|パスの stem 部分を引数 `stem` で置換したパス|Path オブジェクト|
112
    |`with_suffix(suffix)`|パスの suffix 部分を引数 `suffix` で置換したパス|Path オブジェクト|
113
114
    ```python
115
    >>> from pathlib import PurePath
116
    >>> p1 = PurePath('/spam/ham/eggs.txt')
117
    >>> p2 = PurePath('eggs.txt')
118
    >>> p1.is_absolute()  # 絶対パスか
119
    True
120
    >>> p2.is_absolute()
121
    False
122
    >>> p1.is_relative_to('/spam')  # 指定したパスに対して相対か
123
    True
124
    >>> p1.is_relative_to('/ham')  # 指定したパスに対して相対か
125
    False
126
    >>> p1.match('*.txt')  # パターンに一致するか
127
    True
128
    >>> p1.with_name('hoge.txt')  # nameを変更(eggs.txt→hoge.txt)
129
    PurePosixPath('/spam/ham/hoge.txt')
130
    >>> p1.with_stem('fuga')  # stemを変更(eggs→fuga)
131
    PurePosixPath('/spam/ham/fuga.txt')
132
    >>> p1.with_suffix('.py')  # 拡張子を変更(.txt→.py)
133
    PurePosixPath('/spam/ham/eggs.py')
134
    >>> p1.with_suffix('')  # 拡張子を削除
135
    PurePosixPath('/spam/ham/eggs')
136
    ```
137
138
- 具象パスを扱う Path
139
具象パスの昨日はファイルシステムにアクセスするため、基本的に OS 状に捜査対象のファイルパスが存在する必要がある
140
141
  |メソッド名|解説|戻り値|
142
  |---|---|---|
143
  |`cwd()`|**クラスメソッド**<br>現在のディレクトリを表すオブジェクト|Path オブジェクト|
144
  |`home()`|**クラスメソッド**<br>ユーザのホームディレクトリを表すオブジェクト|Path オブジェクト|
145
  |`stat()`|ファイルの各種情報|os.stat_result オブジェクト|
146
  |`chmod(mode)`|パスのパーミッションを変更|None|
147
  |`exists()`|パスが存在する場合 True|bool|
148
  |`glob(pattern)`|パスが指すディレクトリ以下で `pattern` に一致するファイルやディレクトリの一覧を返すジェネレーター|ジェネレーター<br>(Path オブジェクト)|
149
  |`is_dir()`||bool|
150
  |`is_file()`||bool|
151
  |`iterdir()`|パス直下のファイルやディレクトリの一覧を返すジェネレーター|ジェネレーター<br>(Path オブジェクト)|
152
  |`mkdir(mode=0o777, parents=False, exist_ok=False)`|パスを新しいディレクトリとして作成|None|
153
  |`open(mode='r', buffering=-1, encoding=None, errors=None, newline=None)`|組み込み関数 `open()` と同様にファイルを開く|ファイルオブジェクト|
154
  |`read_text(encoding=Noen, errors=None)`|ファイル内容をテキストとして返す|str|
155
  |`rename(target)`|パスの名前を変更する<br>`target` 文字列または他の Path オブジェクト|None|
156
  |`resolve(strict=False)`|パスを絶対パスにし、シンボリックリンクを解決する|Path オブジェクト|
157
  |`rmdir()`|パスが指すディレクトリを削除する<br>ディレクトリは空の必要がある|None|
158
  |`touch(mode=0o666, exist_ok=True)`|パスのファイルが存在しない場合、作成<br>存在する場合、更新日時を現在日時で更新|None|
159
  |`unlink(missing_ok=False)`|パスのファイルを削除する|None|
160
  |`write_text(data, encoding=None, errors=None)`|ファイルに `data` を書き込む<br>書き込んだ文字数を返す|int|
161
162
  ```python
163
  >>> from pathlib import Path
164
  >>> Path.cwd()  # 現在のディレクトリ
165
  PosixPath('/Users/takanori/spam/ham')
166
  >>> Path.home()  # ホームディレクトリ
167
  PosixPath('/Users/takanori')
168
  >>> p = 'spam.txt'
169
  >>> p.exists()  # 存在を確認
170
  True
171
  >>> p.stat().st_mode  # 状態を取得
172
  33188
173
  >>> p.chmod(0o600)  # パーミッションを変更
174
  >>> p.stat().st_mode
175
  33152
176
  >>> p.is_file()  # ファイルかどうか
177
  True
178
  >>> with p.open(encoding='utf-8') as f:  # ファイルを開く
179
  ...     print(f.read())
180
  ...
181
  スパムスパムスパム
182
  >>> p.write_text('ハムハムハム', encoding='utf-8')  # ファイルに書き込み
183
  6
184
  >>> p.read_text(encoding='utf-8')  # ファイルから読み込み
185
  'ハムハムハム'
186
  >>> p.unlink()  # ファイルを削除
187
  >>> p.exists()
188
  False
189
  >>> p.touch()  # ファイルを作成
190
  >>> p.resolve()  # 絶対パスを取得
191
  PosixPath('/Users/takanori/spam/ham/spam.txt')
192
  ```
193
194
  - glob(), iterdir() での検索例
195
196
    ```
197
    ./a.py
198
    ./b.py
199
    ./datas
200
    ./datas/c.txt
201
    ./datas/d.txt
202
    ./readme.txt
203
    ```
204
205
    ```python
206
    >>> from pathlib import Path
207
    >>> p = Path()  # 現在のディレクトリのパスオブジェクトを取得
208
    >>> p.iterdir()  # iterdir()はジェネレーターを返す
209
    <generator object Path.iterdir at 0x7f9f300f7740>
210
    >>> sorted(p.iterdir())  # ディレクトリ直下の全オブジェクトを返す
211
    [PosixPath('a.py'), PosixPath('b.py'), PosixPath('datas'), PosixPath('readme.txt')]
212
    >>> list(p.glob('*.txt'))  # *.txtというファイルを返す
213
    [PosixPath('readme.txt')]
214
    >>> sorted(p.glob('**/*.txt'))  # ディレクトリを再帰的にたどって*.txtというファイルを返す
215
    [PosixPath('datas/c.txt'), PosixPath('datas/d.txt'), PosixPath('readme.txt')]
216
    ```
217
218
- path-like オブジェクト
219
  - 以前は Python でのパス使用は、パス文字列での指定だけだったが、現在は `Path` のような **path-like オブジェクト** が使用できる
220
221
222
  path-like オブジェクトに対応する関数、標準ライブラリ
223
  |名前|説明|
224
  |---|---|
225
  |`open()` 関数|ファイルを開く組み込み関数|
226
  |`configparser`|設定ファイルのパーサー|
227
  |`zipfile`|zip アーカイブファイルの操作|
228
  |`sqlite3`|SQLite データベース|
229
  |`shutil`|高水準ファイル操作|
230
  |`os`|OS のインターフェース|
231
  |`os.path`|パスの操作|
232
233
## 11.2 一時的なファイルやディレクトリを生成する tempfile
234
作成ユーザのみ読み書き可能なパーミッション、競合しないなど、可能な限り安全なファイル・ディレクトリ生成が行われる
235
236
- 以下の 4 つの呼び出し可能オブジェクトは、コンテキストマネージャーとして with 文で使用可能
237
  
238
  |呼び出し可能オブジェクト|解説|
239
  |---|---|
240
  |`TemporaryFile()`|ファイル名の無い一時ファイルを作成|
241
  |`NamedTemporaryFile()`|ファイル名がある一時ファイルを作成|
242
  |`SpooledTemporaryFile()`|一定サイズまでメモリ上で処理され、超えるとディスク書き出しが行われる一時ファイルを作成|
243
  |`TemporaryDirectory()`|一時ディレクトリを作成|
244
245
  - `TemporaryFile()` ファイルシステム上に名前を持ったファイルとして生成される保証はない
246
  - `NamedTemporaryFile()` ファイルシステム上に名前を持ったファイルとして生成される為、他システム等から参照可能
247
  - これらはそれぞれ適切な一時ファイルインスタンスを返すため、インスタンス型は関数名とは必ずしも一致しない
248
249
- 一時ファイルを作成する
250
  - `TemporaryFile(mode='w+b', buffering=-1, encoding=None, newline=None, suffix=None, prefix=None, dir=None, *, errors=None)`
251
    - `mode` 一時ファイルオープン時のモード
252
    - `buffering` バッファリングポリシー
253
      - `0` バッファリング無し (バイナリのみ)
254
      - `1` 行単位バッファリング (テキスト書き込みのみ)
255
      - `1より大きい整数` 指定値バイトの固定サイズチャンクバッファ
256
    - `encoding` 文字エンコード
257
    - `newline`
258
      - `None`
259
      - `''`
260
      - `\n`
261
      - `\r`
262
      - `\r\n`
263
    - `suffix` 文字列指定すると、ファイル名の接尾辞として使用される
264
    - `prefix` 文字列指定すると、ファイル名の接頭辞として使用される
265
    - `dir` ファイルを生成するディレクトリを指定する
266
      - デフォルトでは環境変数 `TMPDIR` などの値が採用される
267
    - `errors` エンコード、デコード時のエラーの挙動
268
      - `strict` 例外 ValueError を発生させる (`None` も同じ)
269
      - `ignore`
270
      - `replace`
271
      - `surrogateescape`
272
      - `xmlcharrefreplace`
273
      - `backslashreplace`
274
      - `namereplace`
275
  - `NamedTemporaryFile()`
276
    - (TemporaryFile() と同じ引数あり)
277
    - `delete` `delete_on_close`
278
      - `delete=True` かつ `delete_on_close=True` (デフォルト) : コンテキストマネージャーでの終了時、ファイルの close 時に実ファイルが削除される
279
      - `delete=True` かつ `delete_on_close=False` : コンテキストマネージャーでの終了時 (と file-like オブジェクトが消える際) にのみ実ファイルが削除される
280
      - `delete=False` (このとき `delete_on_close` は無視) : 実ファイルの自動削除無し
281
  
282
  ```python
283
  >>> with tempfile.TemporaryFile() as temp:
284
  ...     print(temp)
285
  ...     temp.write(b"AAA")
286
  ...     temp.write(b"BBBB")
287
  ...     temp.seek(0)
288
  ...     temp.read()
289
  ...
290
  <tempfile._TemporaryFileWrapper object at 0x0000021A34892490>  # 型は "_TemporaryFileWrapper" らしい
291
  3  # write(b"AAA") の返り値(書き込み文字数)
292
  4  # write(b"BBBB") の返り値(書き込み文字数)
293
  0  # seek(0) の返り値(オフセット位置)
294
  b'AAABBBB'  # read() の返り値
295
  ```
296
297
- 一時ディレクトリを作成する
298
  - `TemporaryDirectory(suffix=None, prefix=None, dir=None)`
299
    - `suffix` 文字列指定すると、ディレクトリ名の接尾辞として使用される
300
    - `prefix` 文字列指定すると、ディレクトリ名の接頭辞として使用される
301
    - `dir` ディレクトリを生成するディレクトリを指定する
302
  - コンテキストマネージャーとして実行した場合、as キーワードの引数には **ディレクトリパス文字列** が渡される
303
  - コンテキストマネージャーとして試用しなかった場合は TemporaryDirectory インスタンスが返されるが、自分で削除しないといけない...
304
    - `pathlib.Path` などで操作
305
306
## 11.3 高レベルなファイル操作を行う shutil
307
ディレクトリ、ファイル、およびアーカイブに対して高水準な操作を提供する
308
309
- ファイルをコピーする
310
311
  |関数名|解説|戻り値|
312
  |---|---|---|
313
  |`copyfile(src, dst, *, follow_symlinks=True)`|ファイル `src` をファイル `dst` にコピーする|str|
314
  |`copymode(src, dst, *, follow_symlinks=True)`|パーミッションを `src` から `dst` にコピーする|None|
315
  |`copystat(src, dst, *, follow_symlinks=True)`|パーミッション、最終アクセス時刻、最終変更時刻やその他のファイル情報を `src` から `dst` にコピーする|None|
316
  |`copy(src, dst, *, follow_symlinks=True)`|`copyfile()` + `copymode()`|str|
317
  |`(src, dst, *, follow_symlinks=True)`|`copyfile()` + `copystat()`|str|
318
319
  - 引数の `src` `dst` にはファイルを表す文字列の他、**path-like オブジェクト** や **Path オブジェクト** も指定可能
320
  - `copyfile()` `copy()` `copy2()` の戻り値は `dst` のパス文字列
321
  - ※ `copy()` `copy2()` の `dst` にはディレクトリを指定可能だが、`copyfile()` はファイルのみ
322
323
  ```python
324
  >>> shutil.copyfile("testsrc.txt", "testdst.txt")
325
  'testdst.txt'
326
  >>> shutil.copy("testsrc.txt", "testdst_copy.txt")
327
  'testdst_copy.txt'
328
  >>> shutil.copy2("testsrc.txt", "testdst_copy2.txt")
329
  'testdst_copy2.txt'
330
  ```
331
332
  ```
333
  green@cucumber MINGW64 /e/PythonExamPrac/chapter11
334
  $ stat ./testsrc.txt
335
    File: ./testsrc.txt
336
    Size: 41              Blocks: 1          IO Block: 65536  regular file
337
  Device: e2ca7b4dh/3804920653d   Inode: 1970324837509872  Links: 1
338
  Access: (0644/-rw-r--r--)  Uid: (197610/   green)   Gid: (197610/ UNKNOWN)
339
  Access: 2025-07-23 21:56:51.628029400 +0900
340
  Modify: 2025-07-23 21:49:51.453832500 +0900
341
  Change: 2025-07-23 21:49:51.453832500 +0900
342
   Birth: 2025-07-23 21:49:08.805897000 +0900
343
  
344
  green@cucumber MINGW64 /e/PythonExamPrac/chapter11
345
  $ stat ./testdst.txt
346
    File: ./testdst.txt
347
    Size: 41              Blocks: 1          IO Block: 65536  regular file
348
  Device: e2ca7b4dh/3804920653d   Inode: 281474977245937  Links: 1
349
  Access: (0644/-rw-r--r--)  Uid: (197610/   green)   Gid: (197610/ UNKNOWN)
350
  Access: 2025-07-23 21:57:01.835098500 +0900
351
  Modify: 2025-07-23 21:54:58.789202300 +0900
352
  Change: 2025-07-23 21:55:04.887191200 +0900
353
   Birth: 2025-07-23 21:54:58.789202300 +0900
354
  
355
  green@cucumber MINGW64 /e/PythonExamPrac/chapter11
356
  $ stat ./testdst_copy.txt
357
    File: ./testdst_copy.txt
358
    Size: 41              Blocks: 1          IO Block: 65536  regular file
359
  Device: e2ca7b4dh/3804920653d   Inode: 281474977245938  Links: 1
360
  Access: (0644/-rw-r--r--)  Uid: (197610/   green)   Gid: (197610/ UNKNOWN)
361
  Access: 2025-07-23 21:56:37.277060300 +0900
362
  Modify: 2025-07-23 21:55:52.996307000 +0900
363
  Change: 2025-07-23 21:55:52.996307000 +0900
364
   Birth: 2025-07-23 21:55:52.996307000 +0900
365
  
366
  green@cucumber MINGW64 /e/PythonExamPrac/chapter11
367
  $ stat ./testdst_copy2.txt
368
    File: ./testdst_copy2.txt
369
    Size: 41              Blocks: 1          IO Block: 65536  regular file
370
  Device: e2ca7b4dh/3804920653d   Inode: 281474977245939  Links: 1
371
  Access: (0644/-rw-r--r--)  Uid: (197610/   green)   Gid: (197610/ UNKNOWN)
372
  Access: 2025-07-23 21:56:37.277060300 +0900
373
  Modify: 2025-07-23 21:49:51.453832500 +0900
374
  Change: 2025-07-23 21:49:51.453832500 +0900
375
   Birth: 2025-07-23 21:56:32.456361500 +0900
376
  ```
377
378
- 再帰的にディレクトリやファイルを操作する
379
  
380
  |関数名|解説|戻り値|
381
  |---|---|---|
382
  |`rmtree(path, ignore_errors=False, onerror=None)`|`path` で指定したディレクトリを再帰的に削除<br>配下のファイル、ディレクトリもすべて削除|None|
383
  |`move(src, dst, copy_function=copy2)`|`src` ディレクトリ以下のファイルを再帰的に `dst` に移動<br>コピーに使用する関数を指定可能|str|
384
385
  - 再帰コピーには copytree() を使用
386
    - `copytree(src, dst, symlinks=False, ignore=None, coy_function=copy2, ignore_dangling_symlinks=False, dirs_exist_ok=False)`
387
      - `src` コピー対象ディレクトリ
388
      - `dst` コピー先ディレクトリ (すでに存在する場合は例外送出)
389
      - `symlinks` シンボリックリンクの扱い
390
        - True : シンボリックファイルのままコピー
391
        - False : リンク先のファイルをコピーして配置
392
      - `ignore` コピー対象外ファイルを決定する関数を指定
393
        - shutil.ignore_patterns() を利用すると任意パターンのフィルタが利用できる
394
      - `copy_function` コピーに用いる関数を指定
395
      - `ignore_dangling_symlinks` True にすると、引数 symlinks=False の場合にリンク先が存在しなくてもエラーを出さない
396
      - `dirs_exist_ok` True にすると、コピー先ディレクトリが存在していてもエラーを出さない
397
    - **戻り値** コピー先ディレクトリ名
398
399
      ```bash
400
      $ ls ./from
401
      a.pyc  a.swp  a.txt  b.txt  c.txt  d.txt
402
      ```
403
404
      ```python
405
      >>> import shutil
406
      >>> ignore = shutil.ignore_patterns('*.pyc', '*.swp')  # 拡張子が.pycと.swpを除外対象にする
407
      >>> ignore  # ignore(path, names)という呼び出し可能オブジェクト
408
      <function ignore_patterns.<locals>._ignore_patterns at 0x7fe7d821aa60>
409
      >>> shutil.copytree('./from', './to', ignore=ignore)  # fromからtoに再帰的にコピー
410
      'to'
411
      ```
412
413
      ```bash
414
      $ ls ./to
415
      a.txt  b.txt  c.txt  d.txt
416
      ```