4

How can we define several tab lengths in a python string? For example, we want to print the keys, value types and values of a dict nicely aligned (with varying sizes of keys and types):

my_dict = {
    "short_key": 4,
    "very_very_very_very_very_long_keys": 5.0
}

formatted_string_1 = '\n'.join([f"{k}:\t({type(v).__name__})\t{v}".expandtabs(10) for k, v in my_dict.items()])
print(f"Option 1 (.expandtabs(10)), first tab is too small:\n{formatted_string_1}")

formatted_string_2 = '\n'.join([f"{k}:\t({type(v).__name__})\t{v}".expandtabs(40) for k, v in my_dict.items()])
print(f"\n\nOption 2 (.expandtabs(40)), second tab is too large:\n{formatted_string_2}")

Running this we get:

Option 1 (.expandtabs(10)), first tab is too small:
short_key:          (int)     4
very_very_very_very_very_long_keys:     (float)   5.0

and:

Option 2 (.expandtabs(40)), second tab is too large:
short_key:                              (int)                                   4
very_very_very_very_very_long_keys:     (float)                                 5.0

I would like to be able to define a long tab for the first space, and a short tab for the second one, something like .expandtabs([40, 10]), such that we get two nice alignments:

short_key:                              (int)     4
very_very_very_very_very_long_keys:     (float)   5.0

Any idea?

Brian61354270
  • 8,690
  • 4
  • 21
  • 43
Julep
  • 760
  • 1
  • 6
  • 18

2 Answers2

2

Don't use tabs for alignment. You can specify your desired widths directly in the f-string's format spec:

print(
    '\n'.join(
        f"{f'{k}:':40}"
        f"{f'({type(v).__name__})':10}"
        f"{v:<}" 
        for k, v in my_dict.items()
    )
)

outputs

short_key:                              (int)     4
very_very_very_very_very_long_keys:     (float)   5.0

You can even use variable widths computed from the members of my_dict:

key_width = max(len(k) for k in my_dict) + 6
type_width = max(len(type(v).__name__) for v in my_dict.values()) + 5

print(
    '\n'.join(
        f"{f'{k}:':{key_width}}"
        f"{f'({type(v).__name__})':{type_width}}"
        f"{v:<}" 
        for k, v in my_dict.items()
    )
)

which again outputs

short_key:                              (int)     4
very_very_very_very_very_long_keys:     (float)   5.0
Brian61354270
  • 8,690
  • 4
  • 21
  • 43
-1

If your use case isn't string format (for which specifying widths directly is the superior solution), and you need to expand tabs in an existing string to different widths, you can use this function:

import itertools
from collections.abc import Iterable

def expand_tabs(
    s: str, 
    col_widths: Iterable[int] = (), 
    fillwidth: int = 8
) -> str:
    return ''.join(
        f"{part:{width}}" 
        for part, width in itertools.zip_longest(
            s.split("\t"), 
            col_widths, 
            fillvalue=fillwidth
        )
    )

Demo:

>>> expand_tabs("a\tb\tc", [2, 3, 4])
'a b  c   '
>>> expand_tabs("a\tb\tc", [4])
'a   b       c       '
>>> expand_tabs("a\tb\tc", [4], 2)
'a   b c '
>>> expand_tabs("a\tb\tc", fillwidth=2)
'a b c '

Note that this function uses fixed columns widths instead of padding entries to the next tab stop.

To produce the table in the question, this function can be used like so:

print(
    '\n'.join(
        expand_tabs(
            f"{k}:\t({type(v).__name__})\t{v}", 
            [40, 10],
        ) 
        for k,v in my_dict.items()
    )
)

which outputs

short_key:                              (int)     4   
very_very_very_very_very_long_keys:     (float)   5.0 
Brian61354270
  • 8,690
  • 4
  • 21
  • 43