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

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

ただいまの
回答率

90.12%

他のクラスに存在するメソッドを監視したい。監視対象が動いた時に処理を走らせたい。

受付中

回答 5

投稿 編集

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

sponge_yukari

score 10

こんにちわ。
どう考えても答えにたどり着けなかったので質問します。

 やりたいこと

言語はPython3.6です。
他のクラスに存在するメソッドが動いた時に、それを察知して別のクラスで別の動作を行うようにしたい。
具体的にはevent処理ですが、応用できる可能性があるのでソースには表記していません。

 ソース

class A() :
    ・・・
    def methodA(self) :
    """ここを感知したい"""
class B() :
    a = A()
    ・・・
class main() :
    b = B()
    def process(self) :
    """感知したときにこのメソッドで別の処理を動かす"""
    ・・・


具体例ではなく申し訳ありません。
init等入れてませんが、これで伝われば幸いです。

 条件

・Aのインスタンス化は必ずBを経由して、mainでBをインスタンス化する形を取ります。
・できればAもBも手をつけずに感知したいです。

 その他

スレッドで動かすことも考えましたが、負荷がかかると思いやっていません。
手段がなければそれでどうにかします。

以上です

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

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

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

    クリップを取り消します

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

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

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

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

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

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

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

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

    質問の評価を下げる

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

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

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

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

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

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

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

    詳細な説明はこちら

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

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

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

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

  • fuzzball

    2018/09/19 13:25 編集

    「宣言」って何でしょうか?インスタンスの生成ってことですか?あと、スレッドを使えば出来るアテがあるということでしょうか?その場合、methodAの実行はどうやって検知するつもりなのでしょうか?

    キャンセル

  • sponge_yukari

    2018/09/19 15:43

    申し訳ありません、別言語の名残で「宣言」と書いてました。Pythonではインスタンスの生成が正しいのですね。修正しておきます。 スレッドを使う場合、スレッドでクラス自体を監視して数値の変化があればそれでそのメソッドが使われたと判断する予定でした。Aのソースを簡略化しちゃったので肝心の数値は書いてませんね・・・。

    キャンセル

  • fuzzball

    2018/09/19 15:56

    ということは、メソッド(methodA)の監視ではなく変数の監視ということでしょうか?(その変数はmethodAでしか触らないということ?)

    キャンセル

  • sponge_yukari

    2018/09/19 16:10

    できることならメソッドの監視、できないのならそのメソッドで関係する変数の監視 と考えています。 そうですね、変数はmethodAでのみ変更します。

    キャンセル

回答 5

+3

典型的な「Observerパターン」で実装できます。

同じキーワードで検索すると優良記事がたくさんあるので参考にしてください。

適当に検索一位をはってみます。
https://qiita.com/nirperm/items/60ebc707bf35a836f637


考え方としては、あらかじめ連動する関数のポインタを獲得しておけば、自分が呼び出されるたびについでに連動すべき関数をコールする、というものです。


普通AやBでObserverとSubjectを継承すればよいです。
notifyを実装する必要がある気がしますが。

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

+2

Pythonはやっておらず、あまりdesign-patternにも詳しくはない ( 趣味でやっているので ) のですが、
確かデザインパターンのどれかに質問者さんがやりたいとおっしゃっている処理をサポートしている
ものがあった気がします。

たぶん、デザインパターン習得編 通りなら
Observerパターンだと思いますが。
それかMediatorパターン辺り。

これをPython向けの解説を探してみて...とかはどうでしょうか?

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

+2

ModelからViewに通知したいという要件として認識しました。

Eventを発火させる部分はデコレータでもう少し簡単に記述できそうですが。
こんな感じのコードでどうでしょうか。

# -*- coding: utf-8 -*-
from copy import deepcopy
from inspect import currentframe
from pprint import pformat


class EventHandler(object):
    def __init__(self):
        self._observers = []

    def append(self, listener):
        """
        Listenerの追加
        :param listener:
        :return:
        """
        assert listener is not None
        if listener not in self._observers:
            self._observers.append(listener)
        return self

    def remove(self, listener):
        """
        Listenerの削除
        :param listener:
        :return:
        """
        self._observers.remove(listener)
        return self

    def fire(self, sender: str, new_value):
        for callback in self._observers:
            callback(sender, new_value)

    def __iadd__(self, other):
        return self.append(other)

    def __isub__(self, other):
        return self.remove(other)


class Model(object):
    """
        モデル
    """
    def __init__(self):
        self.handler = EventHandler()
        self._TopMost = True
        self._ForeColor = "#000000"
        self._BackColor = "#000000"

    @property
    def top_most(self) -> bool:
        return self._TopMost

    @top_most.setter
    def top_most(self, value: bool) -> None:
        self._TopMost = value
        self.handler.fire(currentframe().f_code.co_name, value)

    @property
    def fore_color(self) -> str:
        return self._ForeColor

    @fore_color.setter
    def fore_color(self, value: str) -> None:
        self._ForeColor = value
        self.handler.fire(currentframe().f_code.co_name, value)

    @property
    def back_color(self) -> str:
        return self._BackColor

    @back_color.setter
    def back_color(self, value: str) -> None:
        self._BackColor = value
        self.handler.fire(currentframe().f_code.co_name, value)

    def __deepcopy__(self, memo):
        cls = self.__class__
        result = cls.__new__(cls)
        memo[id(self)] = result
        for k, v in self.__dict__.items():
            setattr(result, k, deepcopy(v, memo))
        return result

    def __repr__(self):
        return pformat(vars(self), indent=4, width=1)


def changed(sender, val):
    print(sender, val)


def main():
    """
        エントリーポイント
    """
    model = Model()
    model.handler += changed
    print(model)
    print("#" * 60)
    model.top_most = False
    model.fore_color = "#AAAAAA"
    print(model)

if __name__ == '__main__':
    main()

■参考情報

投稿

編集

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

+1

Aを継承したラップクラスを作って、methodAをオーバーライドして、その中でprocessを呼ぶ(実行する)ようにすれば出来るような出来ないような。(Bも同様にラップしたりする必要があるでしょう)

これで出来るならAとBそのものには手を加えなくて済みます。

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

+1

内部の実装によっては出来ない場合もあるかもしれませんが、Main から methodA を参照できるのであれば、methodA を関数でラップするのはどうでしょうか。つまり、デコレータを動的に適用します。

class A:
    def __init__(self):
        self.value = 0
    def methodA(self):
        self.value += 1
        print(f'methodA: {self.value}')

class B:
    def __init__(self):
        self.a = A()
    def exec(self):
        self.a.methodA()

class Main:
    def __init__(self):
        self.b = B()

        def wrap(func):
            def _wrap(*args, **kwargs):
                result = func(*args, **kwargs)
                self.process()
                return result
            return _wrap

        self.b.a.methodA = wrap(self.b.a.methodA)

    def process(self):
        print(f'process: {self.b.a.value}')

main = Main()
main.b.exec()

# methodA: 1
# process: 1

投稿

  • 回答の評価を上げる

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

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

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

  • 回答の評価を下げる

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

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

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

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

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

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