3

I have a function that looks if a value is bigger, smaller or equal zero and depending on the result returns one of three colours. Also if a certain attribute is present it doesn't return one of the three colours but a separate fourth on. I solved it like this:

def set_colour(x, trigger=False):
    if x > 0.0:
        colour = 'green'
    elif x < 0.0:
        colour = 'red'
    else:
        colour = 'black'

    if trigger:
        colour = 'blue'

    return colour

But I don't like it. Is there a better way that is more elegant, efficient and pythonic? I found this post very interesting but can't use dictionaries since I am not checking for a static value but comparing the value.

Is there a possibility to define a variable for all numbers greater zero to test against in order to use the dictionary solution? Maybe with lambda? I tried some things but didn't get anywhere.

At the moment this solution that I have might be working okay but in future additional colours might be added for values smaller than -1.0, -2.0 or bigger 1.0 or 2.0 etc. and at that point the code will just get longer and longer for something that I feel can be solved more elegantly.

I think I check all relevant posts here so I hope this is not a duplicate.

Community
  • 1
  • 1
k3njiy
  • 151
  • 4
  • 14

5 Answers5

7

This is the only change I'd make:

def set_colour(x, trigger=False):
    if trigger:
        return 'blue'
    elif x > 0.0:
        return 'green'
    elif x < 0.0:
        return 'red'
    else:
        return 'black'
Yuval Adam
  • 161,610
  • 92
  • 305
  • 395
  • Thank you! ^_^ In this case I will just implement these minor changes. It's just that often I write some code that turns out to be not the best way to do it in python. So I was digging and hoping to find a new trick. – k3njiy Apr 17 '14 at 09:25
4

You could use a sign() function and index lookup:

import math
def sign(v):
    return int(math.copysign(1,v))

def set_color(x, trigger=False):
    if trigger: return "blue"
    return ('red', 'black', 'green')[sign(x)+1]

That said, I'm not sure how much "better" this is. "Clever" != "better" in the usual case.

EDIT Alternative which allows you to extend it easily for many color ranges:

colorRanges = (
    # start, color
    (-2, 'red'),
    (0, 'yellow'),
    (1, 'orange'),
)

def set_color(x, trigger=False):
    if trigger: return "blue"
    if x == 0.0: return 'black'

    for start, color in colorRanges:
        if x < start: return color

    return 'green'

This allows you to define color ranges easily but I don't have a good idea to handle the corner cases better.

Aaron Digulla
  • 321,842
  • 108
  • 597
  • 820
1

You can use nested conditional expression, like this

def set_colour(x, trigger=False):
    if trigger: return "blue"
    return "green" if x > 0.0 else "red" if x < 0.0 else "black"

assert set_colour(None, True)   == "blue"
assert set_colour(0.0, True)    == "blue"
assert set_colour(0.0, False)   == "black"
assert set_colour(0.01, False)  == "green"
assert set_colour(-0.01, False) == "red"
thefourtheye
  • 233,700
  • 52
  • 457
  • 497
  • I only used assert for unittests till now. Have to look into the apparent other possibilities of it. Thanks! ^_^ – k3njiy Apr 17 '14 at 09:31
1

This is quite readable and easy to extend:)

def set_colour(x, trigger=False):
    color = 'black'
    color = 'green' if x > 0.0 else color
    color = 'red' if x < 0.0 else color
    color = 'blue' if trigger else color

    return color
qwetty
  • 1,238
  • 2
  • 10
  • 24
  • Thank you! This is nicer to read especially if the list of different colours gets longer. I do prefer @Yuval Adam's version though since it might be a wee bit faster because of the immediate returns. Though I have read somewhere that you shouldn't use multiple returns. But for now this is a story for another day. ^_^ – k3njiy Apr 17 '14 at 09:36
1

You cannot run from the fact that you will need some conditions. However, you can list your conditions in a list, and corresponding colors in another list and then call the conditions. This way, you can separate your conditions form your code somewhat. This is because functions are first class values in Python.

You can do something like this:

conditions =  [  
   lambda m: m <  -3,
   lambda m: m >= -3 and m < -2,
   lambda m: m >= -3 and m < -2,
   lambda m: m >= -2 and m <  0,
   lambda m: m ==  0,
   lambda m: m >=  0 and m <  1,
   lambda m: m >= 1 ]

colors = [
   'color1',
   'color2',
   'color3',
   'color4',
   'color5',
   'color6',
   'color7' ]

def colVal(m): return colors[ [c(m) for c in conditions].index(True) ]

Here, colVal(-4) is 'color1', etc ...

This way you can keep appending your conditions list as you wish, and still keep the rest of the code intact. This is an idea with what you can do. However how you want to format your code is up to you. For example, you can do

def set_colour(x, trigger=False): 
    if trigger: return 'someColor'
    else: return colVal(x)

Not sure if this is what you want though.

ssm
  • 5,277
  • 1
  • 24
  • 42
  • :D This is actually exactly what I had in mind but failed to get to work! Although I must go with @Yuval Adam 's version because it does seem to be more efficient I really like this version and I will surely play around with it! Thank you very much! ^_^ – k3njiy Apr 17 '14 at 09:44
  • 1
    You're welcome. It is fun to think out-of-the-box, and your question seemed like the perfect question to play around with some of pythons features I wouldn't ordinarily use. So thanks for the question :). – ssm Apr 17 '14 at 09:49