質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
86.02%
Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Q&A

解決済

Pythonの変数の変化を検知してEventを起こしたい

namuyan
namuyan

総合スコア76

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

4回答

1グッド

1クリップ

14535閲覧

投稿2018/04/30 12:30

編集2018/04/30 13:56

Windows10 Python3.5 64bit

複数のスレッドで共通の変数を操作している時に、
dict, set, listやint, str, boolなどの変数の変更によりイベントを起こしたいです。

どなたか良さそうなライブラリを知りませんでしょうか?

有難うございます。どうやらobserverが目的とする動作を示す単語のようです。検索により目的を満たしそうなライブラリを発見できました。

追記

Observerについて検索した所、event-based programs というものらしくRxPY を発見する事ができました。しかし目的とは異なるやり方のようです。
変数を操作する側には、通常のBuildinのように扱わせて、on_changeを上書きすることなどにより変化を間接的に検知したいと考えています。例えば、KivyのWidgetの変数はint, str, listなどですが操作性が通常のBuildinと同様にもかかわらずユーザー側で変化させると自動的に描写を変化せてくれます。

調べていたら目的にほぼ合致するQ&Aを発見しました。
how-to-trigger-function-on-value-change?

YouheiSakurai👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもマイページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

下記のような質問は推奨されていません。

  • 質問になっていない投稿
  • スパムや攻撃的な表現を用いた投稿

適切な質問に修正を依頼しましょう。

KSwordOfHaste

2018/04/30 12:37

「python observable」など調べてみたことを明記すべきと思います。
tachikoma

2018/04/30 13:32

PyQtにそんなイベントトリガー仕込むAPIがあった気がしなくもない。

回答4

0

例えば、KivyのWidgetの変数はint, str, listなどですが操作性が通常のBuildinと同様にもかかわらずユーザー側で変化させると自動的に描写を変化せてくれます。

こんな感じですかねぇ?

python

1def dosomething(): 2 print("何かする") 3 4class MyDict(dict): 5 def __init__(self, *args, **kargs): 6 super(MyDict, self).__init__(*args, **kargs) 7 8 def __getitem__(self, key): 9 dosomething() 10 return super(MyDict, self).__getitem__(key) 11 12 def __setitem__(self, key, val): 13 dosomething() 14 super(MyDict, self).__setitem__(key, val) 15 16d = MyDict({10:10, 15:15}) 17print(d) 18d[20] = 20 19print(d[10]) 20print(d[20]) 21""" # => 22{10: 10, 15: 15} 23何かする 24何かする 2510 26何かする 2720 28"""

追記

これだけじゃ役に立たないので、この方法を使うならオーバーライドするべきメソッドの一覧を示しておきます。変化した場合だけで、dictの場合。もしかしたら漏れ、間違いがあるかもしれない(保証はしません。自分でも調べて・・・)。

  • __init__ 必要なら
  • __setitem__
  • __delitem__
  • clear
  • pop
  • popitem
  • setdefault
  • update

投稿2018/05/01 08:42

編集2018/05/01 09:31
hayataka2049

総合スコア30902

下記のような回答は推奨されていません。

  • 質問の回答になっていない投稿
  • スパムや攻撃的な表現を用いた投稿

このような回答には修正を依頼しましょう。

回答へのコメント

hayataka2049

2018/05/01 08:56 編集

あ、mkgreiさんのリンク先とほぼ同じだった。しかも、そっちの方がsuper()とか使うよりスマートな気もします・・・(珍しくpythonのself非省略がちゃんと役に立ってるのを見た気がする)

0

https://stackoverflow.com/questions/21510223/python-3-hook-list-dict-changes

自作クラスでObserverパターンに書き換えるのが妥当であると思います。


https://qiita.com/monoquro/items/ff1f862eb37ee2d8c389

書き換えるのはいろいろと考えないといけないので。
それでもというのであれば止めませんが。

投稿2018/04/30 23:09

mkgrei

総合スコア8556

下記のような回答は推奨されていません。

  • 質問の回答になっていない投稿
  • スパムや攻撃的な表現を用いた投稿

このような回答には修正を依頼しましょう。

0

「int, str, bool」を格納している変数の変化を検知するためには、locals()で得られるdictオブジェクトを監視することになると思います。

python

1print("i" in locals()) # iが未定義だとFalseが返る 2i = 0 3print("i" in locals()) # iが定義されるとTrueが返る 4print(locals()["i"]) # iの現在の値、0が返る 5i = 1 6print(locals()["i"]) # iの現在の値、1が返る

でも、以下のように素直にオブジェクトの変更をフックしようとしてもAttributeError: 'dict' object attribute '__setitem__' is read-onlyとなってしまうので、たぶん監視用のスレッドを別立てして変更を検知するのがいいんじゃないかと思います。

python

1def hook(fn): 2 def inner(*args, **kwargs): 3 print(*args, *kwargs.items()) 4 return fn(*args, **kwargs) 5 return inner 6 7locals().__setitem__ = hook(locals().__setitem__)

追記:ビジーループでwatcherを書いてみた

locals()が返す辞書を監視してもlocals()を呼ばないと辞書の内容が更新されないということがわかったので、最終的にフレームを監視することにしました。でも、ビジーループで監視しているので、CPU使用率が上がってしまう悪い例です。その点は留意ください。

python

