📄 pubsub.py
字号:
#---------------------------------------------------------------------------
"""
This module provides a publish-subscribe component that allows
listeners to subcribe to messages of a given topic. Contrary to the
original wxPython.lib.pubsub module (which it is based on), it uses
weak referencing to the subscribers so the lifetime of subscribers
is not affected by Publisher. Also, callable objects can be used in
addition to functions and bound methods. See Publisher class docs for
more details.
Thanks to Robb Shecter and Robin Dunn for having provided
the basis for this module (which now shares most of the concepts but
very little design or implementation with the original
wxPython.lib.pubsub).
The publisher is a singleton instance of the PublisherClass class. You
access the instance via the Publisher object available from the module::
from wx.lib.pubsub import Publisher
Publisher().subscribe(...)
Publisher().sendMessage(...)
...
:Author: Oliver Schoenborn
:Since: Apr 2004
:Version: $Id: pubsub.py,v 1.8 2006/06/11 00:12:59 RD Exp $
:Copyright: \(c) 2004 Oliver Schoenborn
:License: wxWidgets
"""
_implNotes = """
Implementation notes
--------------------
In class Publisher, I represent the topics-listener set as a tree
where each node is a topic, and contains a list of listeners of that
topic, and a dictionary of subtopics of that topic. When the Publisher
is told to send a message for a given topic, it traverses the tree
down to the topic for which a message is being generated, all
listeners on the way get sent the message.
Publisher currently uses a weak listener topic tree to store the
topics for each listener, and if a listener dies before being
unsubscribed, the tree is notified, and the tree eliminates the
listener from itself.
Ideally, _TopicTreeNode would be a generic _TreeNode with named
subnodes, and _TopicTreeRoot would be a generic _Tree with named
nodes, and Publisher would store listeners in each node and a topic
tuple would be converted to a path in the tree. This would lead to a
much cleaner separation of concerns. But time is over, time to move on.
"""
#---------------------------------------------------------------------------
# for function and method parameter counting:
from types import InstanceType
from inspect import getargspec, ismethod, isfunction
# for weakly bound methods:
from new import instancemethod as InstanceMethod
from weakref import ref as WeakRef
# -----------------------------------------------------------------------------
def _isbound(method):
"""Return true if method is a bound method, false otherwise"""
assert ismethod(method)
return method.im_self is not None
def _paramMinCountFunc(function):
"""Given a function, return pair (min,d) where min is minimum # of
args required, and d is number of default arguments."""
assert isfunction(function)
(args, va, kwa, dflt) = getargspec(function)
lenDef = len(dflt or ())
lenArgs = len(args or ())
lenVA = int(va is not None)
return (lenArgs - lenDef + lenVA, lenDef)
def _paramMinCount(callableObject):
"""
Given a callable object (function, method or callable instance),
return pair (min,d) where min is minimum # of args required, and d
is number of default arguments. The 'self' parameter, in the case
of methods, is not counted.
"""
if type(callableObject) is InstanceType:
min, d = _paramMinCountFunc(callableObject.__call__.im_func)
return min-1, d
elif ismethod(callableObject):
min, d = _paramMinCountFunc(callableObject.im_func)
return min-1, d
elif isfunction(callableObject):
return _paramMinCountFunc(callableObject)
else:
raise 'Cannot determine type of callable: '+repr(callableObject)
def _tupleize(items):
"""Convert items to tuple if not already one,
so items must be a list, tuple or non-sequence"""
if isinstance(items, list):
raise TypeError, 'Not allowed to tuple-ize a list'
elif isinstance(items, (str, unicode)) and items.find('.') != -1:
items = tuple(items.split('.'))
elif not isinstance(items, tuple):
items = (items,)
return items
def _getCallableName(callable):
"""Get name for a callable, ie function, bound
method or callable instance"""
if ismethod(callable):
return '%s.%s ' % (callable.im_self, callable.im_func.func_name)
elif isfunction(callable):
return '%s ' % callable.__name__
else:
return '%s ' % callable
def _removeItem(item, fromList):
"""Attempt to remove item from fromList, return true
if successful, false otherwise."""
try:
fromList.remove(item)
return True
except ValueError:
return False
# -----------------------------------------------------------------------------
class _WeakMethod:
"""Represent a weak bound method, i.e. a method doesn't keep alive the
object that it is bound to. It uses WeakRef which, used on its own,
produces weak methods that are dead on creation, not very useful.
Typically, you will use the getRef() function instead of using
this class directly. """
def __init__(self, method, notifyDead = None):
"""The method must be bound. notifyDead will be called when
object that method is bound to dies. """
assert ismethod(method)
if method.im_self is None:
raise ValueError, "We need a bound method!"
if notifyDead is None:
self.objRef = WeakRef(method.im_self)
else:
self.objRef = WeakRef(method.im_self, notifyDead)
self.fun = method.im_func
self.cls = method.im_class
def __call__(self):
"""Returns a new.instancemethod if object for method still alive.
Otherwise return None. Note that instancemethod causes a
strong reference to object to be created, so shouldn't save
the return value of this call. Note also that this __call__
is required only for compatibility with WeakRef.ref(), otherwise
there would be more efficient ways of providing this functionality."""
if self.objRef() is None:
return None
else:
return InstanceMethod(self.fun, self.objRef(), self.cls)
def __eq__(self, method2):
"""Two WeakMethod objects compare equal if they refer to the same method
of the same instance. Thanks to Josiah Carlson for patch and clarifications
on how dict uses eq/cmp and hashing. """
if not isinstance(method2, _WeakMethod):
return False
return self.fun is method2.fun \
and self.objRef() is method2.objRef() \
and self.objRef() is not None
def __hash__(self):
"""Hash is an optimization for dict searches, it need not
return different numbers for every different object. Some objects
are not hashable (eg objects of classes derived from dict) so no
hash(objRef()) in there, and hash(self.cls) would only be useful
in the rare case where instance method was rebound. """
return hash(self.fun)
def __repr__(self):
dead = ''
if self.objRef() is None:
dead = '; DEAD'
obj = '<%s at %s%s>' % (self.__class__, id(self), dead)
return obj
def refs(self, weakRef):
"""Return true if we are storing same object referred to by weakRef."""
return self.objRef == weakRef
def _getWeakRef(obj, notifyDead=None):
"""Get a weak reference to obj. If obj is a bound method, a _WeakMethod
object, that behaves like a WeakRef, is returned, if it is
anything else a WeakRef is returned. If obj is an unbound method,
a ValueError will be raised."""
if ismethod(obj):
createRef = _WeakMethod
else:
createRef = WeakRef
if notifyDead is None:
return createRef(obj)
else:
return createRef(obj, notifyDead)
# -----------------------------------------------------------------------------
def getStrAllTopics():
"""Function to call if, for whatever reason, you need to know
explicitely what is the string to use to indicate 'all topics'."""
return ''
# alias, easier to see where used
ALL_TOPICS = getStrAllTopics()
# -----------------------------------------------------------------------------
class _NodeCallback:
"""Encapsulate a weak reference to a method of a TopicTreeNode
in such a way that the method can be called, if the node is
still alive, but the callback does not *keep* the node alive.
Also, define two methods, preNotify() and noNotify(), which can
be redefined to something else, very useful for testing.
"""
def __init__(self, obj):
self.objRef = _getWeakRef(obj)
def __call__(self, weakCB):
notify = self.objRef()
if notify is not None:
self.preNotify(weakCB)
notify(weakCB)
else:
self.noNotify()
def preNotify(self, dead):
"""'Gets called just before our callback (self.objRef) is called"""
pass
def noNotify(self):
"""Gets called if the TopicTreeNode for this callback is dead"""
pass
class _TopicTreeNode:
"""A node in the topic tree. This contains a list of callables
that are interested in the topic that this node is associated
with, and contains a dictionary of subtopics, whose associated
values are other _TopicTreeNodes. The topic of a node is not stored
in the node, so that the tree can be implemented as a dictionary
rather than a list, for ease of use (and, likely, performance).
Note that it uses _NodeCallback to encapsulate a callback for
when a registered listener dies, possible thanks to WeakRef.
Whenever this callback is called, the onDeadListener() function,
passed in at construction time, is called (unless it is None).
"""
def __init__(self, topicPath, onDeadListenerWeakCB):
self.__subtopics = {}
self.__callables = []
self.__topicPath = topicPath
self.__onDeadListenerWeakCB = onDeadListenerWeakCB
def getPathname(self):
"""The complete node path to us, ie., the topic tuple that would lead to us"""
return self.__topicPath
def createSubtopic(self, subtopic, topicPath):
"""Create a child node for subtopic"""
return self.__subtopics.setdefault(subtopic,
_TopicTreeNode(topicPath, self.__onDeadListenerWeakCB))
def hasSubtopic(self, subtopic):
"""Return true only if topic string is one of subtopics of this node"""
return self.__subtopics.has_key(subtopic)
def getNode(self, subtopic):
"""Return ref to node associated with subtopic"""
return self.__subtopics[subtopic]
def addCallable(self, callable):
"""Add a callable to list of callables for this topic node"""
try:
id = self.__callables.index(_getWeakRef(callable))
return self.__callables[id]
except ValueError:
wrCall = _getWeakRef(callable, _NodeCallback(self.__notifyDead))
self.__callables.append(wrCall)
return wrCall
def getCallables(self):
"""Get callables associated with this topic node"""
return [cb() for cb in self.__callables if cb() is not None]
def hasCallable(self, callable):
"""Return true if callable in this node"""
try:
self.__callables.index(_getWeakRef(callable))
return True
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -