views:

177

answers:

3

I need to convert (0, 128, 64) to something like this #008040. I'm not sure what to call the latter, making searching difficult.

+6  A: 

Use the format operator %:

>>> '#%02x%02x%02x' % (0, 128, 64)
'#008040'

Note that it won't check bounds...

>>> '#%02x%02x%02x' % (0, -1, 9999)
'#00-1270f'
Dietrich Epp
That is what I needed, thanks!
Anteater7171
+8  A: 
def clamp(x): 
  return max(0, min(x, 255))

"#{0:02x}{1:02x}{2:02x}".format(clamp(r), clamp(g), clamp(b))

This uses the preferred method of string formatting, as described in PEP 3101. It also uses min() and max to ensure that 0 <= {r,g,b} <= 255.

Update added the clamp function as suggested below.

Update From the title of the question and the context given, it should be obvious that this expects 3 ints in [0,255] and will always return a color when passed 3 such ints. However, from the comments, this may not be obvious to everyone, so let it be explicitly stated:

Provided three int values, this will return a valid hex triplet representing a color. If those values are between [0,255], then it will treat those as RGB values and return the color corresponding to those values.

Jesse Dhillon
Just one suggestion: `def clamp(x): return max(0, min(x, 255))`
Mark Ransom
Good suggestion. Thanks Mark, I'll update the solution.
Jesse Dhillon
@Jesse Dhillon: "clamping" == "silently failing". Try something like this: `if not(0 <= x <= 255): raise ValueError('rgb (%r) not in range(256)' % x)`
John Machin
@John, From a "programming by contract" perspective, clamping is not silently failing: we have a well-defined problem domain wherein values outside of 0-255 produce undefined behavior. Anyone using tools like the ones provided here can enforce a different contract at a higher level, but the only guarantee provided here is that valid colors will come out of the process. Someone else can provide the guarantee that only valid bytes will go in. Otherwise, we can say that it's silently failing until we check that inputs are not lists, unicodes, tuples, dragons, orcs, brunettes, etc.
Jesse Dhillon
@Jesse: Instead of muttering shibboleths like "programming by contract", consider the practical effect -- if one is to spend some effort adding a few extra lines of code, which is better: checking for correct input, or deliberately suppressing an error? If the often mythical "someone else" has checked the input, then clamping is pointless. BTW, since when do unicodes and brunettes satisfy `0 <= obj <= 255`?
John Machin
@John, If your command of the concept of contract programming is so inept that you consider it a buzzword, then I consider myself blessed for never having had to work with you. You don't seem to have a good answer for why I shouldn't include a check that `type(x).__name__ != 'list' and type(x).__name__ != 'brunette'`. It's as possible to pass those values into `clamp` as it is to pass `int`'s. Unless you're proposing that we observe some sort of convention only to pass `int` values into this function? An agreement on valid inputs? Perhaps a covenant of mutually acceptable conditions?
Jesse Dhillon
@Jesse: Sigh. "shibboleth" != "buzzphrase". `type(x).__name__ != 'list' etc` is ludicrous -- try `isinstance(x, int)`. Practical effect: if "someone else" does their job, any extra code here is wasted. Otherwise, checking highlights the problem, clamping conceals it. Concealing errors is bad. End of story.
John Machin
@John, When I write a web app I put the validators in model layer, not in my templates. I rely on "someone else" -- in this case myself at another time -- to make sure that only valid values come into or out of my database. I don't waste my time by running those values through schizophrenic re-validation schemes before they are displayed. I rely on guarantees about data validity made at other layers of the application, otherwise my template formatting functions would be bloated and full of duplicate code.
Jesse Dhillon
+3  A: 
triplet = (0, 128, 64)
print '#'+''.join(map(chr, triplet)).encode('hex')

or

from struct import pack
print '#'+pack("BBB",*triplet).encode('hex')

python3 is slightly different

from base64 import b16encode
print(b'#'+b16encode(bytes(triplet)))
gnibbler