I am attempting to generate a new colour and its contrasting colour with this python script.
I plan to try to make a web app that is different every time it is loaded (i.e. background and text colour)
I am wondering if there is a better and more efficient way to do this?
Here is my code:
import random
values = ['0','1','2','3','4','5','6','7','8','9','10','11','12','13','14','15']
value_dict = [['A','10'],['B','11'],['C','12'],['D','13'],['E','14'],['F','15']]
start = "#"
def contrastcolour(colour):
if colour[0] == '#':
colour = colour[1:]
rgb = (colour[0:2], colour[2:4], colour[4:6])
comp = ['%02X' % (255 - int(a, 16)) for a in rgb]
return ''.join(comp)
def startcolour():
colour = "#"
for i in range(6):
x = values[random.randint(0,len(values)-1)]
for thing in value_dict:
if x == thing[1]:
x = thing[0]
colour = colour + x
return colour
base = startcolour()
contrast = start + contrastcolour(base)
print("The colours: {0}".format([base, contrast]))
The sample output is:
The colours: ['#2EF7F3', '#D1080C']
-
\$\begingroup\$ It's a specification issue rather than a code issue, but it seems to me that you need a better contrasting-color algorithm. What contrasting color does your code generate for a medium grey (#7F7F7F)? \$\endgroup\$Mark– Mark2017年12月08日 01:14:04 +00:00Commented Dec 8, 2017 at 1:14
4 Answers 4
Review
Globals should be
ALL_CAPS
start
=>
START
Accumulation: Loop variables that are not explicitly used within the loop block should be replaced by
_
, as is standard Python style.for i in range(6):
You don't use the
i
later on, so the convention is to use_
for a dummy variable.=>
for _ in range(6)
The name of the variable
value_dict
implies it's adict
(Python's built-in dictionary type) but it's implemented as alist
.value_dict = [['A','10'],['B','11'],['C','12'],['D','13'],['E','14'],['F','15']] ... for thing in value_dict: if x == thing[1]: x = thing[0]
You could change this to use an actual
dict
like this:val_dict = {'10' : 'A', '11' : 'B', '12' : 'C', '13' : 'D', '14' : 'E', '15' : 'F'} ... if x in val_dict: x = val_dict[x]
Or better yet why not immediately convert to an hexadecimal using
hex
?Check your naming. Don't be overly generic.
values # thing # Each of these could literally be anything x #
colour = colour + x
can be rewritten ascolour += x
Use spaces around the comma:
a,b
=>a, b
Use a
if __name__ == '__main__'
guard
Essentially your startcolour()
can be rewritten as a one-liner
from random import randint
_HEX = list('0123456789ABCDEF')
...
def startcolour():
return '#' + ''.join(_HEX[randint(0, len(_HEX)-1)] for _ in range(6))
-
\$\begingroup\$ "Variables that are not used in loops should be replaced by _ as is Python idiom" I read this several times, and it wasn't until I read your code that I was able to come up with a guess as to what it's supposed to mean. Do you mean "Loop variables that are not explicitly used within the loop block should be replaced by
_
, as is Python idiom"? \$\endgroup\$Acccumulation– Acccumulation2017年12月07日 17:11:59 +00:00Commented Dec 7, 2017 at 17:11 -
\$\begingroup\$ What's the purpose of the trailing underscore at the end of the variable
HEX_
? \$\endgroup\$Peter Olson– Peter Olson2017年12月07日 17:25:43 +00:00Commented Dec 7, 2017 at 17:25 -
\$\begingroup\$ @PeterOlson making sure you don't conflict with anything in the namespace already called HEX \$\endgroup\$Snowbody– Snowbody2017年12月07日 18:17:17 +00:00Commented Dec 7, 2017 at 18:17
-
2\$\begingroup\$
list(...)
call is unnecessary as you can already index and iterate strings. You could also just userandom.choice(_HEX) for _ in range(6)
\$\endgroup\$Joshua Griffiths– Joshua Griffiths2017年12月07日 18:41:52 +00:00Commented Dec 7, 2017 at 18:41 -
1\$\begingroup\$ @JoshuaGriffiths This has been covered by another answer \$\endgroup\$Ludisposed– Ludisposed2017年12月07日 18:42:20 +00:00Commented Dec 7, 2017 at 18:42
To expand on Ludisposed's answer, you could instead use random.choice
, leaving you with:
_HEX = '0123456789ABCDEF'
def startcolour():
return '#' + ''.join(random.choice(_HEX) for _ in range(6))
I however would suggest you make a Colour
class, if you actually want to mutate colours. There are three values in a colour being red, blue and green. And so if you make a list that contains these three values, then you can focus on just that list, and not have to mutate to and from string, list, tuple, iterable, iterator.
And so I would use:
import random
_START = '#'
class Colour:
def __init__(self, value):
value = list(value)
if len(value) != 3:
raise ValueError('value must have a length of three')
self._values = value
def __str__(self):
return _START + ''.join('{:02X}'.format(v) for v in self)
def __iter__(self):
return iter(self._values)
def __getitem__(self, index):
return self._values[index]
def __setitem__(self, index):
return self._values[index]
@staticmethod
def from_string(string):
colour = iter(string)
if string[0] == _START:
next(colour, None)
return Colour(int(''.join(v), 16) for v in zip(colour, colour))
@staticmethod
def random():
return Colour(random.randrange(256) for _ in range(3))
def contrast(self):
return Colour(255 - v for v in self)
base = Colour.random()
print("The colours: {0}".format([str(base), str(base.contrast())]))
To make the code shorter, pick one random 6-digit hexadecimal number, rather than six random hexadecimal digits:
def start_colour():
random_colour = random.randint(0, 0xffffff) # inclusive range
return f'{START}{random_colour:06X}'
# older pythons would use: return '{0}{1:06X}'.format(START,random_colour)
likewise:
def contrast_colour(colour):
rgb = int(colour.lstrip(START), 16)
complementary_colour = 0xffffff-rgb
return f'{START}{complementary_colour:06X}'
The %
method for string interpolation is out-of-date.
-
2\$\begingroup\$
randCol
is a name that doesn't conform to PEP-8. Similarly, your spacing is non-conforming. However, you came closer than anyone advising on random number generation (at least you don't use string concatenation for it). Still, I think it's really strange to writeint('ffffff', 16)
, when you could write0xffffff
. \$\endgroup\$wvxvw– wvxvw2017年12月07日 15:45:04 +00:00Commented Dec 7, 2017 at 15:45 -
\$\begingroup\$ Be careful using a single integer for color. An RGB color is semantically a sequence of three integers, not a single value. This "contrast" operation works when treating color as a single number, but most other operations on colors will give bad results (e.g. interpolation). \$\endgroup\$starchild– starchild2017年12月07日 20:23:13 +00:00Commented Dec 7, 2017 at 20:23
-
\$\begingroup\$ @Snowbody It's still not PEP-8. I think you meant
random_colour
. \$\endgroup\$wizzwizz4– wizzwizz42017年12月07日 22:22:51 +00:00Commented Dec 7, 2017 at 22:22 -
\$\begingroup\$ I checked over PEP-8 and found a list of acceptable identifier formats; I didn't see that it recommended one over the others. Can you help me narrow it down? \$\endgroup\$Snowbody– Snowbody2017年12月07日 22:41:59 +00:00Commented Dec 7, 2017 at 22:41
-
\$\begingroup\$ @Snowbody You use
camalCase
while Python usessnake_case
for variables and functions PEP8#Naming \$\endgroup\$Ludisposed– Ludisposed2017年12月08日 09:29:29 +00:00Commented Dec 8, 2017 at 9:29
Unless you were explicitly tasked with generating numbers by concatenating strings, it doesn't really make sense to do it by concatenating strings.
Now, since you are operating on numbers, it makes sense for your functions to take numbers as input, not strings. This is in general a good practice: first convert the user's representation into your application's internal representation (sometimes this process is referred to as "normalization"), and then assume the format comfortable for your application.
Operations on colors, which are typically represented as a 24-bit integer, are one of the very few remaining areas of modern programming where low-level "bit magic" isn't frowned upon.
Below, is how'd I write it, using "bit magic" (not really magical).
import random
import functools
def start_color():
return random.randint(0xffffff)
def constrast_color(color):
r, g, b = color >> 16, color >> 8, color
return functools.reduce(
lambda a, b: (a << 8) | b,
map(lambda x: 0xff - (x & 0xff), [r, g, b]),
)
Test:
>>> '{:06X}'.format(constrast_color(0xffaa12))
'0055ED'
-
\$\begingroup\$ Did you consider simplifying your bit magic to '0xFFFFFF - color'? \$\endgroup\$Joshua Griffiths– Joshua Griffiths2017年12月07日 18:30:56 +00:00Commented Dec 7, 2017 at 18:30
-
\$\begingroup\$ +1 for operating on the color channels independently. It this case operating on the full color value works just as well, but in most cases it doesn't. \$\endgroup\$starchild– starchild2017年12月07日 20:25:57 +00:00Commented Dec 7, 2017 at 20:25