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

Pet*_*org 7 python model-view-controller pygame duck-typing isinstance

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

例:

class CPUSpinnerController:
    ...
    def Notify(self, event):
        if isinstance( event, QuitEvent ):
            self.keepGoing = 0
Run Code Online (Sandbox Code Playgroud)

这导致一些非常非常规的代码.有没有人对如何改进这方面有任何建议?或者实施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()
Run Code Online (Sandbox Code Playgroud)

这是使用@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()
Run Code Online (Sandbox Code Playgroud)

Pau*_*nta 12

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

期望的界面

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
Run Code Online (Sandbox Code Playgroud)

这里,该__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
Run Code Online (Sandbox Code Playgroud)

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

self.ed.add(KeyboardEvent, self.on_keyboard_event)
Run Code Online (Sandbox Code Playgroud)

问题在于self.on_keyboard_event:它是一个绑定的方法,你传递给EventDispatcher.绑定方法引用self; 这意味着只要EventDispatcher拥有绑定方法,self就不会被删除.

WeakBoundMethod

您将需要创建一个WeakBoundMethod只包含弱引用的类self(我看到您已经知道弱引用),这样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)
Run Code Online (Sandbox Code Playgroud)

这是我在CodeReview上发布的更强大的实现,这是一个如何使用该类的示例:

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))
Run Code Online (Sandbox Code Playgroud)

Connection 对象(可选)

从管理器/调度程序中删除侦听器时,不是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))
        ]
Run Code Online (Sandbox Code Playgroud)

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

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