watchdog を PySide で利用し、
ディレクトリの監視を UI で制御してみます。
watchdog について
インストール
pip install watchdog
今回作るのは下記のようなものです。

watchdog が指定したディレクトリを監視し、
そのディレクトリ内でフォルダやファイルの作成・更新・移動・削除があった場合にそれを検知して知らせるところまでを実装します。
実装
UI の作成
import sys import os from PySide2 import QtCore, QtGui, QtWidgets from PySide2.QtCore import Qt class UI(QtWidgets.QDialog): def __init__(self, parent=None): super(UI, self).__init__(parent) self.resize(QtCore.QSize(500, 100)) root_Area = QtWidgets.QHBoxLayout() self.setLayout(root_Area) # パス入力欄 self.path_Line = QtWidgets.QLineEdit() root_Area.addWidget( self.path_Line) # 開始ボタン self.start_Btn = QtWidgets.QPushButton('開始') self.start_Btn.clicked.connect(self.start) root_Area.addWidget(self.start_Btn) # 停止ボタン self.stop_Btn = QtWidgets.QPushButton('停止') self.stop_Btn.clicked.connect(self.stop) root_Area.addWidget(self.stop_Btn) # 監視開始 def start(self): # 文字列の取得 path = self.path_Line.text() # パスが存在する場合 if os.path.isdir(path): pass # 監視の停止 def stop(self): pass if __name__ == '__main__': app = QtWidgets.QApplication(sys.argv) ui = UI() ui.show() sys.exit(app.exec_())
かなりシンプルな UI にしました。

