2012-11-22 230 views
2

所以我在製作一個遊戲,所有對象都來自一個GameObject類,它看起來像這樣;Python:回調問題

class GameObject(pygame.sprite.DirtySprite): 
    actions = dict() 

    def __init__(self): 
     pygame.sprite.DirtySprite.__init__(self) 
     self.rect = None 
     self.state = None 

    def update(self): 
     if callable(self.__class__.actions[self.state]): 
     #If this key has a function for its element... 
      self.__class__.actions[self.state](self) 

現在,我遇到了繼承的另一個問題。觀察下面的課程,以及源自它的兩節課。

class Bullet(gameobject.GameObject): 
    FRAME = pygame.Rect(23, 5, 5, 5) 
    STATES = config.Enum('IDLE', 'FIRED', 'MOVING', 'RESET') 

    def __init__(self): 
     gameobject.GameObject.__init__(self) 
     self.image = config.SPRITES.subsurface(self.__class__.FRAME) 
     self.rect = self.__class__.START_POS.copy() 
     self.state = self.__class__.STATES.IDLE 

    actions = { 
       STATES.IDLE : None  , 
       STATES.FIRED : start_moving, 
       STATES.MOVING : move  , 
       STATES.RESET : reset  , 
       } 

class ShipBullet(bullet.Bullet): 
    SPEED  = -8 
    START_POS = pygame.Rect('something') 

    def __init__(self): 
     super(self.__class__, self).__init__() 
     self.add(ingame.PLAYER) 

class EnemyBullet(bullet.Bullet): 
    SPEED  = 2 
    START_POS = pygame.Rect('something else') 

    def __init__(self): 
     super(self.__class__, self).__init__() 
     self.add(ingame.ENEMIES) 

除了NoneBullet.actions每一個元素(靜態成員,你介意)是內Bullet舉辦的活動。 Bullet並不意味着自己創建;如果這是C++,那將是一個抽象類。所以會發生什麼,Bullet的子類通過Bullet.actions搜索每一幀來決定下一步該做什麼,取決於它們的狀態(它們是否在移動,是否被拍攝等)。然而,由於Bullet.actions的元素是Bullet自己的方法,其子類正在執行那些而不是它們自己的擴展版本(它們調用父方法)。出於內存使用的原因,我不想重複這個回調字典。所以,我問這個; 我怎麼能通過它的父母字典充滿回調方法,並執行自己的版本,如果它存在的話,以及他們的父母的版本,如果它不是呢?

+0

哪裏'start_moving','move'等在定義了碼? – BrenBarn

+0

它們在'whateverclass .__ init__'之下定義,但在'whateverclass.actions'之上。爲了清晰起見,我只是將它們排除在外。 – JesseTG

+1

爲什麼你把'actions'變成一個類變量? – BrenBarn

回答

2

一個可能的解決方案是,而不是存儲函數的名稱直接引用,並使用getattr檢索正確的參考:

actions = { 
      STATES.IDLE : None   , 
      STATES.FIRED : 'start_moving', 
      STATES.MOVING : 'move'  , 
      STATES.RESET : 'reset'  , 
      } 

[...] 

def update(self): 
    method_name = self.__class__.actions[self.state] 
    if method_name and callable(getattr(self, method_name)): 
     getattr(self, method_name)(self) 

對於加速,你可以預先計算初始化的對象時,此表:

class Bullet(gameobject.GameObject): 

    FRAME = pygame.Rect(23, 5, 5, 5) 
    STATES = config.Enum('IDLE', 'FIRED', 'MOVING', 'RESET') 

    action_names = { 
        STATES.IDLE : None   , 
        STATES.FIRED : 'start_moving', 
        STATES.MOVING : 'move'  , 
        STATES.RESET : 'reset'  , 
        } 

    def __init__(self): 
     gameobject.GameObject.__init__(self) 
     self.image = config.SPRITES.subsurface(self.__class__.FRAME) 
     self.rect = self.__class__.START_POS.copy() 
     self.state = self.__class__.STATES.IDLE 

     # Update actions table using getattr, so we get the correct 
     # method for subclasses. 
     self.actions = {} 
     for state, method_name in self.action_names.items(): 
      if method_name and callable(getattr(self, method_name)): 
       self.actions[state] = getattr(self, method_name) 
      else: 
       self.actions[state] = lambda self: None 


    def update(self): 
     self.actions[self.state]() 

