4-3. ディレクトリと木構造

ディレクトリと木構造について説明します。 この内容はPythonというよりも、Windows・macOS・Linuxなどの一般的なOSに共通する概念です。 なお、Colaboratoryは、仮想マシン上のLinuxの上で動作しています。

ディレクトリと階層構造

OS上でのファイルは一般に、階層的に管理されています。 ファイルが置かれる場所のことを、ディレクトリと呼びます。 ファイルをまとめることから、フォルダとも呼ばれます。

zip版の教材を、IPP_textbook ディレクトリに展開すると、次のような階層構造になります。

IPP_textbook
├── 1
│   ├── 1-0.ipynb
│   ├── 1-1.ipynb
│   ├── 1-2.ipynb
│   ├── 1-3.ipynb
│   ├── 1-4.ipynb
│   ├── colab1.png
│   ├── colab2.png
│   ├── colab3.png
│   ├── colab4.png
│   └── colaboratory.png
├── 2
│   ├── 2-1.ipynb
│   ├── 2-2.ipynb
│   └── 2-3.ipynb
├── 3
│   ├── 3-1.ipynb
│   ├── 3-2.ipynb
│   └── 3-3.ipynb
├── 4
│   ├── 4-1.ipynb
│   ├── 4-2.ipynb
│   ├── 4-3.ipynb
│   ├── sample.txt
│   ├── shift_jis.txt
│   ├── test.txt
│   ├── text
│   │   └── novel.txt
│   └── utf-8.txt
├── 5
│   ├── 5-1.ipynb
│   ├── 5-2.ipynb
│   ├── 5-3.ipynb
│   ├── factorial.py
│   └── fig
│       ├── py_conv_1.png
│       ├── py_conv_2.png
│       ├── py_open_1.png
│       └── py_open_2.png
├── 6
│   ├── 6-1.ipynb
│   ├── 6-2.ipynb
│   ├── 6-3.ipynb
│   └── jugemu.txt
├── 7
│   ├── 7-1.ipynb
│   ├── 7-2.ipynb
│   └── iris.csv
├── appendix
│   ├── 1-jupyter-notebook.ipynb
│   ├── 2-set.ipynb
│   ├── 3-recursion.ipynb
│   ├── 3-visualization.ipynb
│   ├── 4-csv.ipynb
│   ├── 5-bokeh.ipynb
│   ├── 5-command.ipynb
│   ├── 5-matplotlib.ipynb
│   ├── 5-re.ipynb
│   ├── argsprint.py
│   ├── B1S.xml
│   ├── fig
│   │   ├── argsprint.png
│   │   ├── console_in_browser.png
│   │   ├── py_conv_1.png
│   │   ├── py_conv_2.png
│   │   ├── py_open_1.png
│   │   ├── py_open_2.png
│   │   ├── sample_py_browser.png
│   │   ├── sample_py_mac_1.png
│   │   ├── sample_py_mac_2.png
│   │   ├── sample_py_mac_3.png
│   │   ├── sample_py_win_1.png
│   │   ├── sample_py_win2_1.png
│   │   ├── sample_py_win2_2.png
│   │   ├── sample_py_win2_3.png
│   │   ├── sample_py_win_2.png
│   │   ├── sample_py_win_3.png
│   │   └── terminal_in_menu.png
│   ├── sample.py
│   ├── small.csv
│   ├── text-sample.txt
│   ├── tokyo-july-temps.csv
│   └── tokyo-temps.csv
├── index.ipynb
├── index_of_terms.ipynb
└── LICENSE

ここで、末端にあるものがファイルであり、末端以外にあるものがディレクトリ(フォルダ)です。

カレントワーキングディレクトリ

プログラムは、必ずどこかのディレクトリで動いています。 このプログラムが動作しているディレクトリのことを、ワーキングディレクトリ(もしくは作業ディレクトリ)と呼びます。 通例、特にWindowsやmacOSでは、何らかのファイルをクリックしてアプリケーションが起動したとき、その開いたファイルのある場所がワーキングディレクトリになります。

ワーキングディレクトリは、プログラムの実行中に変更できます。 Python上では os.chdir を使うことで変更できます。

プログラム実行中の現在のワーキングディレクトリのことを、カレントワーキングディレクトリ、もしくは単にカレントディレクトリと呼びます。 カレントディレクトリは頻繁に利用するので、 . という特別な記号によって表現できます。

パス

カレントディレクトリに置かれているファイルは、ファイル名を指定するだけで開くことができます。 だから、4-1で示したように、カレントディレクトリにある sample.txt は、ファイル名を指定するだけで開けます。

[1]:
open('sample.txt', 'r', encoding='utf-8')
[1]:
<_io.TextIOWrapper name='sample.txt' mode='r' encoding='utf-8'>

一方、それ以外の場所にあるファイルについては、そのファイルのディレクトリまで含めて指定しなければ、開くことができません。

[2]:
open('novel.txt', 'r')
---------------------------------------------------------------------------
FileNotFoundError                         Traceback (most recent call last)
Cell In[2], line 1
----> 1 open('novel.txt', 'r')

