2

I am writing a function of the form:

def fn(adict, b):
    """`adict` contains key(str): value(list). if `b` is a dict we have to
       call `do_something` for pairs of lists from `adict` and `b` for
       matching keys. if `b` is a list, we have to call `do_something`
       for all lists in `adict` with `b` as the common second
       argument.

    """
    if isinstance(b, dict):
        for key, value in adict.items():
            do_something(value, b[key])
    else:
        for key, value in adict.items():
            do_something(value, b)

def do_something(x, y):
    """x and y are lists"""
    pass

I am aware that this may not be a good design (Is it bad design to base control flow/conditionals around an object's class?). But writing two functions, one taking b as a dict and another as a list, seems too redundant. What are some better alternatives?

Community
  • 1
  • 1
subhacom
  • 868
  • 10
  • 24
  • Write two functions. Seriously, it's not that hard and you should know what types your variables are anyway. A list and a dict are _not_ logically the same type, they do not inherit from each other and the common interface is irrelevant in this case since you can't use polymorphism here as you already found out. – Benjamin Gruenbaum Jul 22 '14 at 06:23
  • 1
    Writing two functions doesn't help if you're just going to have the same `if` somewhere else to decide which function to call. The real question is, what is the design that is leading you to want to call the same function with those two types giving those two behaviors? Can you regularlize the design as a whole to eliminate that oddity? – BrenBarn Jul 22 '14 at 06:37
  • @BrenBarn this is time series data where I have a bunch of different sources in adict and sampling times in b. The first case is where each source has different sampling times and the second case is when all of them are sampled at the same time points. This is a common scenario in data analysis and I suspect there should be an existing design practice for this. – subhacom Jul 22 '14 at 06:48

2 Answers2

1

There's indeed a pattern for such problems, it's named "multiple dispatch" or "multimethods". You can find a (quite simple) example Python implementation here http://www.artima.com/weblogs/viewpost.jsp?thread=101605

Using this solution, your code might look like:

from mm import multimethod

@multimethod(list, dict)
def itersources(sources, samples):
    for key, value in sources.iteritems():
        yield value, samples[key]

@multimethod(list, list)
def itersources(sources, samples):
    for key, value in sources.iteritems():
        yield value, samples

def fn(sources, samples):
    for value1, value2 in itersources(sources, samples):
        do_something_with(value1, value2)
bruno desthuilliers
  • 75,974
  • 6
  • 88
  • 118
0

I use a "switch" method for this:

class Demo(object):
    def f(self, a):
        name = 'f_%s' % type(a).__name__
        m = getattr(self, name)
        m(a)

     def f_dict(self, a):
        ...

The code creates a method name from the type of the argument, then looks up the method in self and then calls it.

Aaron Digulla
  • 321,842
  • 108
  • 597
  • 820
  • Not clear how this is an improvement over (1) one function with isinstance() check. Actually I feel that this is less readable, especially: name = 'f_%s' % type(a).__name__. Also this loses out on the possibility of processing subclasses. (2) If one is implementing two functions anyways, one could as well ask the users to explicitly call type specific function. – subhacom Jul 22 '14 at 08:14
  • (1) It becomes more readable when you need to support 10+ types. The pattern supports subclasses but the code gets more complex: You either have to search the inheritance chain until you find a method or you need to write a method per subclass which calls the base method. (2) That only works if the user is aware that there are two functions and they know the argument type at runtime. In my case, neither is true. – Aaron Digulla Jul 22 '14 at 08:50