注意,因爲在__init__代碼使用getattr,它可以被放置在Bullet.__init__和僅僅延伸通過OT她的班級。由於您已經調用超級構造函數,因此不需要更改擴展類,甚至不需要對它們進行註釋。

+0

這不就是我原來做的更復雜的版本嗎? (即在我發佈這個線程之前,每個*對象*都有自己的'actions'版本,這在整個課程中都是一樣的。)這是速度和內存之間的折衷? – JesseTG

+1

@JesseTG:是的,速度和內存之間有一個折衷。在某些時候,您需要查看實例上的操作。您可以在創建實例時執行一次查找並存儲結果(使用內存),或者每次需要該值(放棄速度)時都可以執行查找。 – BrenBarn

+0

@JesseTG我不認爲這是一個更復雜的版本。基類比較複雜,但現在你可以簡單地擴展它,並且更新函數可以正常工作,而不必爲每個子類重寫動作或手動轉換它們。 – BoppreH

2

爲什麼不使用python內置機制進行繼承?

實例函數actions對於派生類B是相同的。它在被調用時獲得實例self,然後就像調用實例本身的函數一樣:如果Python的繼承機制存在或者回退到A的實現,則會調用B的方法。

編輯: l4mpi建議指出,這將每次創建地圖,所以我已將action_map更改爲屬性。

def generateActions(cls): 
    cls.actions = {} 
    for a, f in cls.genactions.items(): 
     cls.actions[a] = getattr(cls, f) if f else lambda *_: None 
    return cls 

注意actions是:

class A(): 
    def actions(self, action): 
     if not hasattr(self, "actions_map"): 
      self.actions_map = { 
        "IDLE" : self.idle, 
        "FIRED" : self.fired, 
        "MOVING" : self.move, 
        "RESET" : self.reset, 
        } 
     return self.actions_map[action] 

    def idle(self): 
     print "A idle" 
     pass 

    def fired(self): 
     print "A fired" 

    def move(self): 
     print "A move" 

    def reset(self): 
     print "A reset" 

class B(A):  
    def fired(self): 
     print "B fired" 


a = A() 
b = B() 

a.actions("FIRED")() 
b.actions("FIRED")() 
b.actions("MOVING")() 

>> A fired 
>> B fired 
>> A move 
+0

但是OP需要一個類A中的動作表,將狀態映射到實例方法。問題是如何引用基類中的操作。 – BoppreH

+0

我編輯了答案,但想法是一樣的 - 使用python的繼承,而不是重新發明輪子。 – zenpoy

+0

儘管答案有效,但可能比使用類或實例變量的版本慢一些,這是因爲函數調用的開銷以及每次調用「actions」時都會生成「actions」字典的事實,以'self'作爲'n'屬性查找的代價來進行可能的操作。 – l4mpi

2

擴展BoppreH的回答,您可以得到由填充在類的創建正確的方法活動字典,使用類裝飾這樣擺脫getattr查找如果動作的給定值爲None,則表示您可以擺脫update中的if callable(...)語句。

現在,你只需要裝飾添加到您的類:

@generateActions 
class Bullet(gameobject.GameObject): 
    FRAME = pygame.Rect(23, 5, 5, 5) 
    STATES = config.Enum('IDLE', 'FIRED', 'MOVING', 'RESET') 

    genactions = { 
      STATES.IDLE : None   , 
      STATES.FIRED : 'start_moving', 
      STATES.MOVING : 'move'  , 
      STATES.RESET : 'reset'  , 
      } 
    ... 