ラインエディットに入れたディレクトリを監視する内容にしていきます。
監視の実装
とりあえず全体のコードがこちら。
import sys import os from PySide2 import QtCore, QtGui, QtWidgets from PySide2.QtCore import Qt # from watchdog.observers import Observer # 通常のオブザーバー from watchdog.observers.polling import PollingObserver from watchdog.events import PatternMatchingEventHandler, FileSystemEvent, FileSystemEventHandler class UI(QtWidgets.QDialog): def __init__(self, parent=None): super(UI, self).__init__(parent) self.resize(QtCore.QSize(500, 100)) root_Area = QtWidgets.QHBoxLayout() self.setLayout(root_Area) # パス入力欄 self.path_Line = QtWidgets.QLineEdit() root_Area.addWidget( self.path_Line) # 開始ボタン self.start_Btn = QtWidgets.QPushButton('開始') self.start_Btn.clicked.connect(self.start) root_Area.addWidget(self.start_Btn) # 停止ボタン self.stop_Btn = QtWidgets.QPushButton('停止') self.stop_Btn.clicked.connect(self.stop) root_Area.addWidget(self.stop_Btn) # self.__observer = Observer() # 通常のオブザーバーの作成 self.__observer = PollingObserver() # 監視開始 def start(self): # 文字列の取得 path = self.path_Line.text() if os.path.isdir(path): # 起動中のスレッドを停止してから新たにObserver作成 if self.__observer.is_alive(): self.__observer.stop() self.__observer.join() # オブザーバーの作成とイベントハンドラへ接続 # self.__observer = Observer() # 通常のオブザーバーの作成 self.__observer = PollingObserver() self.__observer.schedule(WatchdogEvent(), path, True) self.__observer.start() print('監視開始') # 監視の停止 def stop(self): if self.__observer.is_alive(): self.__observer.stop() self.__observer.join() print('監視終了') # 監視中の挙動 class WatchdogEvent(FileSystemEventHandler): # 新規作成された場合 のイベント def on_created(self, event): src_path = event.src_path print('created', src_path) # 更新された場合 のイベント def on_modified(self, event): src_path = event.src_path print('modified', src_path) # 移動させた際のイベント def on_moved(self, event): src_path = event.src_path print('moved', src_path) # 削除した際のイベント def on_deleted(self, event): src_path = event.src_path print('deleted', src_path) if __name__ == '__main__': app = QtWidgets.QApplication(sys.argv) ui = UI() ui.show() sys.exit(app.exec_())
監視中のディレクトリ内のファイルを編集するとそれに応じた内容が出力されるはずです。
オブザーバー
Observer と PollingObserver が監視を行うクラスです。
違いは後述しますが、基本的にはどちらかひとつを使用します。上記のコードは PollingObserver を利用しています。
# self.__observer = Observer() # 通常のオブザーバーの作成 self.__observer = PollingObserver()
下記のインポート文は使用するオブザーバーのみで構いません。
恐らく PollingObserver を使用するケースの方が多いのではないでしょうか。
# from watchdog.observers import Observer # 通常のオブザーバー from watchdog.observers.polling import PollingObserver
主要メソッド
.is_alive() | スレッドが生きているかどうかを判別する。 |
.start() | 監視の開始 |
.stop() | 監視の停止 |
.join() | スレッドが終了するまで待つ。停止したあとはこれを実行しておく。 |
イベントハンドラ
on_created | 作成時のイベント |
on_modified | 更新時のイベント |
on_moved | 移動時のイベント |
on_deleted | 消去時のイベント |
イベントハンドラは名前のままです。
event.src_path
で変更のあった ファイル/フォルダ を取得できます。
通常のオブザーバーとプーリングオブザーバー
先ほどのコードはプーリングオブザーバーで監視するコードです。
オブザーバーを変えるとイベント内容が変わります。
ノーマルなオブザーバーのコード
import sys import os from PySide2 import QtCore, QtGui, QtWidgets from PySide2.QtCore import Qt from watchdog.observers import Observer #from watchdog.observers.polling import PollingObserver from watchdog.events import PatternMatchingEventHandler, FileSystemEvent, FileSystemEventHandler class UI(QtWidgets.QDialog): def __init__(self, parent=None): super(UI, self).__init__(parent) self.resize(QtCore.QSize(500, 100)) root_Area = QtWidgets.QHBoxLayout() self.setLayout(root_Area) # パス入力欄 self.path_Line = QtWidgets.QLineEdit() root_Area.addWidget( self.path_Line) # 開始ボタン self.start_Btn = QtWidgets.QPushButton('開始') self.start_Btn.clicked.connect(self.start) root_Area.addWidget(self.start_Btn) # 停止ボタン self.stop_Btn = QtWidgets.QPushButton('停止') self.stop_Btn.clicked.connect(self.stop) root_Area.addWidget(self.stop_Btn) self.__observer = Observer() # 通常のオブザーバーの作成 #self.__observer = PollingObserver() # 監視開始 def start(self): # 文字列の取得 path = self.path_Line.text() if os.path.isdir(path): # 起動中のスレッドを停止してから新たにObserver作成 if self.__observer.is_alive(): self.__observer.stop() self.__observer.join() # オブザーバーの作成とイベントハンドラへ接続 self.__observer = Observer() # 通常のオブザーバーの作成 #self.__observer = PollingObserver() self.__observer.schedule(WatchdogEvent(), path, True) self.__observer.start() print('監視開始') # 監視の停止 def stop(self): if self.__observer.is_alive(): self.__observer.stop() self.__observer.join() print('監視終了') # 監視中の挙動 class WatchdogEvent(FileSystemEventHandler): # 新規作成された場合 のイベント def on_created(self, event): src_path = event.src_path print('created', src_path) # 更新された場合 のイベント def on_modified(self, event): src_path = event.src_path print('modified', src_path) # 移動させた際のイベント def on_moved(self, event): src_path = event.src_path print('moved', src_path) # 削除した際のイベント def on_deleted(self, event): src_path = event.src_path print('deleted', src_path) if __name__ == '__main__': app = QtWidgets.QApplication(sys.argv) ui = UI() ui.show() sys.exit(app.exec_())
基本的には PollingObserver()
だった部分が Observer()
に変わっただけです。
ファイルをコピーした際の挙動
「test.txt」 をコピーして 「test - コピー.txt」を作ります。
Observer の場合
created D:\test\test - コピー.txt modified D:\test\test - コピー.txt
create イベントと modified イベントが発生します。
PollinngObserver の場合
created D:\test\test - コピー.txt modified D:\test
同様に create イベントと modified イベントが発生します。
が、modified はファイルではなくディレクトリに対して発生します。
Photoshop で上書き保存したときの挙動
「test.png」をフォトショップで上書き保存していきます。
Observer の場合
modified D:\test\test.png created D:\test\psB361.tmp modified D:\test\psB361.tmp deleted D:\test\test.png moved D:\test\psB361.tmp
フォトショップで png データを上書き保存する場合、一時データの tmp ファイルを作って上書き保存を行っています。
上記の出力結果から png データの部分だけを抜き出してみます。
modified D:\test\test.png deleted D:\test\test.png
上書きする png ファイルに関するイベントはこの2つしか発生していません。
編集した後、png データを削除して終了しています。
そして、1行目の modified イベントで取得できるのは 上書き前のデータです。
その後の deleted イベントでも上書きしたデータは取得できません。
イベント内で src_path をコピーするコードを書くことで検証できます。
shutil.copy(src_path , 'D:/hoge')
Observer による監視ではフォトショップの上書き保存のイベントは受け取れない、ということです。
Observer は異なる処理が同時に起きた場合にイベントの発生漏れが起きます。
PollinngObserver の場合
そこででてくるのが PollingObserver の選択肢です。
deleted D:\test\test.png created D:\test\test.png modified D:\test
.tmp の一時データのイベントは受け取れませんが、上書きのイベントに関しては正確に受け取れます。
フォトショップの上書き保存はそのまま上書きしているのではなく、
- 元のデータを削除
- 新規でデータを作成
という方法で保存されています。
上書きしているように見えますが、実際には同名ファイルを削除した後に新規で同名のファイルを作成します。
Observer による監視では 2 の新規作成イベントが発生しなかった ということです。
Observer と PollingObserver は目的によって使い分けてください。
watchdog の処理負荷
負荷の程度は監視量で変わります。
メモリの負荷は気にするレベルではないです。
watchdog は指定したフォルダより下層にある全てのディレクトリを監視します。
この総量が多いほど CPU に負荷をかけます。
そのため、CPU の過負荷を防ぐにはできるだけ下層のディレクトリを監視する必要があります。
