views:

78

answers:

3

Python evangelists will say the reason Python doesn't have a switch statement is because it has dictionaries. So... how can I use a dictionary to solve this problem here? The problem is that all values are being evaluated some and raising exceptions depending on the input.

This is just a dumb example of a class that stores a number or a list of numbers and provides multiplication.

class MyClass(object):

    def __init__(self, value):
        self._value = value

    def __mul__(self, other):
        return {
            (False, False): self._value * other._value                        ,
            (False, True ): [self._value * o for o in other._value]           ,
            (True , False): [v * other._value for v in self._value]           ,
            (True , True ): [v * o for v, o in zip(self._value, other._value)],
        }[(isinstance(self._value, (tuple, list)), isinstance(other._value, (tuple, list)))]

    def __str__(self):
        return repr(self._value)
    __repr__ = __str__



>>> x = MyClass(2.0)
>>> y = MyClass([3.0, 4.0, 5.0])
>>> print x
2.0
>>> print y
[3.0, 4.0, 5.0]
>>> print x * y
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 8, in __mul__
TypeError: can't multiply sequence by non-int of type 'float'

One way that I could solve it would be to prefix each value with "lambda : " and at after the dictionary lookup call the lambda function .... "}(isinsta ...)"

Is there a better way?

+1  A: 

I can think of two approaches here:

  • Some if statements. For just four combinations of True and False, it's not that bad. Sequences of if ... elif ... elif ... clauses are, from what I've seen, not uncommon in Python code.

  • Creating the dict once (as a class field, rather than an instance field), and storing (lambda) functions inside it. This scales better than the previous approach and is faster for many options (although I don't know the value of "many").

Thomas
+1  A: 

I think the main point here is readability.
A dictionary lookup as the one you showed is definitely difficult to read, and therefore to maintain.

In my opinion, main goal while writing software should be readability; for this reason, I would go for a set of if/elif explicitely comparing the two values (instead of having the mapping for the types); then if measurements shows performance concerns, other solutions (like a dictionary lookup with functions) could be explored.

Roberto Liffredo
+1  A: 

Yes, define small lambdas for these different options:

    def __mul__(self, other): 
        scalar_times_scalar = lambda x,y: x*y
        scalar_times_seq    = lambda x,y: [x*y_i for y_i in y]
        seq_times_scalar    = lambda x,y: scalar_times_seq(y,x)
        seq_times_seq       = lambda x,y: [x_i*y_i for x_i,y_i in zip(x,y)]
        self_is_seq, other_is_seq = (isinstance(ob._value,(tuple, list)) 
                                                    for ob in (self, other))
        fn = {
            (False, False): scalar_times_scalar, 
            (False, True ): scalar_times_seq, 
            (True , False): seq_times_scalar, 
            (True , True ): seq_times_seq, 
            }[(self_is_seq, other_is_seq)] 
        return fn(self._value, other._value)

Ideally, of course, you would define these lambdas only once at class or module scope. I've just shown them in the __mul__ method here for ease of reference.

Paul McGuire