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

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

ただいまの
回答率

90.85%

  • Python 3.x

    4439questions

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

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

解決済

回答 4

投稿 編集

  • 評価
  • クリップ 1
  • VIEW 192

namuyan

score 28

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?

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

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

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

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

質問への追記・修正、ベストアンサー選択の依頼

  • KSwordOfHaste

    2018/04/30 21:37

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

    キャンセル

  • tachikoma

    2018/04/30 22:32

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

    キャンセル

回答 4

checkベストアンサー

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を知らないのでわかりません。

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

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

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

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

0

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

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

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

def hook(fn):
    def inner(*args, **kwargs):
        print(*args, *kwargs.items())
        return fn(*args, **kwargs)
    return inner

locals().__setitem__ = hook(locals().__setitem__)

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

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

from copy import deepcopy
from inspect import currentframe
from threading import Event
from threading import Thread
from threading import Lock


class LocalsWatcher(Thread):
    def __init__(self, frame):
        super().__init__()
        self.frame = frame
        self.targets = {}
        self.lock = Lock()
        self.stopping = Event()

    def add(self, name, callback):
        with self.lock:
            self.targets[name] = (
                deepcopy(self.frame.f_locals.get(name)), callback)

    def remove(self, name):
        with self.lock:
            self.targets.pop(name, None)

    def stop(self):
        self.stopping.set()

    def run(self):
        while not self.stopping.is_set():
            with self.lock:
                for name in self.targets:
                    original, callback = self.targets[name]
                    current = self.frame.f_locals.get(name)
                    if current != original:
                        callback(name, original, current)
                        self.targets[name] = (deepcopy(current), callback)

    def __enter__(self):
        self.start()
        return self

    def __exit__(self, *_):
        self.stop()


def main():
    with LocalsWatcher(currentframe()) as watcher:
        watcher.add("i", print)
        for i in range(10):
            pass


if __name__ == "__main__":
    main()

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

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

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

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/05/01 11:44 編集

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

    キャンセル

  • 2018/05/01 13:10

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

    キャンセル

  • 2018/05/01 13:18

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

    キャンセル

  • 2018/05/01 13:32

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

    キャンセル

  • 2018/05/01 13:41

    良かったです。:)

    キャンセル

0

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

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


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

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

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

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

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

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

0

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

こんな感じですかねぇ?

def dosomething():
    print("何かする")

class MyDict(dict):
    def __init__(self, *args, **kargs):
        super(MyDict, self).__init__(*args, **kargs)

    def __getitem__(self, key):
        dosomething()
        return super(MyDict, self).__getitem__(key)

    def __setitem__(self, key, val):
        dosomething()
        super(MyDict, self).__setitem__(key, val)

d = MyDict({10:10, 15:15})
print(d)
d[20] = 20
print(d[10])
print(d[20])
""" # =>
{10: 10, 15: 15}
何かする
何かする
10
何かする
20
"""

 追記

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

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

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

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

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

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/05/01 17:56 編集

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

    キャンセル

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

  • ただいまの回答率 90.85%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

関連した質問

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

  • Python 3.x

    4439questions

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