Python:回调问题
所以我在做一个游戏,所有的对象都来自一个GameObject类,看起来像这样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
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:是的,速度和内存之间有折衷。在某些时候,您需要查找实例上的操作。您可以在创建实例时执行一次查找并存储结果(使用内存),也可以在每次需要时执行查找