Pythonで実装されたGoF Observerの例示的な例はありますか?現在、キークラスを介して組み込まれているデバッグコードのビットが含まれているビットコードがあります(現在、マジックenvが設定されている場合、stderrにメッセージを生成します)。さらに、このクラスには、結果を段階的に返すためのインターフェイスと、結果を(メモリに)格納して後処理するためのインターフェイスがあります。 (クラス自体は、sshを介してリモートマシンでコマンドを同時に実行するためのジョブマネージャです)。
現在、クラスの使用法は次のようになります。
_job = SSHJobMan(hostlist, cmd)
job.start()
while not job.done():
for each in job.poll():
incrementally_process(job.results[each])
time.sleep(0.2) # or other more useful work
post_process(job.results)
_
代替の使用モデルは次のとおりです。
_job = SSHJobMan(hostlist, cmd)
job.wait() # implicitly performs a start()
process(job.results)
_
これはすべて、現在のユーティリティでは正常に機能します。ただし、柔軟性に欠けます。たとえば、私は現在、簡単な出力形式または漸進的な結果としての進行状況バーをサポートしています。また、post_process()
関数の簡単で完全な「マージされたメッセージ」出力もサポートしています。
ただし、複数の結果/出力ストリーム(ターミナルへの進行状況バー、ログファイルへのデバッグと警告、成功したジョブから1つのファイル/ディレクトリへの出力、エラーメッセージ、および失敗したジョブから別の結果へのその他の結果)をサポートしたいと思います。 、など)。
これは、オブザーバーを呼び出す状況のように聞こえます...私のクラスのインスタンスに他のオブジェクトからの登録を受け入れさせ、発生したときに特定のタイプのイベントでコールバックします。
私は PyPubSub を参照しています。これは、SO関連する質問にいくつかの参照が含まれているためです。外部依存関係を自分に追加する準備ができているかどうかわかりませんユーティリティですが、他のユーザーがより簡単に使用できるようになれば、それらのインターフェイスを自分のモデルとして使用することに価値があると思います(このプロジェクトは、スタンドアロンのコマンドラインユーティリティと、他のスクリプト/ユーティリティを作成するためのクラスの両方として意図されています)。
要するに私は自分がやりたいことをする方法を知っています...しかし、それを達成するための多くの方法があります。長期的に見て、コードの他のユーザーにとって何が最も効果的であるかについての提案を求めています。
コード自体は、 classh にあります。
ただし、柔軟性に欠けます。
ええと...実際、非同期APIが必要な場合、これは私には良いデザインのように見えます。それは通常です。たぶん、必要なのはstderrからPythonの logging
モジュールに切り替えることだけです。これには、独自のパブリッシュ/サブスクライブモデルがあり、Logger.addHandler()
オン。
オブザーバーをサポートしたい場合は、シンプルにすることをお勧めします。ほんの数行のコードが必要です。
_class Event(object):
pass
class Observable(object):
def __init__(self):
self.callbacks = []
def subscribe(self, callback):
self.callbacks.append(callback)
def fire(self, **attrs):
e = Event()
e.source = self
for k, v in attrs.iteritems():
setattr(e, k, v)
for fn in self.callbacks:
fn(e)
_
JobクラスはObservable
をサブクラス化できます。興味のあることが起こったら、self.fire(type="progress", percent=50)
などを呼び出します。
他の答えの人々はそれをやり過ぎると思います。 15行未満のコードでPython=のイベントを簡単に実現できます。
単純に2つのクラスがあります:Event
とObserver
。イベントをリッスンするクラスは、オブザーバーを継承し、特定のイベントをリッスン(監視)するように設定する必要があります。 Event
がインスタンス化されて起動されると、そのイベントをリッスンするすべてのオブザーバーが指定されたコールバック関数を実行します。
class Observer():
_observers = []
def __init__(self):
self._observers.append(self)
self._observables = {}
def observe(self, event_name, callback):
self._observables[event_name] = callback
class Event():
def __init__(self, name, data, autofire = True):
self.name = name
self.data = data
if autofire:
self.fire()
def fire(self):
for observer in Observer._observers:
if self.name in observer._observables:
observer._observables[self.name](self.data)
例:
class Room(Observer):
def __init__(self):
print("Room is ready.")
Observer.__init__(self) # Observer's init needs to be called
def someone_arrived(self, who):
print(who + " has arrived!")
room = Room()
room.observe('someone arrived', room.someone_arrived)
Event('someone arrived', 'Lenard')
出力:
Room is ready.
Lenard has arrived!
さらにいくつかのアプローチ...
おそらく、stderrからPythonの logging
モジュールに切り替えるだけで、強力なパブリッシュ/サブスクライブモデルがあります。
ログレコードの作成を開始するのは簡単です。
_# producer
import logging
log = logging.getLogger("myjobs") # that's all the setup you need
class MyJob(object):
def run(self):
log.info("starting job")
n = 10
for i in range(n):
log.info("%.1f%% done" % (100.0 * i / n))
log.info("work complete")
_
消費者側ではもう少し仕事があります。残念ながら、ロガー出力を構成するには、7行のコードを実行する必要があります。 ;)
_# consumer
import myjobs, sys, logging
if user_wants_log_output:
ch = logging.StreamHandler(sys.stderr)
ch.setLevel(logging.INFO)
formatter = logging.Formatter(
"%(asctime)s - %(name)s - %(levelname)s - %(message)s")
ch.setFormatter(formatter)
myjobs.log.addHandler(ch)
myjobs.log.setLevel(logging.INFO)
myjobs.MyJob().run()
_
一方、loggingパッケージには驚くほど多くのものが含まれています。ログデータを一連のファイル、電子メールアドレス、およびWindowsイベントログに送信する必要がある場合は、問題ありません。
ただし、ライブラリを使用する必要はまったくありません。オブザーバーをサポートする非常に簡単な方法は、何もしないメソッドを呼び出すことです。
_# producer
class MyJob(object):
def on_progress(self, pct):
"""Called when progress is made. pct is the percent complete.
By default this does nothing. The user may override this method
or even just assign to it."""
pass
def run(self):
n = 10
for i in range(n):
self.on_progress(100.0 * i / n)
self.on_progress(100.0)
# consumer
import sys, myjobs
job = myjobs.MyJob()
job.on_progress = lambda pct: sys.stdout.write("%.1f%% done\n" % pct)
job.run()
_
ラムダを書く代わりに、_job.on_progress = progressBar.update
_と言うこともできます。これはいいことです。
これは非常に簡単です。 1つの欠点は、同じイベントにサブスクライブする複数のリスナーを自然にはサポートしないことです。
少しのサポートコードで、PythonでC#のようなイベントを取得できます。これがコードです:
_# glue code
class event(object):
def __init__(self, func):
self.__doc__ = func.__doc__
self._key = ' ' + func.__name__
def __get__(self, obj, cls):
try:
return obj.__dict__[self._key]
except KeyError, exc:
be = obj.__dict__[self._key] = boundevent()
return be
class boundevent(object):
def __init__(self):
self._fns = []
def __iadd__(self, fn):
self._fns.append(fn)
return self
def __isub__(self, fn):
self._fns.remove(fn)
return self
def __call__(self, *args, **kwargs):
for f in self._fns[:]:
f(*args, **kwargs)
_
プロデューサーは、デコレーターを使用してイベントを宣言します。
_# producer
class MyJob(object):
@event
def progress(pct):
"""Called when progress is made. pct is the percent complete."""
def run(self):
n = 10
for i in range(n+1):
self.progress(100.0 * i / n)
#consumer
import sys, myjobs
job = myjobs.MyJob()
job.progress += lambda pct: sys.stdout.write("%.1f%% done\n" % pct)
job.run()
_
これは上記の「単純なオブザーバー」コードとまったく同じように機能しますが、_+=
_を使用して好きなだけリスナーを追加できます。 (C#とは異なり、イベントハンドラータイプはなく、イベントをサブスクライブするときにnew EventHandler(foo.bar)
を実行する必要はありません。また、イベントを発生させる前にnullを確認する必要はありません。C#と同様に、イベントはスケルチ例外ではありません。)
logging
が必要なすべてを行う場合は、それを使用します。そうでなければ、あなたのために働く最も簡単なことをしてください。注意すべき重要な点は、大きな外部依存関係をとる必要がないことです。
オブジェクトが何かを監視しているという理由だけでオブジェクトが存続しない実装についてはどうですか?以下に、以下の機能を備えたオブザーバーパターンの実装を示します。
foo
のバインドされたメソッド_.bar
_にオブザーバーを追加するには、foo.bar.addObserver(observer)
を実行するだけです。コードは次のとおりです( githubパッケージ または PyPIパッケージ が最新の実装です):
_import weakref
import functools
class ObservableMethod(object):
"""
A proxy for a bound method which can be observed.
I behave like a bound method, but other bound methods can subscribe to be
called whenever I am called.
"""
def __init__(self, obj, func):
self.func = func
functools.update_wrapper(self, func)
self.objectWeakRef = weakref.ref(obj)
self.callbacks = {} #observing object ID -> weak ref, methodNames
def addObserver(self, boundMethod):
"""
Register a bound method to observe this ObservableMethod.
The observing method will be called whenever this ObservableMethod is
called, and with the same arguments and keyword arguments. If a
boundMethod has already been registered to as a callback, trying to add
it again does nothing. In other words, there is no way to sign up an
observer to be called back multiple times.
"""
obj = boundMethod.__self__
ID = id(obj)
if ID in self.callbacks:
s = self.callbacks[ID][1]
else:
wr = weakref.ref(obj, Cleanup(ID, self.callbacks))
s = set()
self.callbacks[ID] = (wr, s)
s.add(boundMethod.__name__)
def discardObserver(self, boundMethod):
"""
Un-register a bound method.
"""
obj = boundMethod.__self__
if id(obj) in self.callbacks:
self.callbacks[id(obj)][1].discard(boundMethod.__name__)
def __call__(self, *arg, **kw):
"""
Invoke the method which I proxy, and all of it's callbacks.
The callbacks are called with the same *args and **kw as the main
method.
"""
result = self.func(self.objectWeakRef(), *arg, **kw)
for ID in self.callbacks:
wr, methodNames = self.callbacks[ID]
obj = wr()
for methodName in methodNames:
getattr(obj, methodName)(*arg, **kw)
return result
@property
def __self__(self):
"""
Get a strong reference to the object owning this ObservableMethod
This is needed so that ObservableMethod instances can observe other
ObservableMethod instances.
"""
return self.objectWeakRef()
class ObservableMethodDescriptor(object):
def __init__(self, func):
"""
To each instance of the class using this descriptor, I associate an
ObservableMethod.
"""
self.instances = {} # Instance id -> (weak ref, Observablemethod)
self._func = func
def __get__(self, inst, cls):
if inst is None:
return self
ID = id(inst)
if ID in self.instances:
wr, om = self.instances[ID]
if not wr():
msg = "Object id %d should have been cleaned up"%(ID,)
raise RuntimeError(msg)
else:
wr = weakref.ref(inst, Cleanup(ID, self.instances))
om = ObservableMethod(inst, self._func)
self.instances[ID] = (wr, om)
return om
def __set__(self, inst, val):
raise RuntimeError("Assigning to ObservableMethod not supported")
def event(func):
return ObservableMethodDescriptor(func)
class Cleanup(object):
"""
I manage remove elements from a dict whenever I'm called.
Use me as a weakref.ref callback to remove an object's id from a dict
when that object is garbage collected.
"""
def __init__(self, key, d):
self.key = key
self.d = d
def __call__(self, wr):
del self.d[self.key]
_
これを使用するには、_@event
_で監視可能にするメソッドを装飾するだけです。ここに例があります
_class Foo(object):
def __init__(self, name):
self.name = name
@event
def bar(self):
print("%s called bar"%(self.name,))
def baz(self):
print("%s called baz"%(self.name,))
a = Foo('a')
b = Foo('b')
a.bar.addObserver(b.bar)
a.bar()
_
wikipedia から:
from collections import defaultdict
class Observable (defaultdict):
def __init__ (self):
defaultdict.__init__(self, object)
def emit (self, *args):
'''Pass parameters to all observers and update states.'''
for subscriber in self:
response = subscriber(*args)
self[subscriber] = response
def subscribe (self, subscriber):
'''Add a new subscriber to self.'''
self[subscriber]
def stat (self):
'''Return a Tuple containing the state of each observer.'''
return Tuple(self.values())
Observableは次のように使用されます。
myObservable = Observable ()
# subscribe some inlined functions.
# myObservable[lambda x, y: x * y] would also work here.
myObservable.subscribe(lambda x, y: x * y)
myObservable.subscribe(lambda x, y: float(x) / y)
myObservable.subscribe(lambda x, y: x + y)
myObservable.subscribe(lambda x, y: x - y)
# emit parameters to each observer
myObservable.emit(6, 2)
# get updated values
myObservable.stat() # returns: (8, 3.0, 4, 12)
Jasonの回答に基づいて、C#のようなイベントの例を本格的なpythonドキュメントとテストを含むモジュールとして実装しました。私は空想のPythonicが大好きです:)
したがって、すぐに使用できるソリューションが必要な場合は、 githubのコード を使用できます。
オブザーバーyourCallable()
(ディクショナリーを受け入れる呼び出し可能オブジェクト)を登録して、(他のオブザーバーに加えて)すべてのログイベントを受信するには:
twisted.python.log.addObserver(yourCallable)
Twisted-Pythonメーリングリストから:
#!/usr/bin/env python
"""Serve as a sample implementation of a twisted producer/consumer
system, with a simple TCP server which asks the user how many random
integers they want, and it sends the result set back to the user, one
result per line."""
import random
from zope.interface import implements
from twisted.internet import interfaces, reactor
from twisted.internet.protocol import Factory
from twisted.protocols.basic import LineReceiver
class Producer:
"""Send back the requested number of random integers to the client."""
implements(interfaces.IPushProducer)
def __init__(self, proto, cnt):
self._proto = proto
self._goal = cnt
self._produced = 0
self._paused = False
def pauseProducing(self):
"""When we've produced data too fast, pauseProducing() will be
called (reentrantly from within resumeProducing's transport.write
method, most likely), so set a flag that causes production to pause
temporarily."""
self._paused = True
print('pausing connection from %s' % (self._proto.transport.getPeer()))
def resumeProducing(self):
self._paused = False
while not self._paused and self._produced < self._goal:
next_int = random.randint(0, 10000)
self._proto.transport.write('%d\r\n' % (next_int))
self._produced += 1
if self._produced == self._goal:
self._proto.transport.unregisterProducer()
self._proto.transport.loseConnection()
def stopProducing(self):
pass
class ServeRandom(LineReceiver):
"""Serve up random data."""
def connectionMade(self):
print('connection made from %s' % (self.transport.getPeer()))
self.transport.write('how many random integers do you want?\r\n')
def lineReceived(self, line):
cnt = int(line.strip())
producer = Producer(self, cnt)
self.transport.registerProducer(producer, True)
producer.resumeProducing()
def connectionLost(self, reason):
print('connection lost from %s' % (self.transport.getPeer()))
factory = Factory()
factory.protocol = ServeRandom
reactor.listenTCP(1234, factory)
print('listening on 1234...')
reactor.run()
オブザーバー設計への機能的アプローチ:
def add_listener(obj, method_name, listener):
# Get any existing listeners
listener_attr = method_name + '_listeners'
listeners = getattr(obj, listener_attr, None)
# If this is the first listener, then set up the method wrapper
if not listeners:
listeners = [listener]
setattr(obj, listener_attr, listeners)
# Get the object's method
method = getattr(obj, method_name)
@wraps(method)
def method_wrapper(*args, **kwags):
method(*args, **kwags)
for l in listeners:
l(obj, *args, **kwags) # Listener also has object argument
# Replace the original method with the wrapper
setattr(obj, method_name, method_wrapper)
else:
# Event is already set up, so just add another listener
listeners.append(listener)
def remove_listener(obj, method_name, listener):
# Get any existing listeners
listener_attr = method_name + '_listeners'
listeners = getattr(obj, listener_attr, None)
if listeners:
# Remove the listener
next((listeners.pop(i)
for i, l in enumerate(listeners)
if l == listener),
None)
# If this was the last listener, then remove the method wrapper
if not listeners:
method = getattr(obj, method_name)
delattr(obj, listener_attr)
setattr(obj, method_name, method.__wrapped__)
これらのメソッドを使用して、任意のクラスメソッドにリスナーを追加できます。例えば:
class MyClass(object):
def __init__(self, prop):
self.prop = prop
def some_method(self, num, string):
print('method:', num, string)
def listener_method(obj, num, string):
print('listener:', num, string, obj.prop)
my = MyClass('my_prop')
add_listener(my, 'some_method', listener_method)
my.some_method(42, 'with listener')
remove_listener(my, 'some_method', listener_method)
my.some_method(42, 'without listener')
そして出力は:
method: 42 with listener
listener: 42 with listener my_prop
method: 42 without listener
OPは、「Pythonで実装されたGoF Observerの例示的な例はありますか?」と尋ねます。これはanの例Python 3.7です。このObservableクラスはone observableとmanyオブザーバー残り独立彼らの構造。
from functools import partial
from dataclasses import dataclass, field
import sys
from typing import List, Callable
@dataclass
class Observable:
observers: List[Callable] = field(default_factory=list)
def register(self, observer: Callable):
self.observers.append(observer)
def deregister(self, observer: Callable):
self.observers.remove(observer)
def notify(self, *args, **kwargs):
for observer in self.observers:
observer(*args, **kwargs)
def usage_demo():
observable = Observable()
# Register two anonymous observers using lambda.
observable.register(
lambda *args, **kwargs: print(f'Observer 1 called with args={args}, kwargs={kwargs}'))
observable.register(
lambda *args, **kwargs: print(f'Observer 2 called with args={args}, kwargs={kwargs}'))
# Create an observer function, register it, then deregister it.
def callable_3():
print('Observer 3 NOT called.')
observable.register(callable_3)
observable.deregister(callable_3)
# Create a general purpose observer function and register four observers.
def callable_x(*args, **kwargs):
print(f'{args[0]} observer called with args={args}, kwargs={kwargs}')
for gui_field in ['Form field 4', 'Form field 5', 'Form field 6', 'Form field 7']:
observable.register(partial(callable_x, gui_field))
observable.notify('test')
if __== '__main__':
sys.exit(usage_demo())