Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing objects around an event queue in Python

Tags:

python

So i have a relatively convoluted setup for something I'm working on explained as follows:

This is is python. and more of a rough outline, but it covers everything I need. Though the process next function is the same so feel free to clean that up if you want.

#timer event that runs every .1 second and processes events in a queue
some_event_timer():
    events.process_next()

class Event_queue:
    def __init__(self):
        self.events = []

    def push(self, event, parameters):
        self.events.insert(len(self.events), event, parameters)

    def process_next(self):
        event = self.pop(0)
        event[0](event[1])

class Foo:            
    def __init__(self, start_value = 1):
        self.value = start_value

    def update_value(self, multiple):
        self.value *= multiple

    def return_bah(self)
        return self.value + 3

class Bar:
    def __init__(self, number1, number2):
        self.init = number1
        self.add = number2

    def print_alt_value(self, in_value):
        print in_value * (self.init + self.add)

That is a barebones of what I have, but it illustrates my problem: Doing the below

events2 = Event_queue2()
foo1 = Foo(4)   ----> foo1.value = 4 here
bar1 = Bar(4, 2)

events2.push(foo1.update_value,1.5)
events2.push(bar1.print_alt_value,foo1.value)
events2.push(bar.print_alt_value,foo1.return_bah())

events2.process_next() ----> should process update_value to change foo.value to 6
events2.process_next() ----> should process print_alt_value in bar class - expected 36
events2.process_next() ----> should process print_alt_value - expected 54

I initially expected my output to be 36 6 * (4 + 2)

I know why its not, foo1.value and foo1.return_bah() gets passed as an evaluated parameter (correct term?). What I really want is to pass the reference to the variable or the reference to the method, rather than having it evaluate when I put it in my event queue.

Can anyone help me.

I tried searching, but I couldn't piece together what I wanted exactly. TO get what I have now I initially looked at these threads: Calling a function of a module from a string with the function's name in Python

Use a string to call function in Python

But I don't see how to support parameters from that properly or how to support passing another function or reference to a variable from those.

I suppose at least for the method call, I could perhaps pass the parameter as foo1.return.bah and evaluate in the process_next method, but I was hoping for a general way that would accept both standard variables and method calls, as the event_queue will take both.

Thank you for the help

Update edit:

So I following the suggestion below, and got really close, but:

Ok, so I followed your queue suggestion and got really close to what I want, but I don't completely understand the first part about multiple functions.

I want to be able to call a dictionary of objects with this as well. for example:

names = ["test1", "test2"]
    for name in names:
        names_objs[name] = Foo(4)

Then when attempting to push via lambda

for name in names_list:
    events2.push(lambda: names_objs[name].update_value(2))

doesn't work. When teh event actually gets processed it only runs on whatever name_objs[name] references, and if the name variable is no longer valid or has been modified outside the function, it is wrong. This actually wasn't surprising, but adding a:

name_obj_hold = name_objs[name] 

then pushing that didn't either. it again only operates on whatever name_obj_hold last referenced.

Can someone clarify the multiple funcs thing. I'm afraid I'm having trouble wrapping my head around it.

basically I need the initial method call evaluated, so something like: names_objs[name].some_func(#something in here#) gets the proper method and associated with the right class object instance, but the #something in here# doesn't get evaluated (whether it is a variable or another function) until it actually gets called from the event queue.

like image 538
Chris W Avatar asked Nov 19 '13 21:11

Chris W


People also ask

Can you pass an object to a function in Python?

If you pass immutable arguments like integers, strings or tuples to a function, the passing acts like call-by-value. The object reference is passed to the function parameters. They can't be changed within the function, because they can't be changed at all, i.e. they are immutable.

Can you peek in a queue Python?

A peek function in the python queue is used to print the first element of the queue. It returns the item which is present at the front index of the queue. It will not remove the first element but print it.

How do queues relate to multiprocessing?

A simple way to communicate between process with multiprocessing is to use a Queue to pass messages back and forth. Any pickle-able object can pass through a Queue. This short example only passes a single message to a single worker, then the main process waits for the worker to finish.

Why do we pass objects in class Python?

Passing object as parameter In class Person, MyClass is also used so that is imported. In method display() object of MyClass is created. Then the my_method() method of class MyClass is called and object of Person class is passed as parameter. On executing this Python program you get output as following.


1 Answers

Instead of passing in the function to call func1 and the arguments that should be passed to the function, pass in a function func2 that calls func1 with the arguments that should be passed in.

d = {"a":1}
def p(val):
  print val

def func1():
  p(d["a"])

def call_it(func):
  func()

call_it(func1)
d["a"] = 111
call_it(func1)

Within func1, d["a"] is not evaluated until func1 actually executes.

For your purposes, your queue would change to:

class EventQueue(object):
  def __init__(self):
    self.events = deque()
  def push(self, callable):
    self.events.append(callable)
  def process_next(self):
    self.events.popleft()()

collections.deque will be faster at popping from the front of the queue than a list.

And to use the EventQueue, you can use lambdas for quick anonymous function.

events2 = EventQueue()
foo1 = Foo(4)
bar1 = Bar(4, 2)

events2.push(lambda: foo1.update_value(1.5))
events2.push(lambda: bar1.print_alt_value(foo1.value))
events2.push(lambda: bar1.print_alt_value(foo1.return_bah()))

events2.process_next()
events2.process_next() # 36.0
events2.process_next() # 54.0

For Edit:

In this case you need to "capture" the value in a variable that is more tightly scoped than the loop. You can use a normal function and partial() to achieve this.

for name in names_list:
  def update(name):
    names_objs[name].update_value(2)
  events2.push(partial(update, name))
like image 109
unholysampler Avatar answered Oct 19 '22 05:10

unholysampler