It's pretty simple with the module operator. Here's an example:
>>> import operator
>>> bucket = (operator.ge, -100, operator.le, -5)
>>> def in_bucket(value, bucket): return bucket[0](value, bucket[1]) and bucket[2](value, bucket[3])
...
>>> in_bucket(-101, bucket)
False
>>> in_bucket(-100, bucket)
True
>>> in_bucket(-5, bucket)
True
>>> in_bucket(-4, bucket)
False
But you can do better, by defining a more generic structure:
>>> conditions = ((operator.ge, -100), (operator.le, -5))
>>> def match_conditions(value, conditions): return all(c[0](value, c[1]) for c in conditions)
...
>>> match_conditions(-101, conditions)
False
>>> match_conditions(-100, conditions)
True
>>> match_conditions(-5, conditions)
True
>>> match_conditions(-4, conditions)
False
The all operator returns true iff all conditions are met. The key difference between bucket and conditions is that you can add conditions that do not concern boundaries, e.g value must be pair:
>>> conditions = ((operator.ge, -100), (operator.le, -5), (lambda v, _: v%2==0, None))
>>> match_conditions(-7, conditions)
False
>>> match_conditions(-6, conditions)
True
>>> match_conditions(-5, conditions)
False
Now you can use a dictionary to summarize your conditions (first example you gave) :
>>> value_by_conditions = {
... ((operator.lt, -100),): -15,
... ((operator.ge, -100), (operator.le, -5)): -4,
... ((operator.gt, -5), (operator.le, 5)): 18,
... ((operator.gt, 5),): 88,
... }
>>> next((v for cs, v in value_by_conditions.items() if match_conditions(23, cs)), None)
88
>>> next((v for cs, v in value_by_conditions.items() if match_conditions(-101, cs)), None)
-15
>>> next((v for cs, v in value_by_conditions.items() if match_conditions(-100, cs)), None)
-4
Notes:
- I used tuples since lists are not hashable (and thus can't be used as dict keys) ;
next((x for x in xs if <test>), None) takes the first element in xs that passes the test. If no elements passes the test, it returns the default value None ;
- You have, in older versions of Python (< 3.7), no guarantee for the order of the tests. It's important if you have overlapping conditions.
- This is clearly suboptimal, because you test if
value < 100 then if value >= 100, etc.
Is this really pythonic? I'm, not so sure. Have a look at https://www.python.org/dev/peps/pep-0020/ to make your own idea.