亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Python實(shí)現(xiàn)Event回調(diào)機(jī)制的方法

 更新時(shí)間:2019年02月13日 10:46:51   作者:tab_space  
今天小編就為大家分享一篇Python實(shí)現(xiàn)Event回調(diào)機(jī)制的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧

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)

Python Event回調(diào)機(jī)制

圖1. 事件觸發(fā)說(shuō)明圖

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

其實(shí)Event回調(diào)機(jī)制就是觀察者模式,如下圖:

Python 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類(lèi)圖

上述案例中,是針對(duì)游戲客戶(hù)端UI的案例。所以我們呈現(xiàn)出的UML圖也是與UI相關(guān)。如圖3所示,它顯示了Python中實(shí)現(xiàn)Event回調(diào)的機(jī)制。

Python Event回調(diào)機(jī)制

圖3. UML關(guān)系圖

如上圖所示,此機(jī)制主要由三個(gè)類(lèi)及他們的實(shí)例(instance)組成:UIBase, UIScene, UIDataEvent。

1 . UIBase: 所有UIScene的基類(lèi),其實(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)景類(lèi),管理某個(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: 事件類(lèi),該類(lèi)有個(gè)類(lèi)變量_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è)類(lèi)之間的聯(lián)系與各自的作用,此步展示代碼實(shí)現(xiàn)相關(guān)功能。

a) UIBase.py

首先列出來(lái)的是UIBase的類(lèi),除了上述的__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中找,找到能夠滿(mǎn)足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)器,在銷(xiāo)毀一個(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è)類(lèi)之后,使用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類(lèi)最頂端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è)參考,也希望大家多多支持腳本之家。

相關(guān)文章

最新評(píng)論