7

I have 5 sets of request's categories defined as python dicts, for example:

category1 = {'type1', 'type2', 'type3'}
category2 = {'type4', 'type5'}
category3 = {'type6', 'type7', 'type8', 'type9'}
category4 = {'type10', 'type11'}
category5 = {'type12', 'type13', 'type14'}

And I need to handle requests using their category, for instance:

if request_type in category1:
    # process category1 request
    process_category1_request(...)
elif request_type in category2:
    # process category2 request
    process_category2_request(...)
elif...

and I need to dispatch a request using the request type to a different function to process it.

I already know there are ways of dispatching this requests in Python without the need of using if-elif, but my question is: what's the best way to do it while maintaining the code clean and simple?

smci
  • 32,567
  • 20
  • 113
  • 146
Sergio Ayestarán
  • 5,590
  • 4
  • 38
  • 62
  • Perhaps you could pass the variable in the function: process_request(category1)...etc. – bozdoz Jan 10 '13 at 17:15
  • 4
    your `dict`s look like sets to me (only keys, no values). – Martijn Pieters Jan 10 '13 at 17:21
  • 1
    Be careful as @MartijnPieters pointed out. The set literal syntax is `{ item1, item2, etc...}`. This is similar to the dict literal syntax , as in it uses braces. So you actually have 5 sets and not dicts – dm03514 Jan 10 '13 at 17:32

5 Answers5

17

If request_type can be present in more than one category, you could use a tuple to loop through them in priority order:

categories = (
    (category1, dispatch1method), 
    (category2, dispatch2method),
    (category3, dispatch3method),
    (category4, dispatch4method),
    (category5, dispatch5method),
)

next(method for cat, method in categories if request_type in cat)(arguments)

Otherwise use a dict() to map category types to dispatch methods instead; reusing the same tuple-of-tuples mapping above to build a dispatch:

category_dispatch = {}
for cat, dispatch in categories:
    category_dispatch.update(dict.fromkeys(cat.keys(), dispatch))

Then just look up the request type on that:

category_dispatch[request_type](arguments)

A mapping lookup like that would be faster than a scan through the tuple, where we have to test against each category in turn until we find a match.

In fact, the priority ordering can be maintained by reversing that same tuple structure like so:

category_dispatch = {}
for cat, dispatch in reversed(categories):
    category_dispatch.update(dict.fromkeys(cat.keys(), dispatch))

since now the highest priority mapping for a given request_type key will be entered into the category_dispatch structure last. This will give you the fastest dispatch even if request types were present in multiple categories.

Disadvantage is that if your category* mappings are dynamic (request types get added to and removed from different categories over time) you'd need to maintain the category_dispatch dict to reflect those changes too.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • 3
    Wasn't me, I think your answer is (as usual) kick ass – Jakob Bowyer Jan 10 '13 at 17:23
  • 1
    Not me either, I upvoted. The single table approach is very much to be preferred to the linear scan, though -- it's simpler *and* faster. You might want to emphasize that. – Fred Foo Jan 10 '13 at 17:27
  • 1
    @larsmans: Absolutely; but if there is a requirement for the category to be present in more than one category, this is the choice you have. – Martijn Pieters Jan 10 '13 at 17:29
  • @MartijnPieters: but if the dispatch table is built at program startup, you've got the best of both worlds. – Fred Foo Jan 10 '13 at 20:18
4

I think the cleanest may be two maps, to make the code most readable.

type_category_map = {"type1" : "category1", 
"type2" : "category1", , 
"type3" : "category1",
"type4" : "category2",
....
"type14" : "category5"}

category_function_map = {"category1" : "handler1_function",
"category2" : "handler2_function,
....
}

Then the python is just this:

category = type_category_map[request_type]
handler_function = category_function_map[category]
handler_function(request)

There would be ways to do it with a single data structure, but none that would be as clear and easy to follow as this, I think.

Clay Wardell
  • 14,846
  • 13
  • 44
  • 65
3

Map your categories to a handler. Independent of the size of the map you will have O(1) access time.

MAP = {
  'cat1': handler1,
  'cat2': handler2,
   ....
}

MAP[request_type](...)
3

You cannot specify a dict like

category1 = {'type1', 'type2', 'type3'}

you don't have key-values here.

As for you question, is a straightforward solution good for you?

dispatchers = {}

def register_dispatches(types, dispatcher):
    dispatchers.update(dict.fromkeys(types, dispatcher))

def process(request_type, *args, **kwargs):
    dispatchers[request_type](*args, **kwargs)


register_dispatches(['type1', 'type2', 'type3'], process_category1_request)
register_dispatches(['type4', 'type5'], process_category2_request)
...
process(request_type, ...)
Andrey Petrov
  • 121
  • 1
  • 3
2
categories = {request1 : dispatch1, request2 : dispatch2, request3 : dispatch3}
for category, dispatch in categories.iteritems():
    if something in category:
        dispatch(something)

How about this?

Jakob Bowyer
  • 33,878
  • 8
  • 76
  • 91