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只包含弱引用的类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不再存在.