I have this code which makes it easy to color a string in terminal but it is looking really repetitive. Is there a more idiomatic way of writing this? Also is there a functional way of doing this more effectively?
It seems really trivial, something like strings blue
,red
etc should point to a generic function, and when you invoke it, you should get the output depending on the caller's name!
But apparently such a thing doesn't exist unless I call it as arguments. But then I lose the flexibility of writing blue(bold(italics("Foo")))
which is really the best way to do this from an end user perspective.
Can I have a single function object in the memory and do this more effectively?
bcolors = {"BLUE": '033円[94m',
"HIGH": '033円[93m',
"OKAY": '033円[92m',
"FAIL": '033円[91m',
"BOLD": '033円[1m',
"LINE": '033円[4m',
"ENDC": '033円[0m'
}
def blue(string):
return bcolors["BLUE"] + string + bcolors["ENDC"]
def yellow(string):
return bcolors["HIGH"] + string + bcolors["ENDC"]
def green(string):
return bcolors["OKAY"] + string + bcolors["ENDC"]
def red(string):
return bcolors["FAIL"] + string + bcolors["ENDC"]
def bold(string):
return bcolors["BOLD"] + string + bcolors["ENDC"]
def line(string):
return bcolors["LINE"] + string + bcolors["ENDC"]
2 Answers 2
I came up with this:
for key in bcolors:
locals().update({key: lambda string: bcolors[key] + string + bcolors["ENDC"]})
which is almost equivalent to your code (except the function names are uppercase). There is still a function being created for every colour but it's more concise in writing.
It works for me but apparently changing locals() is a bad idea:
Others have suggested assigning to locals(). This won't work inside a function, where locals are accessed using the LOAD_FAST opcode, unless you have an exec statement somewhere in the function.
https://stackoverflow.com/questions/8028708/dynamically-set-local-variable-in-python
Note: The contents of this dictionary should not be modified; changes may not affect the values of local and free variables used by the interpreter.
https://docs.python.org/2/library/functions.html#locals
Another way I see is to write a function like so:
def _(color):
return lambda string: bcolors[color] + string + bcolors[color]
but you'd have to call it like this:
_("BLUE")("hello")
That's the closest I can come up with.
-
1\$\begingroup\$ Hack is fine, as long as you know it is one xD. Its also a means to explore the internals. \$\endgroup\$Nishant– Nishant2016年03月16日 08:55:28 +00:00Commented Mar 16, 2016 at 8:55
This is what I came with, it seems to be a much more generic and functional option. Personally, I would also use string formatting to fill color codes in string only when they are needed. Anyway, here is a sample:
def tag(attribute):
attribute = ''.join(('{', attribute, '}'))
endattr = "{ENDC}"
return lambda text: ''.join((attribute, text, endattr))
#Usage
blue = tag("BLUE")
text = blue("It's blue text")
print(text.format(**bcolors))
# or if not using format
blue = tag("033円[94m")
print(blue("It's another blue text"))
# but I find this much less readable
You may also try to achieve a completely custom behaviour by defining a __getattr__
method in a class. However, I don't believe this is complex enough to use it, unless you really need to use single function instance for this.
Sidenote: in Python, string cancatenation with +
is inefficient, use ''.join
instead.