Search code examples
pythonpython-3.xterminalstring-formattingtext-alignment

How to print a list of dicts as an aligned table?


So after going through multiple questions regarding the alignment using format specifiers I still can't figure out why the numerical data gets printed to stdout in a wavy fashion.

def create_data(soup_object,max_entry=None):
    max_=max_entry
    entry=dict()
    for a in range(1,int(max_)+1):

        entry[a]={'Key':a,
        'Title':soup_object[a].div.text.strip(),
        'Link':soup_object[a].div.a['href'],
        'Seeds':soup_object[a](attrs={'align':'right'})[0].text.strip(),
        'Leechers':soup_object[a](attrs={'align':'right'})[1].text.strip()}

        yield entry[a]

tpb_get_data=tuple(create_data(soup_object=tpb_soup.body.table.find_all("tr"),max_entry=5))
for data in tpb_get_data:
    print('{0} {1:<11}  {2:<25} {3:<25} '.format(data['Key'], data['Title'], data['Seeds'],data['Leechers']))

I tried using f-strings with the formatting specifiers but still it prints the data in the following way, can someone please help me figure this out.

 1 Salvation.S02E11.HDTV.x264-KILLERS  262         19 
 2 Salvation.S02E13.WEB.x264-TBS[ettv]  229         25 
 3 Salvation.S02E08.HDTV.x264-KILLERS  178         21 
 4 Salvation.S02E01.HDTV.x264-KILLERS  144          11 
 5 Salvation.S02E09.HDTV.x264-SVA[ettv]  129       14

I have read most of the questions regarding this, I would like to know if there is a raw method rather than using a library like tabulate which does an excellent job. But I also want to learn how to do this without any library.


Solution

  • As already mentioned, you calculated lengths of strings incorrectly.
    Instead of hardcoding them, delegate this task to your program.

    Here is a general approach:

    from operator import itemgetter
    from typing import (Any,
                        Dict,
                        Iterable,
                        Iterator,
                        List,
                        Sequence)
    
    
    def max_length(objects: Iterable[Any]) -> int:
        """Returns maximum string length of a sequence of objects"""
        strings = map(str, objects)
        return max(map(len, strings))
    
    
    def values_max_length(dicts: Sequence[Dict[str, Any]],
                          *,
                          key: str) -> int:
        """Returns maximum string length of dicts values for specific key"""
        return max_length(map(itemgetter(key), dicts))
    
    
    def to_aligned_data(dicts: Sequence[Dict[str, Any]],
                        *,
                        keys: List[str],
                        sep: str = ' ') -> Iterator[str]:
        """Prints a sequence of dicts in a form of a left aligned table"""
        lengths = (values_max_length(dicts, key=key) 
                   for key in keys)
    
        format_string = sep.join(map('{{:{}}}'.format, lengths))
    
        for row in map(itemgetter(*keys), dicts):
            yield format_string.format(*row)
    

    Examples:

    data = [{'Key': '1',
             'Title': 'Salvation.S02E11.HDTV.x264-KILLERS',
             'Seeds': '262',
             'Leechers': '19'},
            {'Key': '2',
             'Title': 'Salvation.S02E13.WEB.x264-TBS[ettv]',
             'Seeds': '229',
             'Leechers': '25'},
            {'Key': '3',
             'Title': 'Salvation.S02E08.HDTV.x264-KILLERS',
             'Seeds': '178',
             'Leechers': '21'},
            {'Key': '4',
             'Title': 'Salvation.S02E01.HDTV.x264-KILLERS',
             'Seeds': '144',
             'Leechers': '11'},
            {'Key': '5',
             'Title': 'Salvation.S02E09.HDTV.x264-SVA[ettv]',
             'Seeds': '129',
             'Leechers': '14'}]
    keys = ['Key', 'Title', 'Seeds', 'Leechers']
    print(*to_aligned_data(data, keys=keys),
          sep='\n')
    # 1 Salvation.S02E11.HDTV.x264-KILLERS   262 19
    # 2 Salvation.S02E13.WEB.x264-TBS[ettv]  229 25
    # 3 Salvation.S02E08.HDTV.x264-KILLERS   178 21
    # 4 Salvation.S02E01.HDTV.x264-KILLERS   144 11
    # 5 Salvation.S02E09.HDTV.x264-SVA[ettv] 129 14
    keys = ['Title', 'Leechers']
    print(*to_aligned_data(data, keys=keys),
          sep='\n')
    # Salvation.S02E11.HDTV.x264-KILLERS   19
    # Salvation.S02E13.WEB.x264-TBS[ettv]  25
    # Salvation.S02E08.HDTV.x264-KILLERS   21
    # Salvation.S02E01.HDTV.x264-KILLERS   11
    # Salvation.S02E09.HDTV.x264-SVA[ettv] 14
    keys = ['Key', 'Title', 'Seeds', 'Leechers']
    print(*to_aligned_data(data, keys=keys, sep=' ' * 5),
          sep='\n')
    # 1     Salvation.S02E11.HDTV.x264-KILLERS       262     19
    # 2     Salvation.S02E13.WEB.x264-TBS[ettv]      229     25
    # 3     Salvation.S02E08.HDTV.x264-KILLERS       178     21
    # 4     Salvation.S02E01.HDTV.x264-KILLERS       144     11
    # 5     Salvation.S02E09.HDTV.x264-SVA[ettv]     129     14
    

    See docs for more. There are examples with alignment as well.