1from copy import deepcopy 2from inspect import currentframe 3from threading import Event 4from threading import Thread 5from threading import Lock 6 7 8class LocalsWatcher(Thread): 9 def __init__(self, frame): 10 super().__init__() 11 self.frame = frame 12 self.targets = {} 13 self.lock = Lock() 14 self.stopping = Event() 15 16 def add(self, name, callback): 17 with self.lock: 18 self.targets[name] = ( 19 deepcopy(self.frame.f_locals.get(name)), callback) 20 21 def remove(self, name): 22 with self.lock: 23 self.targets.pop(name, None) 24 25 def stop(self): 26 self.stopping.set() 27 28 def run(self): 29 while not self.stopping.is_set(): 30 with self.lock: 31 for name in self.targets: 32 original, callback = self.targets[name] 33 current = self.frame.f_locals.get(name) 34 if current != original: 35 callback(name, original, current) 36 self.targets[name] = (deepcopy(current), callback) 37 38 def __enter__(self): 39 self.start() 40 return self 41 42 def __exit__(self, *_): 43 self.stop() 44 45 46def main(): 47 with LocalsWatcher(currentframe()) as watcher: 48 watcher.add("i", print) 49 for i in range(10): 50 pass 51 52 53if __name__ == "__main__": 54 main()

投稿2018/04/30 20:01

編集2018/05/01 04:14
YouheiSakurai

総合スコア6137

下記のような回答は推奨されていません。

  • 質問の回答になっていない投稿
  • スパムや攻撃的な表現を用いた投稿

このような回答には修正を依頼しましょう。

回答へのコメント

KSwordOfHaste

2018/05/01 02:44 編集

locals()は現在実行している関数のローカル変数の名前空間ですがそれは関数の実行が開始される度に生成されるものなのでローカル変数の更新を監視するなら関数が起動される度にhookしなくてはならないと思います。挙げておられる例はモジュールのトップレベルなのでlocals()もglobals()も典型的に同じdictになりますが、locals()/globals()の元々の意味合いは違うものと思います。
YouheiSakurai

2018/05/01 04:10

locals()で生成した辞書の更新タイミング/トリガについて、私自身が回答した時点で知らないことがありましたが、書いてみてもちゃんと書ききれたので、特におっしゃられている点で違うところはないです。追記分のコードを参照してください。
KSwordOfHaste

2018/05/01 04:18

なるほど、watcher.addを呼び出した時点でのコンテキスト内にあるローカル変数を監視する仕組みが書けますよということですね。よくわかりました。
KSwordOfHaste

2018/05/01 04:32

挙げておられるコードは興味深かったです。別スレッドで変数の変化を監視してますが、「変更する度に逐次コールバックをトリガーする」のではなく「適当なタイミングで最新の値が変わっていたらトリガーする」となっているので、頻繁に変数を変更するような場合でも余計なトリガーが発生しないのでUIにうまく使うとおもしろいことができそうに思いました!

0

ベストアンサー

追記:自分の回答はオブジェクトの属性(インスタンス変数・クラス変数など)」を代入によって更新したことを監視する場合について述べてます。dict, listなどmutableなオブジェクトの値の変化(要素を追加したり書き換えたりしたときの変化)は自分のコメントで書いた方法の対象外です。つまり

instance.int_attribute = 1は監視できますが
instance.dict_attribute['key'] = 1は監視できません。

これを監視するならmkgreiさん回答にある方法で行う必要があると思います。


質問者さんがお望みの機能がどういう感じのものかはっきりわかっていないので外してるかもしれませんが

KivyのWidgetの変数はint, str, listなどですが操作性が通常のBuildinと同様にもかかわらずユーザー側で変化させると自動的に描写を変化せてくれます

これはint, str, list自身に監視可能な機能がついているのではなく、widgetの属性を変更する(setattr(widget, '属性名'))際にfookを掛けているような仕組みではないのかなぁと想像してます(詳細を知らずに想像でのコメントです)

一例ですが__setattr__を定義しておくと任意の属性にアクセスしたときの振る舞いを制御できます。

In [1]: class A: ...: def __setattr__(self, name, value): ...: print(f'{name} <= {value}') ...: super().__setattr__(name, value) ...: In [2]: a = A() In [3]: a.a = 1 a <= 1

上記のprint(...)の部分を例えばself.on_change(name, value)といった呼び出しに変更しておくと、on_changeという属性(メソッド)に適当な処理を書いておけば属性が変更される度に「何かする」ことができそうです。__setattr__に「特定の属性だけ監視」などの工夫を入れ込むのもPythonなら手軽にできそうですね。

この程度のものでよいのでしたらRxPYでちょっと仕様が気に入らないという場合に最小限のフックを定義できるようなものが自前でも定義できそうな気もします。(それを実装済みのライブラリーが使えれば一番いいのはもちろんですが・・・)

またtachikomaさんがコメントしておられるPyQtの機能がこうしたものに似たものか違うものかはスミマセンがPyQtを知らないのでわかりません。

投稿2018/04/30 14:06

編集2018/05/01 02:38
KSwordOfHaste

総合スコア18380

下記のような回答は推奨されていません。

  • 質問の回答になっていない投稿
  • スパムや攻撃的な表現を用いた投稿

このような回答には修正を依頼しましょう。

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
86.02%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

同じタグがついた質問を見る

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。