@generateActions 
class ShipBullet(bullet.Bullet): 
    ... 
+0

優雅的解決方案,但我認爲裝飾者太容易出錯。這將需要大量的文檔,如果用戶仍然忘記它,所產生的bug將非常難以追蹤。 – BoppreH

+0

你也可以用一個元類來做到這一點,它可以消除使用顯式裝飾器的需要,但這會更神奇。 – BrenBarn

+0

@BoppreH如果您將方法名稱的dict重命名爲類似'genActions'的類型,忘記添加生成器會導致'update'中的屬性錯誤 - 您可以在其中記錄'actions'應該包含的內容以及存在用於創建字典的發生器。 – l4mpi

0

考慮定義爲遊戲對象類型類。

這是我的解決方案。我已經簡化了所有與我在這裏創建的點無關的所有內容。

class GameObjectClass(type): 
    """A metaclass for all game objects""" 

    @staticmethod 
    def find(key, bases, dict): 
     """Find a member in the class dict or any of the bases""" 
     if key in dict: 
      return dict[key] 
     for b in bases: 
      attr = getattr(b, key, None) 
      if attr is not None: 
       return attr 
     return None 

    def __new__(mcs, name, bases, dict): 
     actions = GameObjectClass.find('actions', bases, dict) 
     actionsResolved = {} 
     for key, methodname in actions.items(): 
      if methodname is None: 
       actionsResolved[key] = None 
      else: 
       actionsResolved[key] = GameObjectClass.find(methodname, bases, dict) 
     dict['actionsResolved'] = actionsResolved 
     return type.__new__(mcs, name, bases, dict) 

class GameObject(object): 

    # This class and all its subclasses will have 
    # GameObjectClass for a metaclass 
    __metaclass__ = GameObjectClass 
    actions = dict() 

    def __init__(self): 
     self.state = None 

    def update(self): 
     if callable(self.__class__.actionsResolved[self.state]): 
      self.__class__.actionsResolved[self.state](self) 

class Bullet(GameObject): 
    STATES = config.Enum('IDLE', 'FIRED', 'MOVING', 'RESET') 
    def __init__(self): 
     super(Bullet, self).__init__() 
     self.state = self.__class__.STATES.IDLE 
    # Here, strings are used. They will be resolved to 
    # references to actual methods (actionsResolved), 
    # and this resolution will happen only once 
    # (when the game object class is defined) 
    actions = { 
     STATES.IDLE: None, 
     STATES.FIRED: 'start_moving', 
     STATES.MOVING: 'move', 
     STATES.RESET: 'reset' 
    } 
    def start_moving(self): 
     print "Bullet.start_moving" 
    def move(self): 
     print "Bullet.move" 
    def reset(self): 
     print "Bullet.reset" 

class ShipBullet(Bullet): 
    # This one will be correctly used for the FIRED state 
    def start_moving(self): 
     print "ShipBullet.start_moving" 
0

也許我不太明白你想要做..什麼

據我瞭解,你有一個類(A),其中主要介紹函數和類(B),主要描述這些屬性。

你想從類B的實例中調用類A的方法嗎?

爲什麼不能做這樣的事情:

class Bullet(gameobject.GameObject): 
    ... 

class ShipBullet(bullet.Bullet): 
    ABC = bullet.Bullet 

    def somefunc(self): 
     somekey = 5 
     self.ABC.actions[somekey](self, *a, **kw) 
     # or 
     super(self.__class__, self).actions[somekey](self, *a, **kw) 
     # or 
     bullet.Bullet.actions[somekey](self, *a, **kw) 

您需要添加裁判在行動定義,instanse像

def move(self, to_x, to_y): #as classic method 
    .... 
# or 
def move(whom, to_x, to_y): #as "free-function" 
    ....