Search code examples
pythonstring-formatting

Modifying a string template to use other variables as modifiers


I'm doing a helper class to make benchmarking loops easier, which consists on timing it's entire execution and each user defined amount of iterations:

import time

class LoopTimer:
    @staticmethod
    def run(fn, start, end, step = 1, batch_size = 1, precision = 8, 
            template = 'iteration = {current_iteration} \t took {batch_elapsed_time:.8f}s \t total = {total_elapsed_time:.8f}s \t value = {value}'):
        counter, counter_total = time.process_time(), time.process_time()
        for i in range(start, end, step):
            value = fn(i)
            if i > 0 & i % batch_size == 0:
                end = time.process_time()
                print(template.format_map({
                    'current_iteration':i,
                    'batch_elapsed_time': end-counter,
                    'total_elapsed_time': end-counter_total,
                    'value': value,
                    'batch_start': counter,
                    'batch_end': end,
                    'loop_start': counter_total
                }))
                counter = time.process_time()

How can users be able to define LoopTimer.precision so there's no need to create a new template to, for example, limit the current float precision (8)? Of course, it could be doable by preformatting the string like:

template = 'iteration = {current_iteration} \t took {batch_elapsed_time:.*precision1*f}s \t total = {total_elapsed_time:.*precision2*f}s \t value = '
print(template.replace('*precision1*', '1').replace('*precision2*', '9').format_map({
    'current_iteration':i,
    'batch_elapsed_time': end-counter,
    'total_elapsed_time': end-counter_total,
    'value': value,
    'batch_start': counter,
    'batch_end': end,
    'loop_start': counter_total
}))

But feels a bit dirty after seeing str.format capabilities.


EDIT

There's also a strange behaviour when formatting (using the template defined above):

iteration = 97   took 0.00000000s        total = 0.00000000s     value = 158456325028528675187087900672
iteration = 98   took 0.00000000s        total = 0.00000000s     value = 316912650057057350374175801344
iteration = 99   took 0.00000000s        total = 0.00000000s     value = 633825300114114700748351602688
iteration = 100          took 0.00000000s        total = 0.00000000s     value = 1267650600228229401496703205376
iteration = 101          took 0.00000000s        total = 0.00000000s     value = 2535301200456458802993406410752
iteration = 102          took 0.00000000s        total = 0.00000000s     value = 5070602400912917605986812821504

How could this be fixed?


Solution

  • One level of nested format is allowed so you can change the template to: template = 'iteration = {current_iteration} took {batch_elapsed_time:.{precision}f}s total = {total_elapsed_time:.{precision}f}s value = {value}' and then add 'precision': precision in the format_map().

    \t length is not fixed, it insert spaces to make the current substring 8 characters wide, then go to the next substring. For example, the output of print("1\t23\t456\t7890") is
    1 23 456 7890, 1 is followed by 7 spaces \t, 23 is followed by 6 spaces \t, 456 is followed by 5 spaces \t, each substring are 8 characters wide. Therefore, I used fixed 6 spaces is used in template.

    Test run for LoopTimer.run(lambda i: 2 ** i, 97, 103, precision = 4):

    iteration = 97      took 0.0000s      total = 0.0000s      value = 158456325028528675187087900672
    iteration = 98      took 0.0000s      total = 0.0000s      value = 316912650057057350374175801344
    iteration = 99      took 0.0000s      total = 0.0000s      value = 633825300114114700748351602688
    iteration = 100      took 0.0000s      total = 0.0000s      value = 1267650600228229401496703205376
    iteration = 101      took 0.0000s      total = 0.0000s      value = 2535301200456458802993406410752
    iteration = 102      took 0.0000s      total = 0.0000s      value = 5070602400912917605986812821504