简体   繁体   English

Python:回调问题

[英]Python: Callback issue

So I'm making a game, and all objects derive from one GameObject class, which looks something like this; 因此,我正在做一个游戏,所有对象都来自一个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)

Now, I'm running into another issue with inheritance. 现在,我遇到了继承的另一个问题。 Observe the class below, and the two that derive from it; 观察下面的类,以及从该类派生的两个类;

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)

Every element of Bullet.actions (a static member, mind you) except for None is a function held within Bullet . Bullet.actions每个元素(请注意,是静态成员),除了None之外,都是Bullet内的函数。 Bullet is not meant to be created on its own; Bullet并不是要自己创建; if this were C++, it would be an abstract class. 如果是C ++,它将是一个抽象类。 So what happens is, Bullet 's subclasses search through Bullet.actions every frame to decide what to do next, depending on their state (are they moving, were they just shot, etc.). 因此,发生的是, Bullet的子类在每一帧中搜索Bullet.actions以决定下一步要执行的操作,具体取决于它们的状态(移动的状态,被射击的状态等)。 However, since the elements of Bullet.actions are Bullet 's own methods, its subclasses are executing those instead of their own extended versions (which call the parent methods). 但是,由于Bullet.actions的元素是Bullet 自己的方法,因此其子类将执行这些方法,而不是执行其自己的扩展版本(称为父方法)。 I don't want to have to duplicate this dict of callbacks for memory usage reasons. 由于内存使用的原因,我不想重复此回调命令。 So, I ask this; 所以,我问这个; how can I have an instance of a subclass look through it's parents dictionary full of callback methods, and execute their own version if it exists, and their parent's version if it doesn't? 我怎样才能让一个子类的实例通过充满了回调方法的父字典进行查看,并执行它们自己的版本(如果存在),并执行其父版本(如果不存在)?

One possible solution is to store the function's name instead of direct references and using getattr to retrieve the correct reference: 一种可能的解决方案是存储函数的名称而不是直接引用,并使用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)

For a speedup, you can pre-compute this table when initializing the object: 为了加快速度,您可以在初始化对象时预先计算此表:

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]()

Notice that since the code in __init__ uses getattr , it can be placed in Bullet.__init__ and merely extended by the other classes. 注意,由于__init__的代码使用getattr ,因此可以将其放在Bullet.__init__并且只能由其他类进行扩展。 As you already call the super constructor, there would be no need to change the extending classes or even annotate them. 正如您已经调用了超级构造函数一样,将无需更改扩展类甚至对其进行注释。

Why not use python built-in mechanism for inheritance? 为什么不使用python内置机制进行继承?

the instance function actions is the same for the derived class B . 派生类B的实例函数actions相同。 It get the the instance self when invoked and then it's like calling the function on the instance itself: Python's inheritance mechanism invokes B 's method if it's there or fallbacks to A 's implementation. 它在调用时获取实例self ,然后就像在实例本身上调用该函数:Python的继承机制调用B的方法(如果存在)或回退到A的实现。

EDIT: l4mpi suggested pointed out that this will create the map each time, so I've change the action_map to be an attribute. 编辑: 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

Extending BoppreH's answer, you could get rid of the getattr lookup by filling the activity dict with the right methods at class creation, using a class decorator like this: 扩展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

Notice that actions is filled with a do-nothing lambda if the given value for an action is None , meaning you can get rid of that if callable(...) statement in update . 请注意,如果动作的给定值为None ,则actions将填充空值lambda,这意味着if callable(...) update if callable(...)语句可以消除该行为。

Now you simply need to add the decorator to your classes: 现在,您只需要将装饰器添加到类中:

@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):
    ...

Consider defining a typeclass for the game objects. 考虑为游戏对象定义类型类。

Here is my solution. 这是我的解决方案。 I've simplified away all that's irrelevant to the point I'm making here. 我已经简化了所有与我在这里提出的观点无关的内容。

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"

Maybe I did not quite understand what you want to do .. 也许我不太了解你想做什么。

As I understand it, you have a class (A) in which mainly describes the function and class (B), which mainly describes the properties. 据我了解,您有一个主要描述功能的类(A)和主要描述属性的类(B)。

You want to call methods of the class A from an instance of class B? 您要从类B的实例调用类A的方法吗?

Why not do something like this: 为什么不做这样的事情:

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)

You need to add ref to instanse in action definitions like 您需要在动作定义中添加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