[英]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.