総合演習

演習問題

本章で学習したクラス構文、ダックタイピング、演算子のオーバーロード、および型ヒントを活用して、簡易的なファイルシステムを構築しましょう。 以下の仕様を満たすプログラムを作成しなさい。

  1. ファイルシステム内のすべての要素(ファイルとディレクトリ)が適合すべきプロトコルとして、Node を定義すること。
    • 属性として、要素の名前を表す name(文字列型)を持つこと。
    • メソッドとして、自身の階層構造を表示する display メソッドを持つこと。display メソッドは、引数としてインデントの深さを表す depth(整数型)を受け取り、戻り値はなしとすること。
  2. Node プロトコルに適合する File クラスを作成すること。
    • 初期化メソッド __init__namecontent(中身の文字列)を受け取り、動的に属性を設定すること。
    • display メソッドは、指定された depth の数だけスペース4つ分のインデントを出力した後に、ファイル名と中身を表示すること。表示形式は [File] ファイル名 (内容の文字数文字) とすること。
  3. Node プロトコルに適合する Directory クラスを作成すること。
    • 初期化メソッド __init__name を受け取ること。
    • ディレクトリ内に保持する子ノードのリストとして、nodes 属性(list[Node] 型)を空リストで初期化すること。
    • display メソッドは、指定された depth の数だけインデントを出力した後にディレクトリ名を表示すること。表示形式は [Dir] ディレクトリ名 とすること。さらに、自身が持つ子ノードの display メソッドを、depth を1増やした状態で順に呼び出して表示させること。
  4. Directory クラスにおいて、演算子のオーバーロードとイテレータプロトコルを実装すること。
    • / 演算子(__truediv__)をオーバーロードし、directory / node の形式で子ノードを nodes リストに追加できるようにすること。また、追加後に自分自身を戻り値として返すことで、directory / file1 / file2 のように連続して要素を追加できるようにすること。
    • len() 関数に対応する __len__ を定義し、直下にある子ノードの数を返すこと。
    • インデックスアクセスに対応する __getitem__ を定義し、指定したインデックスの子ノードを返すこと。
    • for ループなどでの巡回に対応する __iter__ を定義し、子ノードのイテレータを返すこと。
  5. 作成したクラスを用いて、ルートディレクトリを作成し、複数のファイルやサブディレクトリを追加して、構造を表示するプログラムを書きなさい。

解答例

演習問題のプログラムの実装例は以下の通りである。

実装の方針として、まず typing.Protocol を用いてファイルとディレクトリの共通の枠組みとなる Node を定義する。 次に Node を満たすように FileDirectory クラスをそれぞれ定義し、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)

results matching ""

    No results matching ""