総合演習
演習問題
本章で学習したクラス構文、ダックタイピング、演算子のオーバーロード、および型ヒントを活用して、簡易的なファイルシステムを構築しましょう。 以下の仕様を満たすプログラムを作成しなさい。
- ファイルシステム内のすべての要素(ファイルとディレクトリ)が適合すべきプロトコルとして、
Nodeを定義すること。- 属性として、要素の名前を表す
name(文字列型)を持つこと。 - メソッドとして、自身の階層構造を表示する
displayメソッドを持つこと。displayメソッドは、引数としてインデントの深さを表すdepth(整数型)を受け取り、戻り値はなしとすること。
- 属性として、要素の名前を表す
Nodeプロトコルに適合するFileクラスを作成すること。- 初期化メソッド
__init__でnameとcontent(中身の文字列)を受け取り、動的に属性を設定すること。 displayメソッドは、指定されたdepthの数だけスペース4つ分のインデントを出力した後に、ファイル名と中身を表示すること。表示形式は[File] ファイル名 (内容の文字数文字)とすること。
- 初期化メソッド
Nodeプロトコルに適合するDirectoryクラスを作成すること。- 初期化メソッド
__init__でnameを受け取ること。 - ディレクトリ内に保持する子ノードのリストとして、
nodes属性(list[Node]型)を空リストで初期化すること。 displayメソッドは、指定されたdepthの数だけインデントを出力した後にディレクトリ名を表示すること。表示形式は[Dir] ディレクトリ名とすること。さらに、自身が持つ子ノードのdisplayメソッドを、depthを1増やした状態で順に呼び出して表示させること。
- 初期化メソッド
Directoryクラスにおいて、演算子のオーバーロードとイテレータプロトコルを実装すること。/演算子(__truediv__)をオーバーロードし、directory / nodeの形式で子ノードをnodesリストに追加できるようにすること。また、追加後に自分自身を戻り値として返すことで、directory / file1 / file2のように連続して要素を追加できるようにすること。len()関数に対応する__len__を定義し、直下にある子ノードの数を返すこと。- インデックスアクセスに対応する
__getitem__を定義し、指定したインデックスの子ノードを返すこと。 forループなどでの巡回に対応する__iter__を定義し、子ノードのイテレータを返すこと。
- 作成したクラスを用いて、ルートディレクトリを作成し、複数のファイルやサブディレクトリを追加して、構造を表示するプログラムを書きなさい。
解答例
演習問題のプログラムの実装例は以下の通りである。
実装の方針として、まず typing.Protocol を用いてファイルとディレクトリの共通の枠組みとなる Node を定義する。
次に Node を満たすように File と Directory クラスをそれぞれ定義し、Directory には各種特殊メソッドを追加して操作性を向上させる。
最後にこれらのクラスを組み合わせて階層構造を作成し、一括して表示を行う。
file_system.py
from typing import Protocol
class Node(Protocol):
name: str
def display(self, depth: int) -> None:
...
class File:
def __init__(self, name: str, content: str):
self.name = name
self.content = content
def display(self, depth: int) -> None:
indent = " " * depth
print(f"{indent}[File] {self.name} ({len(self.content)} chars)")
class Directory:
def __init__(self, name: str):
self.name = name
self.nodes: list[Node] = []
def display(self, depth: int) -> None:
indent = " " * depth
print(f"{indent}[Dir] {self.name}")
for node in self.nodes:
node.display(depth + 1)
def __truediv__(self, other: Node) -> "Directory":
self.nodes.append(other)
return self
def __len__(self) -> int:
return len(self.nodes)
def __getitem__(self, index: int) -> Node:
return self.nodes[index]
def __iter__(self):
return iter(self.nodes)
root = Directory("root")
src = Directory("src")
tests = Directory("tests")
# / 演算子を用いてディレクトリの下にファイルやディレクトリを追加する
root / src / tests
src / File("main.py", "print('Hello')") / File("utils.py", "def add(a, b): return a + b")
tests / File("test_main.py", "def test_main(): pass")
root / File("README.md", "Project documentation.")
# 階層構造の表示
root.display(0)
# lenやインデックスアクセス、イテレータの検証
print(f"Root elements count: {len(root)}")
print(f"First element in root: {root[0].name}")
print("Looping through root elements:")
for child in root:
print(f"- {child.name} ({type(child).__name__})")
プログラムの実行結果は以下のようになります。
実行結果
[Dir] root
[Dir] src
[File] main.py (14 chars)
[File] utils.py (27 chars)
[Dir] tests
[File] test_main.py (21 chars)
[File] README.md (22 chars)
Root elements count: 3
First element in root: src
Looping through root elements:
- src (Directory)
- tests (Directory)
- README.md (File)