1

I am trying to figure out the most Pythonic way to pass in a limited callback for interacting with a complicated object's methods. I have a program which receives data over a communications channel, and that data falls into a few different categories. I need to decouple the determination of which category the data is in, from the further handling of the data:

class Categorizer(object):
    '''Determines which category data is in. 
       There may be multiple variants of this class'''
    def handleData(self, data, callback):
        if self.somethingReallySpecial(data):
            callback.onSomethingReallySpecial(data)
        elif self.somethingSpecial(data):
            callback.onSomethingSpecial(data)
        else:
            callback.onSomethingMundane(data)
    # ... other methods ...

class IAmAReallyComplicatedBeast(object):
    def __init__(self, categorizer, other_stuff):
        self.categorizer = categorizer
        # ...
    # ... lots of other methods ...
    def something(self, other_stuff):
        data = self.obtain_data()
        # this is probably wrong, but here's what I want to do:
        beast = self
        class Dispatcher(object):
            def onSomethingMundane(data):
                beast.doFoo(data)
            def onSomethingSpecial(data):
                beast.doBar(data)
            def onSomethingReallySpecial(data):
                beast.doBaz(data)
        self.categorizer.handleData(data, Dispatcher())
    def doFoo(self, data):
        # for mundane data
    def doBar(self, data):
        # for special data
    def doBaz(self, data):
        # for really special data

In Java I would use an inner class (like the Dispatcher here)... is there a Pythonic way of handling this?


I don't want to put the onSomethingXXX methods directly on my IAmAReallyComplicatedBeast class, for two reasons:

  • this means I'd have to use those names as is
  • I don't want the Categorizer class to have arbitrary access to the IAmAReallyComplicatedBeast object. Perhaps this comes from the usual Java paranoia mindset, but it seems like good design to me.
Jason S
  • 184,598
  • 164
  • 608
  • 970
  • 1
    I'd use a `dispatch dict`. In Python, dicts can hold functions. – ch3ka Oct 28 '14 at 16:39
  • I think the inner class is always exactly what you want and maybe the best way to do it. These thighs can never be pythonich because python is not designed to do these kind of protections. – Michele d'Amico Oct 28 '14 at 17:51

4 Answers4

2

The Pythonic way of making dispatchers is to use a dictionary. Remember that in Python functions are first-class objects, so can be values in a dict.

class IAmAReallyComplicatedBeast(object):
    def something(self, other_stuff):
        data = self.obtain_data()
        dispatcher = {
            'something_mundane': self.do_foo,
            'something_special': self.do_bar,
            'something_really_special': self.do_baz
        }
        self.categorizer.handleData(data, dispatcher)

class Categorizer(object):
    '''Determines which category data is in. 
       There may be multiple variants of this class'''
    def handleData(self, data, callback):
        if self.somethingReallySpecial(data):
            dispatcher['something_really_special'](data)

Note: I know you weren't proposing this, but inner classes are really non-Pythonic: the inner class gains no special access to the outer class, and that sort of thing is not recommended.

Daniel Roseman
  • 588,541
  • 66
  • 880
  • 895
  • question about performance: my categorizer dispatch gets called several thousand times a second; what's the cost of a dict lookup compared to an attribute lookup? – Jason S Oct 28 '14 at 16:52
  • They are *exactly the same*. Dict lookups are O(1) anyway, but even more importantly attributes are implemented as dicts. – Daniel Roseman Oct 28 '14 at 16:53
  • any thoughts about using a `namedtuple` instead of a `dict`? I know it's not necessary + it's immutable, but it seems like that might be a better approach (fail fast if the expected methods are not provided). – Jason S Oct 28 '14 at 16:56
  • @JasonS A `namedtuple` is a good idea. Here are some CPython 2 performance results: http://stackoverflow.com/a/1336890/1763356. – Veedrac Oct 29 '14 at 19:39
1

You could make IAmAReallyComplicatedBeast also a Dispatcher by aliasing the necessary methods:

class IAmAReallyComplicatedBeast(object):
    # ... snip ...

    # In something we can just pass ourself because we are a dispatcher
    self.categorizer.handleData(data, self)
    # ... snip ...
    def doFoo(self, data):
        # Do mundane things here

    onSomethingMundane = doFoo

    # ... etc. ...

Alternatively, you could create a class that wraps the methods and simply create instances of it, rather than creating a new class every time:

class Dispatcher(object):
    __slots__ = ('onSomethingMundane',
                 'onSomethingSpecial',
                 'onSomethingVerySpecial')
    def __init__(self, mundane, special, very_special):
        self.onSomethingMundane = mundane
        self.onSomethingSpecial = special
        self.onSomethingReallySpecial = very_special

Then your something method would be a bit clearer:

def something(self, other_stuff):
        data = self.obtain_data()
        dispatcher = Dispatcher(self.doFoo, self.doBar, self.doBaz)
        self.categorizer.handleData(data, dispatcher)
Sean Vieira
  • 155,703
  • 32
  • 311
  • 293
1

If you are interested in sharing the dispatcher with other classes you could do something like this:

class Dispatcher(object):
    def __init__(self,f1,f2,f3):
        self.onSomethingMundane=f1
        self.onSomethingSpecial=f2
        self.onSomethingReallySpecial=f3


class IAmAReallyComplicatedBeast(object):
    #...
    def something(self, other_stuff):
        data = self.obtain_data()
        # this is probably wrong, but here's what I want to do:
        beast = self
        beast_dispatcher = Dispatcher(beast.doFoo,beast.doBar,beast.doBaz)
        self.categorizer.handleData(data, beast_dispatcher)
    #...
Rui Botelho
  • 752
  • 1
  • 5
  • 18
1

As @ch3ka already suggests, a dict would be pythonic choice here imho. Things could like this then:

class Categorizer(object):
    '''Determines which category data is in. 
       There may be multiple variants of this class'''
    def handleData(self, data, callback_mapping):
        # get the category
        category = self.categorize(data)
        # invoke the corresponding handler
        callback_mapping[category](data)


class IAmAReallyComplicatedBeast(object):
    def __init__(self, categorizer, other_stuff):
        self.categorizer = categorizer
        # ...
    # ... lots of other methods ...
    def something(self, other_stuff):
        data = self.obtain_data()
        self.categorizer.handleData(data,
                                    dict(mundane=self.doFoo, 
                                         special=self.doBar,
                                         really_special=self.doBaz)
    def doFoo(self, data):
        # for mundane data
    def doBar(self, data):
        # for special data
    def doBaz(self, data):
        # for really special data

Another pattern frequently used is to create the name for the method to invoke dynamically. E.g. in python's builtin BaseHTTPServer do_XXX is invoked, where XXX is a placeholder for the requests HTTP method:

    mname = 'do_' + self.command
    if not hasattr(self, mname):
        self.send_error(501, "Unsupported method (%r)" % self.command)
        return
    method = getattr(self, mname)
    method()

See: https://hg.python.org/cpython/file/2.7/Lib/BaseHTTPServer.py#l323 Hence you could e.g. name your methods doSpecial, doReallySpecial and doMundane and invoke them from the categorizer.

sebastian
  • 9,526
  • 26
  • 54