Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/310.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python:回调问题_Python_Oop_Inheritance_Callback_Static Members - Fatal编程技术网

Python:回调问题

Python:回调问题,python,oop,inheritance,callback,static-members,Python,Oop,Inheritance,Callback,Static Members,所以我在做一个游戏,所有的对象都来自一个GameObject类,看起来像这样 class GameObject(pygame.sprite.DirtySprite): actions = dict() def __init__(self): pygame.sprite.DirtySprite.__init__(self) self.rect = None self.state = None def update(sel

所以我在做一个游戏,所有的对象都来自一个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
中的一个函数<代码>项目符号不是要自己创建的;如果这是C++,它就是抽象类。因此,所发生的是,
Bullet
的子类在
Bullet.actions
每一帧中搜索,根据它们的状态(它们是否正在移动,是否刚刚被射中,等等)来决定下一步要做什么。但是,由于
Bullet.actions
的元素是
Bullet
自己的方法,因此它的子类执行这些方法,而不是它们自己的扩展版本(调用父方法)。我不想因为内存使用的原因而重复这个回调命令。所以我问这个,如何让子类的实例查看其包含回调方法的父字典,如果存在,则执行其自己的版本;如果不存在,则执行其父版本?

一种可能的解决方案是存储函数名而不是直接引用,并使用来检索正确的引用:

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

请注意,由于
\uuuuu init\uuuu
中的代码使用,因此可以将其放置在
Bullet.\uuuuuu init\uuu
中,仅由其他类扩展。既然您已经调用了超级构造函数,那么就不需要更改扩展类,甚至不需要对它们进行注释。

为什么不使用python内置的继承机制呢

实例函数
actions
与派生类
B
相同。它在调用时获得实例
self
,然后就像在实例本身上调用函数一样:Python的继承机制调用
B
的方法(如果存在)或回退到
A
的实现

编辑:l4mpi指出每次都会创建映射,所以我将动作映射更改为属性

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的答案,您可以通过在类创建时使用正确的方法填充activity dict,使用如下类装饰器摆脱
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
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)
请注意,如果操作的给定值为
None
,则
actions
将用donothing lambda填充,这意味着您可以在
update
中取消该
如果可调用(…)
语句

现在,您只需将装饰器添加到类中:

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

考虑为游戏对象定义一个typeclass

这是我的解决办法。我已经简化了所有与我在这里提出的观点无关的内容

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的方法吗

为什么不这样做:

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
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添加到instance中,如

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

代码中定义的
start\u moving
move
等在哪里?它们在
whateverclass.\uuu init\uuu
下面定义,但在
whateverclass.actions
上面定义。为了清晰起见,我把它们省略了。为什么要将
动作
作为一个类变量?因为大多数对象都有多个实例(即多个外星人、多个块、多个粒子等),并且每个对象都存储相同的
动作
dict(因此能够执行相同的动作)这是一个浪费内存的好办法,但我认为装饰师太容易出错。它需要大量的文档,如果用户仍然忘记了它,那么产生的bug将非常难以跟踪。您也可以使用元类来完成这项工作,这样就不需要使用显式的decorator,但这将更神奇。@BoppreH如果您使用方法名将dict重命名为类似
genActions
,忘记添加生成器将导致
update
中的属性错误-您可以在其中记录
操作
应包含的内容,并且有一个生成器用于创建dict。但是OP需要类a中的操作表,即将状态映射到实例方法。问题是如何引用基类中的操作。我已经编辑了答案,但想法是一样的-使用python的继承,而不是重新发明轮子。虽然答案有效,它可能比使用类或实例变量的版本慢一点,这是因为函数调用的开销以及您在每次调用
操作时生成
dict的事实,这是以
n
属性查找
self
以查找
n
可能的操作为代价的。@zenpoy实际上我刚刚测试过它,在python 3.2上,您的版本比使用字典慢150%,甚至比使用getattr慢。此外,版本的性能应与操作的数量成比例,而getattr和class dict版本应保持不变。@l4mpi-请参阅我的编辑。这会解决所有问题,并且仍然是一个相当简单的解决方案吗?这不是我最初做的更复杂的版本吗?(也就是说,在我发布这个帖子之前,每个对象都有自己版本的
操作
,在整个类中都是相同的。)这是速度和内存之间的折衷吗?@JesseTG:是的,速度和内存之间有折衷。在某些时候,您需要查找实例上的操作。您可以在创建实例时执行一次查找并存储结果(使用内存),也可以在每次需要时执行查找