Performance for simple code that converts a RGB tuple to hex string I'm rewriting a full color Mandelbrot Set explorer in Python using tkinter. For it, I need to be able to convert a Tuple[int, int, int] into a hex string in the form #123456. Here are example uses of the two variants that I came up with:

&gt;&gt;&gt;rgb_to_hex(123, 45, 6)
'#7b2d06'

&gt;&gt;&gt;rgb_to_hex(99, 88, 77)
'#63584d'

&gt;&gt;&gt;tup_rgb_to_hex((123, 45, 6))
'#7b2d06'

&gt;&gt;&gt;tup_rgb_to_hex((99, 88, 77))
'#63584d'

&gt;&gt;&gt;rgb_to_hex(*(123, 45, 6))
'#7b2d06'

&gt;&gt;&gt;rgb_to_hex(*(99, 88, 77))
'#63584d'


The functions I've come up with are very simple, but intolerably slow. This code is a rare case where performance is a real concern. It will need to be called once per pixel, and my goal is to support the creation of images up to 50,000x30,000 pixels (1500000000 in total‬). 100 million executions take ~300 seconds: 

&gt;&gt;&gt;timeit.timeit(lambda: rgb_to_hex(255, 254, 253), number=int(1e8))
304.3993674000001


Which, unless my math is fubared, means this function alone will take 75 minutes in total for my extreme case.

I wrote two versions. The latter was to reduce redundancy (and since I'll be handling tuples anyways), but it was even slower, so I ended up just using unpacking on the first version:

# Takes a tuple instead
&gt;&gt;&gt;timeit.timeit(lambda: tup_rgb_to_hex((255, 254, 253)), number=int(1e8))
342.8174099

# Unpacks arguments
&gt;&gt;&gt;timeit.timeit(lambda: rgb_to_hex(*(255, 254, 253)), number=int(1e8))
308.64342439999973


The code:

from typing import Tuple

def _channel_to_hex(color_val: int) -&gt; str:
    raw: str = hex(color_val)[2:]
    return raw.zfill(2)

def rgb_to_hex(red: int, green: int, blue: int) -&gt; str:
    return "#" + _channel_to_hex(red) + _channel_to_hex(green) + _channel_to_hex(blue)

def tup_rgb_to_hex(rgb: Tuple[int, int, int]) -&gt; str:
    return "#" + "".join([_channel_to_hex(c) for c in rgb])


I'd prefer to be able to use the tup_ variant for cleanliness, but there may not be a good way to automate the iteration with acceptable amounts of overhead.

Any performance-related tips (or anything else if you see something) are welcome.
You seem to be jumping through some unnecessary hoops. Just format a string directly:

from timeit import timeit


def _channel_to_hex(color_val: int) -&gt; str:
    raw: str = hex(color_val)[2:]
    return raw.zfill(2)


def rgb_to_hex(red: int, green: int, blue: int) -&gt; str:
    return "#" + _channel_to_hex(red) + _channel_to_hex(green) + _channel_to_hex(blue)


def direct_format(r, g, b):
    return f'#{r:02x}{g:02x}{b:02x}'


def one_word(r, g, b):
    rgb = r&lt;&lt;16 | g&lt;&lt;8 | b
    return f'#{rgb:06x}'



def main():
    N = 100000
    methods = (
        rgb_to_hex,
        direct_format,
        one_word,
    )
    for method in methods:
        hex = method(1, 2, 255)
        assert '#0102ff' == hex

        def run():
            return method(1, 2, 255)

        dur = timeit(run, number=N)
        print(f'{method.__name__:15} {1e6*dur/N:.2f} us')


main()


produces:

rgb_to_hex      6.75 us
direct_format   3.14 us
one_word        2.74 us


That said, the faster thing to do is almost certainly to generate an image in-memory with a different framework and then send it to tkinter.