簡體   English   中英

Python:回調問題

[英]Python: Callback issue

因此,我正在做一個游戲,所有對象都來自一個GameObject類,它看起來像這樣;

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)

Bullet.actions每個元素(請注意,是靜態成員),除了None之外,都是Bullet內的函數。 Bullet並不是要自己創建; 如果是C ++,它將是一個抽象類。 因此,發生的是, Bullet的子類在每一幀中搜索Bullet.actions以決定下一步要執行的操作,具體取決於它們的狀態(移動的狀態,被射擊的狀態等)。 但是,由於Bullet.actions的元素是Bullet 自己的方法,因此其子類將執行這些方法,而不是執行其自己的擴展版本(稱為父方法)。 由於內存使用的原因,我不想重復此回調命令。 所以,我問這個; 我怎樣才能讓一個子類的實例通過充滿了回調方法的父字典進行查看,並執行它們自己的版本(如果存在),並執行其父版本(如果不存在)?

一種可能的解決方案是存儲函數的名稱而不是直接引用,並使用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__並且只能由其他類進行擴展。 正如您已經調用了超級構造函數一樣,將無需更改擴展類甚至對其進行注釋。

為什么不使用python內置機制進行繼承?

派生類B的實例函數actions相同。 它在調用時獲取實例self ,然后就像在實例本身上調用該函數:Python的繼承機制調用B的方法(如果存在)或回退到A的實現。

編輯: l4mpi建議指出,這將每次創建地圖,因此我將action_map更改為屬性。

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

擴展BoppreH的答案,您可以使用類裝飾器在類創建時通過使用正確的方法填充活動字典來擺脫getattr查找:

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

請注意,如果動作的給定值為None ,則actions將填充空值lambda,這意味着if callable(...) 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):
    ...

考慮為游戲對象定義類型類。

這是我的解決方案。 我已經簡化了所有與我在這里提出的觀點無關的內容。

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"

也許我不太了解你想做什么。

據我了解,您有一個主要描述功能的類(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)

您需要在動作定義中添加ref來實例化

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM