Python實(shí)現(xiàn)Event回調(diào)機(jī)制的方法
0.背景
在游戲的UI中,往往會(huì)出現(xiàn)這樣的情況:
在某個(gè)戰(zhàn)斗副本中獲得了某個(gè)道具A,那么當(dāng)進(jìn)入主界面的時(shí)候,你會(huì)看到你的背包UI上有個(gè)小紅點(diǎn)(意思是有新道具),點(diǎn)擊進(jìn)入背包后,發(fā)現(xiàn)新增了道具A,顯示個(gè)數(shù)為1,并且在下個(gè)界面中有個(gè)使用的按鈕由灰色不可使用變成橙色的可使用狀態(tài)

圖1. 事件觸發(fā)說(shuō)明圖
其中這里是由道具獲得這個(gè)事件,觸發(fā)了上述的三個(gè)行為。如果使用顯示調(diào)用行為,會(huì)使得代碼難擴(kuò)展,易出錯(cuò),邏輯混亂等問(wèn)題,如果使用Event回調(diào)機(jī)制,就會(huì)變得十分方便。
其實(shí)Event回調(diào)機(jī)制就是觀察者模式,如下圖:

圖2. 觀察者模式
在C#中存在(delegate & event)的語(yǔ)義來(lái)實(shí)現(xiàn)Event回調(diào)機(jī)制:具體使用如下:
public delegate void NewToolGotEvent();
public class ToolBag
{
event NewToolGotEvent newToolGotHandler;
void Start()
{
newToolGotHandler += renderRedPoint;
newToolGotHandler += renderNewTool;
newToolGotHandler += renderAvaliableUseBtn;
}
void renderRedPoint()
{
//TODO
}
void renderNewTool()
{
//TODO
}
void renderAvaliableUseBtn()
{
//TODO
}
void EventHappened()
{
newToolGotHandler(); // usage, fill args if necessary
}
}
如果在Python,可以在注冊(cè)事件的回調(diào)時(shí),帶入一個(gè)參數(shù)callback,在注冊(cè)函數(shù)實(shí)體內(nèi),存在一個(gè)list將callback添加進(jìn)去,形如:
def register_callback(self, cb): self.callbacks.append(cb)
但是這樣是一個(gè)最為普遍的做法,既然是Python,這里我們有更Pythonic的做法,而且相比于上述的觀察者模式,它的做法更加簡(jiǎn)潔,使用更加方便,接下來(lái)我們來(lái)解析一下Python實(shí)現(xiàn)Event callback的步驟。
1. UML類圖
上述案例中,是針對(duì)游戲客戶端UI的案例。所以我們呈現(xiàn)出的UML圖也是與UI相關(guān)。如圖3所示,它顯示了Python中實(shí)現(xiàn)Event回調(diào)的機(jī)制。

