Tuesday, December 23, 2008

Python Question

I've been working on my game, Stones. I think I want the game engine to have something called a "future event list". I'm struggling with how to implement this.

An event consists, essentially, of a timestamp and the code you want to execute when the event happens. An event is created with all this information and then inserted into the event list. The code is executed sometime later. I'm not sure how to specify the code to be executed and how to execute it.

I'd probably like an event to consist of an object that the event is happening to (or is causing), a method to call on that object, and parameters to pass to that method. Is there a slick way to specify, store, and use those parameters when the method is executed?

One way to do it is to have every method that can be an event take an event object as its only parameter. When an event is created, it is loaded up with the object, the object's method to call, and the parameters the method will use. Essentially the event would act as the "execution environment" for the method. If you need to pass a parameter to the method, you specify that parameter in the event. When an event is processed/executed, the method is called on the object with the event as the parameter.

The above scheme seems to have some downsides. For one, you lose the method's signature (list of parameters). So, it's harder to see what needs to be passed to the method. You'd have to read the method to see what it needs. Also, you lose the ability to call the method in a "normal" way, that is, without building up an event object first.

One way to get around the two above problems is to specify a special "eventable" method for each method you want to use in an event. This method would accept an event as a parameter and then call the corresponding "non-eventable" method and pass the second method the needed parameters from the event object. This would work but it would seem to uglify the code a good bit.

Another way to do it would be to have every "eventable" method have all of its parameters be "None" by default and have the last parameter be an event that is also "None" by default. If called with an event, the method gets the values it needs from the event. Otherwise, it uses the parameters passed in. This way of doing it avoids the above two problems but each method would have to have special code to get the parameters from the right source.

So, my question is this. What is/are the best way(s) to save an object, a method on that object, and the parameters the method needs for future execution in a python program?

I'm sure one of the thousands of python programmers who read my blog will have the answer.

6 comments:

Mark said...

I don't know what you exactly are trying to provide with this functionality, but it seems to me you may be over-engineering a bit. Use the dynamic nature of Python to your advantage (i.e. duck typing) and a naming convention for your methods (i.e. the command design pattern) and you can use a single array parameter to contain your arbitrary-length parameter set. Would this suit your needs or is there some info I'm not understanding?

mike barton said...

If you say object.method with no parens, you get a "bound method", which is a simple callable that's the method associated with the object.

What you want for the args is to stick them in a tuple or list, then use * to pass it as args to the function.

In [4]: func = math.pow
In [5]: args = (2, 3)
In [6]: func(*args)
Out[6]: 8.0

er.. math isn't an object, but that would work the same if it was.

Greg said...

Cool. Thanks guys. I considered using a list to store the parameters but I didn't know how to use them in a method call. I didn't know about the '*' operator. That seems to be what I need.

Greg said...

It totally works. Here is the code, without the indenting unfortunately:

class A(object):
def __init__(self, id):
self.id = id

def event(self, string):
print "%s: %s" % (self.id, string)

class Event(object):
def __init__(self, time, method, params):
self.time = float(time)
self.method = method
self.params = params

def execute(self):
self.method(*self.params)

class FEL(object):
def __init__(self):
self.time = 0.0
self.event_list = {}

def add_event(self, event):
if event.time < self.time:
raise "invalid event time"

self.event_list.setdefault(event.time, [])
self.event_list[event.time].append(event)

def run(self):
while self.event_list:
self.time = min(self.event_list.keys())

print "time: %.2f" % (self.time)

event = self.event_list[self.time].pop(0)
if not self.event_list[self.time]:
del self.event_list[self.time]

event.execute()

if __name__ == "__main__":
fel = FEL()

a = A("a")
b = A("b")
c = A("c")

fel.add_event(Event(12, a.event, ["a"]))
fel.add_event(Event(3, b.event, ["b"]))
fel.add_event(Event(12, c.event, ["c"]))
fel.add_event(Event(14, a.event, ["a again"]))

fel.run()

TheMuuj said...

Man, Python is so cool, and I really like that solution.

IANAPP, but the first thought that came to my mind was to simply store a sorted list of timestamp/lambda pairs, and rely on variable capturing for object binding and passing parameters. This has the added benefit of letting you execute more complicated code without having an explicit method.

Something like:
events.schedule(12, lambda: a.event("a"))

But then again I'm not sure about Python's lambda semantics. Are they true closures or what?

mike barton said...

You should be able to pass it any callable -- lambda, bound method, closure or object that implements __call__ or whatever makes sense in the context. I'd probably prefer to do something like:

def add_event(time, callable, *args):

Then,
add_event(12, a.event, "a")
add_event(12, lambda: a.event("a"))
etc...