PythonでGUIアプリケーションを作成する際、UIの描画ロジックとファイル操作などの実ロジックが密結合になりがちです。今回は、PySide6を用いて、GUI表示ロジックとファイル操作の実ロジックを疎結合に保つクリーンな設計(データモデルとリポジトリパターン)を採用したファイルマネージャーのベース実装を紹介します。
このプロジェクトは、独自のファイル管理ツールや画像ビューア、メディア管理ライブラリなどを開発するための強力な土台となります。
アーキテクチャの全体像
本プロジェクトでは、オブジェクト指向のポリモーフィズムとリポジトリパターンを意識した以下の3層構造を採用しています。
- データモデル層 (
BaseNodeObject.py) - データアクセス・操作層 (
FilerRepository.py) - UI・プレゼンテーション層 (
main.py)
これにより、GUI層から os や shutil などのOS標準モジュールを直接呼び出すことを避け、テストや機能拡張を容易にしています。
1. データモデル層: UIに必要な情報をカプセル化
ファイルやディレクトリは、共通の基底クラス BaseNodeObject の派生クラスとして表現します。
アイコンの判定、ファイルタイプ名、サイズのフォーマットなど、UI描画に必要なプロパティはすべてモデル内に隠蔽します。
# BaseNodeObject.py (抜粋)
from PySide6.QtCore import QObject, Signal
import os
class BaseNodeObject(QObject):
"""ファイルやディレクトリのベースとなるデータオブジェクト"""
updated = Signal(object) # 状態が変わったらGUIに通知する信号
def __init__(self, path: str):
super().__init__()
self.path = os.path.abspath(path)
self.name = os.path.basename(path)
self._is_selected = False
@property
def exists(self) -> bool:
return os.path.exists(self.path)
# ... その他の共通プロパティ (modified_time, created_timeなど)
class FileNodeObject(BaseNodeObject):
"""ファイルを表すオブジェクト"""
def __init__(self, path: str):
super().__init__(path)
self.size = os.path.getsize(path) if os.path.exists(path) else 0
self.extension = os.path.splitext(self.name)[1]
@property
def icon(self) -> str:
# 拡張子に応じた絵文字アイコンを返す
ext = self.extension.lower()
if ext in ['.png', '.jpg', '.jpeg']: return "🖼️"
elif ext in ['.py', '.js', '.html']: return "⚙️"
return "📄"
class DirectoryNodeObject(BaseNodeObject):
"""ディレクトリを表すオブジェクト"""
def __init__(self, path: str):
super().__init__(path)
self.children: list[BaseNodeObject] = []
@property
def icon(self) -> str:
return "📁"
このようにすることで、GUI側は「アイコンは何かな?」と各ノードの icon プロパティを呼び出すだけでよくなり、UI側に条件分岐の判定ロジックを書く必要がなくなります。
2. データアクセス層: リポジトリパターンの適用
FilerRepository は、ファイルシステムの読み書きをカプセル化します。GUI側はこのリポジトリを介してのみファイルシステムへの操作を行います。
# FilerRepository.py (抜粋)
import os
import shutil
from BaseNodeObject import DirectoryNodeObject, FileNodeObject
class FilerRepository:
"""ローカルのファイルシステムからノードを取得し、操作するラッパー"""
def get_directory_node(self, dir_path: str) -> DirectoryNodeObject:
"""指定されたパスのディレクトリノードを作成し、中身のノード群を詰めて返す"""
dir_node = DirectoryNodeObject(dir_path)
if not os.path.isdir(dir_path):
return dir_node
try:
for entry in os.scandir(dir_path):
if entry.is_dir():
dir_node.children.append(DirectoryNodeObject(entry.path))
else:
dir_node.children.append(FileNodeObject(entry.path))
except PermissionError:
pass
return dir_node
def rename_node(self, node_path: str, new_name: str) -> bool:
"""ノードの名前を変更する"""
# (実装省略)
def delete_node(self, node_path: str) -> bool:
"""ノードを削除する"""
# (実装省略)
os.scandir を用いてディレクトリ内を走査し、先ほど定義した FileNodeObject や DirectoryNodeObject のインスタンスに変換して返却している点がポイントです。
3. UI・プレゼンテーション層
GUIは main.py で PySide6 を用いて構築されています。
GUIコンポーネント(例えばファイルのリストを表示するTableViewなど)は、FilerRepository.get_directory_node() を呼び出して DirectoryNodeObject を取得し、その中に入っている children をループして描画するだけです。
「戻る」「進む」などのナビゲーション機能や、右クリックのコンテキストメニューからリポジトリのメソッド(名前変更、削除など)を呼び出すように構築することで、シンプルかつメンテナブルなUIコードを維持できます。
応用例・まとめ
このベースクラス群を活用すれば、用途に合わせた様々なツールを素早く開発できます。
-
プレビュー機能の追加:
FileNodeObjectに画像やテキストのプレビューデータを返すメソッドを追加すれば、詳細パネルにプレビューを表示するのも簡単です。 -
仮想ファイルシステム:
FilerRepositoryを差し替えることで、クラウドストレージ(S3やGoogle Driveなど)上のファイルを操作するGUIマネージャーへの拡張も可能です(GUI側のインターフェースを変えずに実装可能)。
ファイル操作を伴うGUIアプリを作成する際は、ぜひこのような「データモデルの分離」と「リポジトリパターンの導入」を試してみてください!