圖3. UML關(guān)系圖
如上圖所示,此機(jī)制主要由三個(gè)類及他們的實(shí)例(instance)組成:UIBase, UIScene, UIDataEvent。
1 . UIBase: 所有UIScene的基類,其實(shí)例有scene_id變量,包含兩個(gè)必要的方法, __init__ 是初始化方法,init_data_listeners方法是將實(shí)例中的某些方法, 例如ui_updata_func中包含的UIDataEvent實(shí)例(所有的UIDataEvent實(shí)例都是單例)遍歷,并把ui_update_func注冊(cè)在每一個(gè)UIDataEvent實(shí)例中。
2 . UIScene: 場(chǎng)景類,管理某個(gè)場(chǎng)景的UI渲染。在其實(shí)例中,存在某些方法,例如ui_update_func需要在某些UIDataEvent實(shí)例觸發(fā)時(shí)候,也被同時(shí)觸發(fā)調(diào)用。ui_update_func在Python中一個(gè)bound method object, 它會(huì)擁有一個(gè)特殊的屬性events,即所有需要觸發(fā)此方法的UIDataEvent實(shí)例集合。這個(gè)通過(guò)裝飾器(decorator)來(lái)實(shí)現(xiàn),即圖中的:
“ui_update_func” is a Python object which add a amount of UIDataEvent instances by Python decorator named “data_listener”
3 . UIDataEvent: 事件類,該類有個(gè)類變量_events, 記錄了所有的UIDataEvent實(shí)例,每一個(gè)UIDataEvent實(shí)例都是單例,而且都有一個(gè)名字,和一個(gè)回調(diào)方法集合_callbacks, 里面的每一個(gè)方法都是在本事件觸發(fā)后需要回調(diào)的方法。實(shí)例還有個(gè)__iadd__方法,將需要回調(diào)的函數(shù)cb注冊(cè)進(jìn)去。__call__事件觸發(fā)是實(shí)際觸發(fā)的函數(shù)。
2. 代碼
上一步講述了三個(gè)類之間的聯(lián)系與各自的作用,此步展示代碼實(shí)現(xiàn)相關(guān)功能。
a) UIBase.py
首先列出來(lái)的是UIBase的類,除了上述的__init__與init_data_listeners方法,還多了destroy方法
# -*- coding: utf-8 -*-
from UIDataNotifier import UIDataEvent
import inspect
class UIBase(object):
def __init__(self, in_scene_id):
self.id = in_scene_id
self.init_data_listeners()
def init_data_listeners(self):
"""為所有標(biāo)有@data_listener的成員函數(shù)注冊(cè)事件監(jiān)聽(tīng)器"""
for listener_name, listener in inspect.getmembers(self, lambda f: hasattr(f, 'events')):
for event in listener.events:
event += listener
def destroy(self):
print '%s.destroy' % self.__class__.__name__
UIDataEvent.clear()
init_data_listener比較難理解,我們看一下built-in的inspect.getmembers的源碼:
def getmembers(object, predicate=None): """Return all members of an object as (name, value) pairs sorted by name. Optionally, only return members that satisfy a given predicate.""" results = [] for key in dir(object): try: value = getattr(object, key) except AttributeError: continue if not predicate or predicate(value): results.append((key, value)) results.sort() return results
其實(shí)源碼的意思就是,在dir(object)的value中找,找到能夠滿足predicate(value) == True的value,然后將(key, value)收集,進(jìn)行排序后返回。
放在代碼的意思是:
for listener_name, listener in inspect.getmembers(self, lambda f: hasattr(f, 'events')):
for event in listener.events:
event += listener
在dir(scene)中找,找到value中存在名叫events的屬性, 返回得到是一個(gè)list,每個(gè)list的元素是一個(gè)二元tuple: (key, value),其中key,即listener_name是dir(scene)的屬性名,而value, 即listener就是屬性對(duì)象,這里其實(shí)就是包含事件的函數(shù)對(duì)象,然后遍歷listener中的每一個(gè)UIDataEvent實(shí)例,并將listener注冊(cè)到event中(+= ==> __iadd__ )
b) UIScene.py
UIScene的代碼如下:
# -*- coding: utf-8 -*- from UIDataNotifier import * from UIBase import UIBase class UIScene(UIBase): def __init__(self, in_scene_id): super(UIScene, self).__init__(in_scene_id) @data_listener(OnItemAdded) def ui_render_red_point(self, item): print 'ui_render_red_point' @data_listener(OnItemAdded) def ui_render_new_tool(self, item): print 'ui_render_new_tool: ' + item @data_listener(OnItemAdded) def ui_render_avaliable_use_btn(self, item): print 'ui_render_avaliable_use_btn' bag_ui_scene = UIScene(123)
在UIScene中只是要填寫(xiě)對(duì)于OnItemAdded這個(gè)事件觸發(fā)之后,需要回調(diào)的函數(shù),上述代碼中寫(xiě)了三個(gè)函數(shù)。注意需要在函數(shù)上加上裝飾器@data_listener(OnItemAdded),這樣此函數(shù)就會(huì)添加一個(gè)特殊的屬性events,具體裝飾器的代碼見(jiàn)UIDataNotifier.py。
最后新建一個(gè)bag_ui_scene的scene。
c) UIDataNotifier.py
UIDataNotifier.py代碼如下:
# -*- coding: utf-8 -*-
import sys
def data_listener(*events):
def wrapped_f(f):
f.events = events
return f
return wrapped_f
class UIDataEvent(object):
_events = []
def __init__(self, name):
self._name = name
self._callbacks = []
UIDataEvent._events.append(self)
def __iadd__(self, cb):
self._callbacks.append(cb)
return self
def __call__(self, *args, **kwargs):
for cb in self._callbacks:
try:
cb(*args, **kwargs)
except:
ex = sys.exc_info()
print "UIDataNotifier cb error, function:", cb.__name__, ex
def __repr__(self):
return 'UIDataEvent %s' % self._name
@classmethod
def clear(cls):
"""清空所有事件上的所有監(jiān)聽(tīng)器,在銷毀一個(gè)界面的時(shí)候調(diào)用"""
for event in cls._events:
event._cb = []
OnItemAdded = UIDataEvent('OnItemAdded')
data_listener裝飾器其實(shí)就是聲明一個(gè)特殊的events屬性,并將所有在UIScene中填寫(xiě)的UIDataEvent實(shí)例元組集合賦值給它。
__iadd__是將參數(shù)cb添加到實(shí)例的變量中_callbacks中,此方法在UIBase的init_data_listeners中使用。
__call__是當(dāng)UIDataEvent實(shí)例自調(diào)用時(shí),例如OnItemAdded(item),實(shí)際調(diào)用的函數(shù),在函數(shù)體里,會(huì)回調(diào)_callbacks中的每個(gè)方法,這也就是Event回調(diào)機(jī)制的核心部分,相當(dāng)于觀察者模式的notify方法
最后新建一個(gè)OnItemAdded事件。
c) client.py
創(chuàng)建上述幾個(gè)類之后,使用Event回調(diào)就非常簡(jiǎn)單了,代碼如下:
# -*- coding: utf-8 -*-
from UIScene import UIScene
from UIDataNotifier import *
OnItemAdded('liu_xin_biao') #新道具流星鏢獲得事件發(fā)生了
輸出:
ui_render_avaliable_use_btn ui_render_new_tool: liu_xin_biao ui_render_red_point
3.使用方法
1. 在本模塊內(nèi)增加一個(gè)事件定義,并在注釋中寫(xiě)明事件的參數(shù)及意義。
如果要監(jiān)聽(tīng)一個(gè)事件,請(qǐng)仔細(xì)閱讀相關(guān)注釋。
2. 在ui類最頂端import需要的事件及data_listener。
3. 在需要響應(yīng)該事件的方法(監(jiān)聽(tīng)器方法)前增加裝飾器@data_listener,參數(shù)內(nèi)列出要監(jiān)聽(tīng)的所有事件。
如:
@data_listener(OnEventA, OnEventB) def my_listener_method(arg1): ...
注意保持監(jiān)聽(tīng)器方法的參數(shù)個(gè)數(shù)及意義與事件觸發(fā)的地方一致。
4. 在邏輯代碼中適當(dāng)?shù)奈恢脤?duì)事件進(jìn)行觸發(fā)。如OnEventA(arg1, ...)
注意:并不是所有與UI的交互都必須使用事件,事件機(jī)制是為了方便多對(duì)多的交互。比如背包物品改變事件,有多個(gè)UI都會(huì)監(jiān)聽(tīng)背包物品的變化,而有多種邏輯都會(huì)導(dǎo)致背包物品變化,這時(shí)使用事件就比較方便。
4. 總結(jié)
本文主要講述了如何使用Python實(shí)現(xiàn)Event回調(diào)機(jī)制,上述的示例代碼參考我的[github-EventCallBack] (https://github.com/csdz/SnapToSnap/tree/master/EventCallBack)。
以上這篇Python實(shí)現(xiàn)Event回調(diào)機(jī)制的方法就是小編分享給大家的全部?jī)?nèi)容了,希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
- 詳解Python 多線程 Timer定時(shí)器/延遲執(zhí)行、Event事件
- python Event事件、進(jìn)程池與線程池、協(xié)程解析
- python事件驅(qū)動(dòng)event實(shí)現(xiàn)詳解
- 詳解python多線程、鎖、event事件機(jī)制的簡(jiǎn)單使用
- python多線程之事件Event的使用詳解
- Python編程之event對(duì)象的用法實(shí)例分析
- Python的互斥鎖與信號(hào)量詳解
- Python3.X 線程中信號(hào)量的使用方法示例
- 分析Python感知線程狀態(tài)的解決方案之Event與信號(hào)量
相關(guān)文章
利用Python統(tǒng)計(jì)每天敲了多少次鍵盤(pán)
每到年末各大App都會(huì)給你來(lái)一次年度總結(jié),最近突發(fā)奇想,鍵盤(pán)是每天必備的工具,為啥不給鍵盤(pán)也來(lái)個(gè)工作總結(jié),本文就來(lái)用Python統(tǒng)計(jì)一下每天敲了多少次鍵盤(pán)吧2024-04-04
python os.path.isfile 的使用誤區(qū)詳解
今天小編就為大家分享一篇python os.path.isfile 的使用誤區(qū)詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-11-11
Python3.6 Schedule模塊定時(shí)任務(wù)(實(shí)例講解)
下面小編就為大家?guī)?lái)一篇Python3.6 Schedule模塊定時(shí)任務(wù)(實(shí)例講解)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-11-11
Python學(xué)習(xí)之while 循環(huán)語(yǔ)句
這篇文章主要給大家介紹了關(guān)于Python中while循環(huán)語(yǔ)句的相關(guān)資料,使用while循環(huán)語(yǔ)句可以解決程序中需要重復(fù)執(zhí)行的操作,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2021-10-10
13個(gè)Pandas實(shí)用技巧,助你提高開(kāi)發(fā)效率
這篇文章主要介紹了13個(gè)Pandas實(shí)用技巧,幫助你提高python開(kāi)發(fā)的效率,感興趣的朋友可以了解下2020-08-08
Python標(biāo)準(zhǔn)庫(kù)re的使用舉例(正則化匹配)
正則表達(dá)式re是內(nèi)置函數(shù),通過(guò)一定的匹配規(guī)則獲取指定的數(shù)據(jù),下面這篇文章主要給大家介紹了關(guān)于Python標(biāo)準(zhǔn)庫(kù)re的使用舉例,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-10-10
Django-rest-framework中過(guò)濾器的定制實(shí)例
這篇文章主要介紹了Django-rest-framework中過(guò)濾器的定制實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-04-04

