5
\$\begingroup\$

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"]
\$\endgroup\$
7
\$\begingroup\$

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.

|improve this answer|||||
\$\endgroup\$
  • 1
    \$\begingroup\$ Hack is fine, as long as you know it is one xD. Its also a means to explore the internals. \$\endgroup\$ – Nishant Mar 16 '16 at 8:55
3
\$\begingroup\$

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.

|improve this answer|||||
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.