Python Forum

Full Version: simplifying a stack of elifs
You're currently viewing a stripped down version of our content. View the full version with proper formatting.
i have a thread function with a role of handling some commands it gets from a queue. there a lot of statement groups like:
    elif msg[0] == 'some command name':
        ...
        # statements to handle that command
        ...
i was thinking of having a mapping of function references but every command involves accessing and modifying local variables, so the functions aspect of this just makes things worse with all the detail to exchange data with the caller (passing locals() is not an option because modifications can't be done that way). if there was a way to run a function in the same context as the caller, that would be a nice direct solution. even calling exec() with the code in a string because passing locals() is still a dictionary that can't be modified.

any ideas?
Skaperen Wrote:every command involves accessing and modifying local variables
Make these variables members of a class instance and dispatch things on instance methods.
class CommandHandler:
    dispatch = {
        'spam': 'on_spam',
        'eggs': 'on_eggs',
    }

    def __init__(self, ...):
        self.foo = ...
        self.bar = ...

    def __call__(self, msg):
        getattr(self, self.dispatch[msg[0]])()

    def on_spam(self):
        self.foo = 3452

    def on_eggs(self):
        self.foo = 1233
Instance variables are the normal way to share data between several functions.

You can also use new classes to manage individual commands

class Command:
    def __init__(self, handler):
        self.handler = handler

class Spam(Command):
    def run(self):
        self.handler.foo = 1023

class Eggs(Command):
    def run(self):
        self.handler.foo = 1048

class CommandHandler:
    dispatch = {
        'spam': Spam,
        'eggs': Eggs,
    }

    def __init__(self, ...):
        self.foo = ...
        self.bar = ...

    def __call__(self, msg):
        self.dispatch[msg[0]](self).run()
i can't use "self" because that might collide with other threads so i think each thread needs its own class or dictionary to keep variables in.

does CPython do locks between threads to access dictionaries? i haven't seen anything to suggest that it does. since Queues are described as having such locks, i suspect nothing else does unless it says it does, so i think i need to keep things separated between threads.

what about defining these action functions inside the function that needs to call them and have it call like this:
    def _agent():
        ...
        def x_foo():
            ... # code to run when request "foo" arrives
        def x_bar():
            ... # code to run when request "bar" arrives

        msg = q.get()
        req = 'x_'+msg[0]
        if req in locals():
            locals()[req]() # call the function to handle this request
Skaperen Wrote:i can't use "self" because that might collide with other threads
Each thread can create its own instance of the class. These instances won't collide.
Skaperen Wrote:what about defining these action functions inside the function that needs to call them
I don't think it helps function to share local variables. It would be a good idea to write a complete working example and compare the different implementations.
it turns out that variables local to the method are accessible by the inner defined function, so, this is not really an issue, at all. this script shows to me that the inner function can access the variable named "this" (and, even "self").
#!/usr/bin/env python3

class foo:
    def __init__(self):
        return

    def meth(self):
        this = 9
        def inner(arg1):
            print('arg1 =',repr(arg1),flush=1)
            print('this =',repr(this),flush=1)
            print('self =',repr(self),flush=1)
            return
        inner(3)
        return

o = foo()
o.meth()
Accessing a variable for reading is different from updating the variable. You can very well pass locals() around as long as you only read variables.
i get around that by making the variables be 1-list and update it by indexing [0]. so, i am reading the reference to the list.
So it means that you prefer a list to a class instance to store values, for example mylist[0] = 123 instead of myobj.foo = 123. I tend to prefer the latter because it avoids a magic number and it is extendable: you can add myobj.bar later.

Using inner functions allows you to omit a parameter mylist or myobj or self in the functions. I think it is the only advantage. Again I don't like this trick very much, I prefer plain functions because they are more explicit and more standard.
no. i actually like the class reference way. i just originally started doing the 1-list way long ago before i knew that classes could be used his way. it's just a design habit i need to break.

i've juggled my design around all this week and have eliminated the inner functions by reducing the number of different message actions to just two, in which the elif stack is now smaller, as the code within each action (what was a function called by name). i might get brave and test this thing this weekend, then code up the backup accelerator that was the original need for it.