用于pygame中的MVC事件处理的Python duck-typing

我和朋友一直在玩pygame,并使用pygame遇到了 this tutorial for building games.我们真的很喜欢它如何将游戏分解为模型 – 视图 – 控制器系统,其中事件作为中间人,但代码大量使用事件系统的isinstance检查.

例:

class CPUSpinnerController:
    ...
    def Notify(self, event):
        if isinstance( event, QuitEvent ):
            self.keepGoing = 0

这导致一些非常非常规的代码.有没有人对如何改进这方面有任何建议?或者实施MVC的替代方法?

这是我根据@ Mark-Hildreth回答编写的一些代码(如何链接用户?)有没有其他人有任何好的建议?在选择解决方案之前,我将把这个开放一天左右.

class EventManager:
    def __init__(self):
        from weakref import WeakKeyDictionary
        self.listeners = WeakKeyDictionary()

    def add(self, listener):
        self.listeners[ listener ] = 1

    def remove(self, listener):
        del self.listeners[ listener ]

    def post(self, event):
        print "post event %s" % event.name
        for listener in self.listeners.keys():
            listener.notify(event)

class Listener:
    def __init__(self, event_mgr=None):
        if event_mgr is not None:
            event_mgr.add(self)

    def notify(self, event):
        event(self)


class Event:
    def __init__(self, name="Generic Event"):
        self.name = name

    def __call__(self, controller):
        pass

class QuitEvent(Event):
    def __init__(self):
        Event.__init__(self, "Quit")

    def __call__(self, listener):
        listener.exit(self)

class RunController(Listener):
    def __init__(self, event_mgr):
        Listener.__init__(self, event_mgr)
        self.running = True
        self.event_mgr = event_mgr

    def exit(self, event):
        print "exit called"
        self.running = False

    def run(self):
        print "run called"
        while self.running:
            event = QuitEvent()
            self.event_mgr.post(event)

em = EventManager()
run = RunController(em)
run.run()

这是使用@Paul中的示例的另一个构建 – 非常简单!

class WeakBoundMethod:
    def __init__(self, meth):
        import weakref
        self._self = weakref.ref(meth.__self__)
        self._func = meth.__func__

    def __call__(self, *args, **kwargs):
        self._func(self._self(), *args, **kwargs)

class EventManager:
    def __init__(self):
        # does this actually do anything?
        self._listeners = { None : [ None ] }

    def add(self, eventClass, listener):
        print "add %s" % eventClass.__name__
        key = eventClass.__name__

        if (hasattr(listener, '__self__') and
            hasattr(listener, '__func__')):
            listener = WeakBoundMethod(listener)

        try:
            self._listeners[key].append(listener)
        except KeyError:
            # why did you not need this in your code?
            self._listeners[key] = [listener]

        print "add count %s" % len(self._listeners[key])

    def remove(self, eventClass, listener):
        key = eventClass.__name__
        self._listeners[key].remove(listener)

    def post(self, event):
        eventClass = event.__class__
        key = eventClass.__name__
        print "post event %s (keys %s)" % (
            key, len(self._listeners[key]))
        for listener in self._listeners[key]:
            listener(event)

class Event:
    pass

class QuitEvent(Event):
    pass

class RunController:
    def __init__(self, event_mgr):
        event_mgr.add(QuitEvent, self.exit)
        self.running = True
        self.event_mgr = event_mgr

    def exit(self, event):
        print "exit called"
        self.running = False

    def run(self):
        print "run called"
        while self.running:
            event = QuitEvent()
            self.event_mgr.post(event)

em = EventManager()
run = RunController(em)
run.run()
处理事件的一种更简洁的方法(也更快,但可能消耗更多的内存)是在代码中有多个事件处理函数.这些方面的东西:

期望的界面

class KeyboardEvent:
    pass

class MouseEvent:
    pass

class NotifyThisClass:
    def __init__(self, event_dispatcher):
        self.ed = event_dispatcher
        self.ed.add(KeyboardEvent, self.on_keyboard_event)
        self.ed.add(MouseEvent, self.on_mouse_event)

    def __del__(self):
        self.ed.remove(KeyboardEvent, self.on_keyboard_event)
        self.ed.remove(MouseEvent, self.on_mouse_event)

    def on_keyboard_event(self, event):
        pass

    def on_mouse_event(self, event):
        pass

这里,__ init__方法接收EventDispatcher作为参数. EventDispatcher.add函数现在采用您感兴趣的事件类型和侦听器.

这有利于提高效率,因为只有调用者才会调用它感兴趣的事件.它还会在EventDispatcher本身内部产生更通用的代码:

EventDispatcher实现

class EventDispatcher:
    def __init__(self):
        # Dict that maps event types to lists of listeners
        self._listeners = dict()

    def add(self, eventcls, listener):
        self._listeners.setdefault(eventcls, list()).append(listener)

    def post(self, event):
        try:
            for listener in self._listeners[event.__class__]:
                listener(event)
        except KeyError:
            pass # No listener interested in this event

但是这个实现存在问题.在NotifyThisClass里面你这样做:

self.ed.add(KeyboardEvent, self.on_keyboard_event)

问题出在self.on_keyboard_event:它是一个绑定的方法,您传递给EventDispatcher.约束方法引用自我;这意味着只要EventDispatcher具有绑定方法,就不会删除self.

WeakBoundMethod

您将需要创建一个仅包含对self的弱引用的WeakBoundMethod类(我看到您已经知道弱引用),以便EventDispatcher不会阻止self的删除.

另一种方法是在删除对象之前调用NotifyThisClass.remove_listeners函数,但这并不是最干净的解决方案,而且我发现它非常容易出错(很容易忘记).

WeakBoundMethod的实现看起来像这样:

class WeakBoundMethod:
    def __init__(self, meth):
        self._self = weakref.ref(meth.__self__)
        self._func = meth.__func__

    def __call__(self, *args, **kwargs):
        self._func(self._self(), *args, **kwargs)

这是我在CodeReview上发布的a more robust implementation,这是一个如何使用该类的示例:

from weak_bound_method import WeakBoundMethod as Wbm

class NotifyThisClass:
    def __init__(self, event_dispatcher):
        self.ed = event_dispatcher
        self.ed.add(KeyboardEvent, Wbm(self.on_keyboard_event))
        self.ed.add(MouseEvent, Wbm(self.on_mouse_event))

连接对象(可选)

从管理器/调度程序中删除侦听器时,不是让EventDispatcher不必要地搜索侦听器,直到找到正确的事件类型,然后搜索列表直到找到正确的侦听器,您可以这样:

class NotifyThisClass:
    def __init__(self, event_dispatcher):
        self.ed = event_dispatcher
        self._connections = [
            self.ed.add(KeyboardEvent, Wbm(self.on_keyboard_event)),
            self.ed.add(MouseEvent, Wbm(self.on_mouse_event))
        ]

这里EventDispatcher.add返回一个Connection对象,该对象知道EventDispatcher的列表所在的位置.删除NotifyThisClass对象时,self._connections也将被调用,它将调用Connection .__ del__,它将从EventDispatcher中删除侦听器.

这可以使您的代码更快更容易使用,因为您只需要显式添加这些功能,它们会自动删除,但由您决定是否要执行此操作.如果这样做,请注意EventDispatcher.remove不再存在.

相关文章
相关标签/搜索