4

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
  • 1
    Methods that take any kind of value are about the worst thing you can do from a OOP point of view and that's why the code looks so ugly. Commented Aug 12, 2010 at 21:05

3 Answers 3

4

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.

Sign up to request clarification or add additional context in comments.

Comments

1

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

Comments

1

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.