⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 pubsub.py

📁 Wxpython Implemented on Windows CE, Source code
💻 PY
📖 第 1 页 / 共 4 页
字号:

#---------------------------------------------------------------------------
"""
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 + -