Welcome, guest | Sign In | My Account | Store | Cart

Although Python now has something similar to ternary operator with the result if ... else other result construction and this allows chaining (adding additional conditions on the if side, this soon becomes unreadable. A common use case is to filter values by ranges so I wrote the following when porting some code from PHP once I found I no longer understand the logic for a simple three-way filter.

Python, 29 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
    def range_comparison(op, checks, value, default=None):
        """
        An extensible ternary operator
        Takes an operator such as op.lt
        checks are tuples of sentinel values and results and must be supplied in
        the right order to suit the operator and prevent it matching greedily.
        ie. with lt checks must be in ascending order and descending or for gt
        """
        for sentinel, result in checks:
            if op(value, sentinel):
                return result
        return default

    class TestRangeComparison(TestCase):
    
        def make_one(self):
            from ..utils import range_comparison
            return range_comparison
    
        def test_lt(self):
            from operator import lt
            FUT = self.make_one()
            checks = [(10, "less than 10"),
                      (20, "less than 20"),
                      (100, "less than 100")]
            self.assertEqual(FUT(lt, checks, 5), "less than 10")
            self.assertEqual(FUT(lt, checks, 10), "less than 20")
            self.assertEqual(FUT(lt, checks, 50), "less than 100")
            self.assertFalse(FUT(lt, checks, 100))

It's very easy to describe filtering by range, say egg sizes, but surprisingly tricky to code this in an elegant and compact manner because you cannot use dictionary dispatch for range membership.

Simple identity-based testing can be done using dictionaries:

if a == 10:
    print("a is ten")
elif a == 20:
    print("a is twenty")
elif a == 30:
    print("a is thirty")
else:
    print("don't recognise a")

Can be written using dictionaroes

comp = dict(
    (10, "a is ten"),
    (20, "a is twenty"),
    (30, "a is thirty"),
)

print comp.get(a, "don't recognise a")

This is great because it is infinitely extensible but low on copy & paste typing so hopefully a source of fewer errors, and, with lots of cases, possibly faster.

But you cannot use this for ranges, say egg sizes. It is surprisingly tricky to code this in an elegant and compact manner. But fortunately Python has the operator module which allows operators to be treated as objects for programmatic manipulation.

1 comment

Charlie Clark (author) 9 years, 6 months ago  # | flag

Just spotted the invalid import in the Test

def make_one(self):
    return range_comparison

Should fix it or direct assignment of FUT to range_comparison