Z
Zak Arntson
I'm currently implementing a game GUI, where there are three
components to the screen: A battle, a command button area and a city
view area. Each of these are rectangular, and handle interactions in
different ways.
For example, on the battle "frame" you can select units, right-click
to deliver context-sensitive commands, etc. On the command frame, you
select buttons to deliver commands, change the state of the battle
frame (i.e., a move command sets the battle frame to accept a move-to
location). This sort of thing.
So I'm implementing the frame code right now. Each Frame object has a
StateMachine object. Which mean that in the __init__ code of a child
of Frame, you add rules to the StateMachine object:
###
class ButtonFrame(Frame):
def __init__(self, owner, pos, size=None):
Frame.__init__(self, owner, pos, size)
ism = self.input_state_machine
ism.add_rule("None", "LWait", (MOUSEBUTTONDOWN, 1))
##
ism.add_rule("LWait", "None", (MOUSEBUTTONDOWN, 2))
ism.add_rule("LWait", "None", (MOUSEBUTTONUP, 1))
###
This is all fine and good. Now, when creating a Frame child class, you
make an implicit agreement that you'll create methods for each state,
such as ButtonFrame.enter_state_LWait() or
ButtonFrame.leave_state_None().
My current approach is that in the Frame class, I have a method to
call _after_ initialization that creates a bunch of dummy methods so
the user doesn't have to implement EVERY state change method in a
Frame child:
###
## @classmethod -- Python 2.4
def _empty_state_method(self):
pass
_empty_state_method = classmethod(_empty_state_method)
def create_empty_state_methods(self):
ism = self.input_state_machine
for timing in ('enter','during','leave'):
for state in ism.states:
method = '%s_state_%s' % (timing, state)
if not hasattr(self.__class__, method):
setattr(self.__class__, method, Frame._empty_state_method)
###
This means that if the user hasn't implemented
ButtonFrame.during_state_LWait(), for example, an empty function will
be provided (pointing to _empty_state_method().
(Aside: I'm considering putting all these state methods in a
dictionary of dictionaries so you can do a quick
myButtonFrame.state_dict['during']['LWait'] to access the proper
method)
What this now means is that when implementing a child, I am not only
forcing a call to Frame.__init__(), but also
Frame.create_empty_state_methods(). So my ButtonFrame class looks
like:
###
class ButtonFrame(Frame):
def __init__(self, owner, pos, size=None):
Frame.__init__(self, owner, pos, size)
ism = self.input_state_machine
ism.add_rule("None", "LWait", (MOUSEBUTTONDOWN, 1))
##
ism.add_rule("LWait", "None", (MOUSEBUTTONDOWN, 2))
ism.add_rule("LWait", "None", (MOUSEBUTTONUP, 1))
self.create_empty_state_methods()
###
Note the last line. When programming EVERY child I have to remember to
add this self.create_empty_state_methods() line.
My question: What are Pythonic alternatives to this sort of thing?
I can think of a few solutions, but none of them seem to be obviously
advantageous:
1. All children don't call __init__, but have an _init() method that
is called by Frame.__init__(). That way ButtonFrame has an _init()
method rather than an __init__() method. This may be my best option.
###
class Frame:
def __init__(self, owner, pos, size=None):
self.owner = owner
self.children = []
# more setup code here.
self._init()
self.create_empty_state_methods()
###
2. Frame has an init() function that needs to be called following the
instantiation of a Frame (or Frame child object). I'm not too keen on
this, because it requires creating the Frame object and _then_ running
an init() method. I try to keep that sort of thing to a minimum
because it makes quick object creation a little funky. (init() has to
return self so you can do a myList.append(ButtonFrame().init()))
###
class Frame:
def __init__(self, owner, pos, size=None):
self.owner = owner
self.children = []
# more setup code here.
def init(self):
self.create_empty_state_methods()
return self
b = Frame()
b.init()
myList.append(b)
###
Phew! Hope that's not overly long! Now I can't be the first person to
want a pre- and post- child init code! I'm worried that I'm
overlooking something or there's an even more Pythonic way to do
things than above.
components to the screen: A battle, a command button area and a city
view area. Each of these are rectangular, and handle interactions in
different ways.
For example, on the battle "frame" you can select units, right-click
to deliver context-sensitive commands, etc. On the command frame, you
select buttons to deliver commands, change the state of the battle
frame (i.e., a move command sets the battle frame to accept a move-to
location). This sort of thing.
So I'm implementing the frame code right now. Each Frame object has a
StateMachine object. Which mean that in the __init__ code of a child
of Frame, you add rules to the StateMachine object:
###
class ButtonFrame(Frame):
def __init__(self, owner, pos, size=None):
Frame.__init__(self, owner, pos, size)
ism = self.input_state_machine
ism.add_rule("None", "LWait", (MOUSEBUTTONDOWN, 1))
##
ism.add_rule("LWait", "None", (MOUSEBUTTONDOWN, 2))
ism.add_rule("LWait", "None", (MOUSEBUTTONUP, 1))
###
This is all fine and good. Now, when creating a Frame child class, you
make an implicit agreement that you'll create methods for each state,
such as ButtonFrame.enter_state_LWait() or
ButtonFrame.leave_state_None().
My current approach is that in the Frame class, I have a method to
call _after_ initialization that creates a bunch of dummy methods so
the user doesn't have to implement EVERY state change method in a
Frame child:
###
## @classmethod -- Python 2.4
def _empty_state_method(self):
pass
_empty_state_method = classmethod(_empty_state_method)
def create_empty_state_methods(self):
ism = self.input_state_machine
for timing in ('enter','during','leave'):
for state in ism.states:
method = '%s_state_%s' % (timing, state)
if not hasattr(self.__class__, method):
setattr(self.__class__, method, Frame._empty_state_method)
###
This means that if the user hasn't implemented
ButtonFrame.during_state_LWait(), for example, an empty function will
be provided (pointing to _empty_state_method().
(Aside: I'm considering putting all these state methods in a
dictionary of dictionaries so you can do a quick
myButtonFrame.state_dict['during']['LWait'] to access the proper
method)
What this now means is that when implementing a child, I am not only
forcing a call to Frame.__init__(), but also
Frame.create_empty_state_methods(). So my ButtonFrame class looks
like:
###
class ButtonFrame(Frame):
def __init__(self, owner, pos, size=None):
Frame.__init__(self, owner, pos, size)
ism = self.input_state_machine
ism.add_rule("None", "LWait", (MOUSEBUTTONDOWN, 1))
##
ism.add_rule("LWait", "None", (MOUSEBUTTONDOWN, 2))
ism.add_rule("LWait", "None", (MOUSEBUTTONUP, 1))
self.create_empty_state_methods()
###
Note the last line. When programming EVERY child I have to remember to
add this self.create_empty_state_methods() line.
My question: What are Pythonic alternatives to this sort of thing?
I can think of a few solutions, but none of them seem to be obviously
advantageous:
1. All children don't call __init__, but have an _init() method that
is called by Frame.__init__(). That way ButtonFrame has an _init()
method rather than an __init__() method. This may be my best option.
###
class Frame:
def __init__(self, owner, pos, size=None):
self.owner = owner
self.children = []
# more setup code here.
self._init()
self.create_empty_state_methods()
###
2. Frame has an init() function that needs to be called following the
instantiation of a Frame (or Frame child object). I'm not too keen on
this, because it requires creating the Frame object and _then_ running
an init() method. I try to keep that sort of thing to a minimum
because it makes quick object creation a little funky. (init() has to
return self so you can do a myList.append(ButtonFrame().init()))
###
class Frame:
def __init__(self, owner, pos, size=None):
self.owner = owner
self.children = []
# more setup code here.
def init(self):
self.create_empty_state_methods()
return self
b = Frame()
b.init()
myList.append(b)
###
Phew! Hope that's not overly long! Now I can't be the first person to
want a pre- and post- child init code! I'm worried that I'm
overlooking something or there's an even more Pythonic way to do
things than above.