File ~/Projects/utpython/lib/python3.12/site-packages/IPython/core/interactiveshell.py:324, in _modified_open(file, *args, **kwargs)
    317 if file in {0, 1, 2}:
    318     raise ValueError(
    319         f"IPython won't let you open fd={file} by default "
    320         "as it is likely to crash IPython. If you know what you are doing, "
    321         "you can use builtins' open."
    322     )
--> 324 return io_open(file, *args, **kwargs)

FileNotFoundError: [Errno 2] No such file or directory: 'novel.txt'

カレントディレクトリに存在しない novel.txt を開こうとしたので FileNotFoundError が生じました。 novel.txt は、text というディレクトリの中にあるので、それ明示するために、/ で区切って、次のように指定します。

[3]:
open('text/novel.txt', 'r', encoding='utf-8')
[3]:
<_io.TextIOWrapper name='text/novel.txt' mode='r' encoding='utf-8'>

実は、カレントディレクトリにあるファイルが、ファイル名の指定だけで開けるのは、自動的に ./ が補われていたからでした。

open の第1引数に渡す文字列ような、ファイルやディレクトリの場所を指定する表記を、パスと呼びます。 パス(経路)と呼ぶのは、/ 区切りで1歩ずつ次に進むディレクトリを指定することに由来しています。

パスを記述する際、ルートディレクトリは / で表されます。 ルートディレクトリから始まるパスを、絶対パスと呼びます。 一方、カレントディレクトリからのパスを、相対パスと呼びます。 パス表記において / 以外から始まる場合は、自動的に先頭に ./ が補われて、相対パスとして扱われます。

さて、./text/ というパス表記は、カレントディレクトリにある一段下の text ディレクトリに進むことに対応します。 これに、一段上のディレクトリを表す .. を組み合わせることで、より柔軟にパスを指定できます。

たとえば、./text/.././ と同じディレクトリを指します。

[4]:
with open('sample.txt', 'r', encoding='utf-8') as f:
    print(f.read(), end='')
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
[5]:
with open('./text/../sample.txt', 'r', encoding='utf-8') as f:
    print(f.read(), end='')
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

また、カレントディレクトリを ./text/ に変化させた後に、sample.txt を開くときには、../sample.txt と指定することができます。

[6]:
import os
os.chdir('./text') # 1段下の text に行く
with open('../sample.txt', 'r', encoding='utf-8') as f:
    print(f.read())
os.chdir('..') # 元のディレクトリに戻る
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.
Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

木構造

ディレクトリの階層構造は、木を逆さにしたような形になっています。 このことから、木構造もしくは単に(tree)と呼ばれます。

木構造の観点で、最上位の階層は(root)と呼ばれ、末端は(leaf)と呼ばれます。 木構造を成す要素(ここではファイルやディレクトリ)は,一般にノード(node)と呼ばれます。

階層構造における包含関係は、木構造では親子関係と呼ばれます。 たとえば、教材のディレクトリ階層においては、ディレクトリ IPP_textbook の中に、ディレクトリ 1 とファイル LICENSE が含まれています。 木構造の観点で、IPP_textbook1LICENSE(parent)と呼ばれ、逆に 1LICENSEIPP_textbook(child)と呼ばれます。 また、1LICENSE のような共通の親を持つノード集合は、兄弟(sibling)と呼ばれます。 親子関係の推移閉包として、祖先(ancestor)や子孫(descendant)も定義されます。

データ表現としての木構造

OSのディレクトリ構造に限らず、木構造はデータ表現として広く用いられます。 Pythonにおいても、入れ子のデータ構造は、木構造と見做せます。

たとえば、教材のディレクトリ構造を、辞書の入れ子で表現すると、次のようになります.

IPP_textbook = {
  '1': {'1-0.ipynb': Data('1-0.ipynb'),
        ...,
        'colaboratory.png', Data('colaboratory.png')},
  '2': {'2-1.ipynb': Data('2-1.ipynb'),
        '2-1.ipynb': Data('2-2.ipynb'),
        '2-3.ipynb': Data('2-3.ipynb')},
  ...
  '5': {'5-1.ipynb': Data('5-1.ipynb'),
        '5-1.ipynb': Data('5-2.ipynb'),
        '5-3.ipynb': Data('5-3.ipynb'),
        'factorial.py', Data('factorial.py'),
        'fig': {'py_conv_1.png': Data('py_conv_1.png'),
                'py_conv_2.png': Data('py_conv_2.png'),
                'py_open_1.png': Data('py_open_1.png'),
                'py_open_2.png': Data('py_open_2.png')}
       },
   ...
}

ここで Data(...) は、... のファイルデータを意味しています。

このデータ表現では、パス IPP_textbook/2/2-2.ipynb へのアクセスが、IPP_textbook['2']['2-2.ipynb'] と表現されます。

データを階層的に管理する際は、データ全体を木構造として捉えて表現することを考えましょう。

[ ]: