Reputation: 189836
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:
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.Upvotes: 1
Views: 179
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
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
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
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