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?
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2018/04/30 13:32

回答4件
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総合スコア18404
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
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総合スコア30939
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。

0
https://stackoverflow.com/questions/21510223/python-3-hook-list-dict-changes
自作クラスでObserverパターンに書き換えるのが妥当であると思います。
https://qiita.com/monoquro/items/ff1f862eb37ee2d8c389
書き換えるのはいろいろと考えないといけないので。
それでもというのであれば止めませんが。
投稿2018/04/30 23:09
総合スコア8562
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
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総合スコア6149
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2018/05/01 02:44 編集
2018/05/01 04:18
2018/05/01 04:32

あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。