Python Forum
Switch .. case .. dispatcher based on singledispatch
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Switch .. case .. dispatcher based on singledispatch
#1
The generic function mechanism (functools.singledispatch) allows us to select an action based on the type of the first argument of the action in the same way that polymorphism does with a class instance.

This post is a study on how we can use this existing mechanism to select an action based on the value of the argument, in the same way that switch ... case ... case ... statements do in other languages. For example, we would like to have a different action depending on a certain variable having the value 'foo', 'bar' or 'baz'. But the singledispatch mechanism cannot do this because these three values have the same type!

The solution here is to create a dictionary-like object named CaseDispatcher that creates a new type and a new instance of that type for each of the words 'foo', 'bar', 'baz'. Based on the value of our variable, we can get this instance from the CaseDispatcher and select the action by the type of this instance.

Without further explanations, see by yourself if this code is understandable

__version__ = '2020.02.02'

import functools

def _delegator(a, m, self):
    """Helper function for delegate_methods()"""
    return getattr(getattr(self, a), m)

def _delegate_methods(cls, attr_name, method_names):
    """Add properties to a class that delegate to a member of the instance
    
    This function is a helper to build the CaseDispatcher class
    """
    for m in method_names:
        f = functools.partial(_delegator, attr_name, m)
        setattr(cls, m, property(f))

class Missing:
    __slots__ = ('case',)
    def __init__(self, value):
        self.case = value
        
class SubDict(dict):
    def __missing__(self, key):
        return Missing(key)

class CaseDispatcher:
    """A dictionary-like storing unique instances of custom types.
    
    Usage:
        After
        
            s = CaseDispatcher(['foo', 'bar', 'baz'])
        
        s['foo'], s['bar'] and s['baz'] are all instances of newly
        created data types. These types can be used to create generic
        functions, for example:
        
        @singledispatch
        def greet(obj):
            print('Hi!')
            
        @greet.register(type(s['foo']))
        def _(obj):
            print('Hi (foo version)!')

        @greet.register(type(s['bar']))
        def _(obj):
            print('Hi (bar version)!')

        x = random.choice(['foo', 'bar', 'baz'])
        greet(s[x])

    """
    def __init__(self, keys=None):
        self.map = SubDict()
        if keys:
            self.add(keys)
        
    def add(self, keys):
        for x in keys:
            if x in self.map:
                continue
            self.map[x] = type('<{}>'.format(x), (),
                            {'case': x, '__slots__': ()})()

# add dictionary-like methods to the CaseDispatcher class.
_delegate_methods(CaseDispatcher, 'map',
    ('__getitem__', 'get', 'items', 'keys', 'values',
     '__contains__', '__iter__', '__len__', '__delitem__'))


if __name__ == '__main__':
        import random
        
        s = CaseDispatcher(['foo', 'bar', 'baz'])
        
        @functools.singledispatch
        def greet(obj):
            print('Hi!')
            
        @greet.register(type(s['foo']))
        def _(obj):
            print('Hi (foo version)!')

        @greet.register(type(s['bar']))
        def _(obj):
            print('Hi (bar version)!')

        for i in range(6):
            x = random.choice(['foo', 'bar', 'baz', 'qux'])
            print(x, 'chosen: ', end = '')
            greet(s[x])
Output:
qux chosen: Hi! foo chosen: Hi (foo version)! bar chosen: Hi (bar version)! foo chosen: Hi (foo version)! foo chosen: Hi (foo version)! baz chosen: Hi!
Reply
#2
I certainly understand the concept, and have done similar things manually using a dictionary, but the automation of this approach goes well beyond what I was doing by hiding the loading and dispatch operations entirely from the user. I like it and get the gist of what's going on. Need to look at it more (when more coherent, it's 10:30 P.M. in my part of the Earth) in order to really understand, but great work. I'll actually search on a place to really try it out on work I am involved with right now and show how I applied it.
I am currently working on a large Political data aggregation project right now, and one application that comes to mind is dispatching the proper demographic dataset based on a specific time period, for example election year and location, making order out of chaos.
Reply
#3
Thank you for your support. I edited the above post to a slightly modified version 2020.02.02. I added a __missing__ feature so that s[x] never raises an error.

An advantage of this approach is that the same CaseDispatcher instance can be used to implement as many generic functions as one wants. In fact a global CaseDispatcher instance would suffice for almost all programs. The only thing that the user must control is the number of keys in the CaseDispatcher. On the other hand, this number is related to the number of versions of generic functions in the program, which normally stays small.

My code is trending towards more abstractions these days because I watched a lot of videos about the work of the late mathematician Alexander Grothendieck who was the absolute master of abstraction in mathematics. I'm convinced that more abstraction could help us develop even greater tools in programming!
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  Dispatcher pattern Gribouillis 1 1,210 Aug-07-2023, 09:04 AM
Last Post: Gribouillis
  Roshambo with only 1 if switch Clunk_Head 12 5,569 Jan-28-2019, 08:59 AM
Last Post: perfringo
  switch to python3 Skaperen 0 2,103 Jul-03-2018, 12:55 AM
Last Post: Skaperen

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020