Jason S
Jason S

Reputation: 189836

Python equivalent of Java inner class for delegation

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:

Upvotes: 1

Views: 179

Answers (4)

Sean Vieira
Sean Vieira

Reputation: 160043

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)

Upvotes: 1

sebastian
sebastian

Reputation: 9696

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.

Upvotes: 1

Daniel Roseman
Daniel Roseman

Reputation: 599926

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.

Upvotes: 2

Rui Botelho
Rui Botelho

Reputation: 751

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)
    #...

Upvotes: 1

Related Questions