【问题标题】:How to print a list of dicts as an aligned table?如何将字典列表打印为对齐表?
【发布时间】:2019-03-31 00:25:48
【问题描述】:

因此,在使用格式说明符解决了多个关于对齐的问题后,我仍然无法弄清楚为什么数字数据会以波浪方式打印到标准输出。

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']))

我尝试使用带有格式说明符的 f 字符串,但它仍然以以下方式打印数据,有人可以帮我解决这个问题。

 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

我已经阅读了大部分关于此的问题,我想知道是否有一种原始方法,而不是使用像 tabulate 这样做得很好的库。但我也想学习如何在没有任何库的情况下做到这一点。

【问题讨论】:

  • 您选择了奇怪的数字进行对齐。当这些字符串的长度至少为 34 时,为什么是 1:&lt;11?试试'{0} {1:&lt;40} {2:&lt;3} {3:&lt;2}'
  • 另外,这些不是 f-strings!使用f-strings,您将拥有print(f'{data['Key']} {data['Title']:&lt;40} {data['Seeds']:&lt;3} {data['Leechers']:&lt;2}')
  • @Georgy 我对格式很陌生,因此我不知道这些数字的作用。我知道那些不是 f-strings,我也使用过它们,但没有在此处发布它们。感谢您的意见。

标签: python python-3.x terminal string-formatting text-alignment


【解决方案1】:

很好的答案购买@Jongware,只是为了

  1. 让它更通用一点
  2. 没有硬编码的项目
  3. 打印任何类型的值,而不仅仅是字符串 -

这里是:

def print_list_of_dicts_as_table(list_of_dicts, keys=None):
    # assuming all dicts have same keys
    first_entry = list_of_dicts[0]
    if keys is None:
        keys = first_entry.keys()
    num_keys = len(keys)

    max_key_lens = [
        max(len(str(item[k])) for item in list_of_dicts) for k in keys
    ]
    for k_idx, k in enumerate(keys):
        max_key_lens[k_idx] = max(max_key_lens[k_idx], len(k))

    fmtstring = (' | '.join(['{{:{:d}}}'] * num_keys)).format(*max_key_lens)

    print(fmtstring.format(*first_entry.keys()))
    print(fmtstring.format(*['-'*key_len for key_len in max_key_lens]))
    for entry in list_of_dicts:
        print(fmtstring.format(*entry.values()))

使用示例:

a=[{'a':'asdd','b':'asd'},{'a':'a','b':'asdsd'},{'a':1,'b':232323}]
print_list_of_dicts_as_table(a)

输出:

a    | b     
---- | ------
asdd | asd   
a    | asdsd 
   1 | 232323

【讨论】:

    【解决方案2】:

    如前所述,您错误地计算了字符串的长度。
    与其对它们进行硬编码,不如将此任务委托给您的程序。

    这是一个通用的方法:

    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)
    

    示例:

    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
    

    请参阅docs 了解更多信息。也有对齐的例子。

    【讨论】:

    • 这看起来很棒,而且很干净。我能知道函数“values_max_length(dicts: Sequence[Dict[str, Any]],*, key: str) -> int”中的单个星号参数是什么意思吗?
    • 没关系,我找到了,stackoverflow.com/questions/2965271/… 抱歉。
    【解决方案3】:

    您得到一个未对齐的结果,因为您没有计算正确的标题长度。您只保留了 11 个字符,其中第一个字符已经是 34 个字符了。

    最简单的方法是让你的程序为你计数:

    key_len,title_len,seed_len,leech_len = ( max(len(item[itemname]) for item in tpb_get_data) for itemname in ['Key','Title','Seeds','Leechers'] )
    
    fmtstring = '{{:{:d}}} {{:{:d}}} {{:{:d}}} {{:{:d}}}'.format(key_len,title_len,seed_len,leech_len)
    
    for data in tpb_get_data:
        print(fmtstring.format(data['Key'], data['Title'], data['Seeds'],data['Leechers']))
    

    效果更好

    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
    

    (仅附加)

    这是一种更通用的方法,它使用要打印的键名列表,并且能够即时生成所有其他必需的变量。它不需要硬编码变量的名称,也不需要固定它们的顺序——顺序取自该列表。调整显示的项目都放在一个地方:同一个列表,get_items。可以在 fmtstring 行中更改输出分隔符,例如在项目之间使用制表符或更多空格。

    get_items = ['Key','Title','Leechers','Seeds']
    lengths = ( max(len(item[itemname]) for item in tpb_get_data) for itemname in get_items )
    fmtstring = ' '.join(['{{:{:d}}}' for i in range(len(get_items))]).format(*lengths)
    
    for data in tpb_get_data:
        print(fmtstring.format(*[data[key] for key in get_items]))
    

    它的工作原理如下:

    1. lengths 列表填充了从get_items 列表中获取的每个命​​名键的最大长度。
    2. 这会返回一个listfmtstring 为这些项目中的每一项重复格式指令{:d} 并填写数字。外部{{:}}format 翻译成{:},因此每个长度的最终结果将是{:<em>number</em>}。这些单独的格式字符串连接成一个较长的格式字符串。
    3. 最后,对实际数据的循环打印来自get_items 的项目。列表理解查找它们; * 表示法强制将列表作为单独的值“写出”,而不是将整个列表作为一个值返回。

    感谢@Georgy 建议寻找一个较少硬编码的品种。

    【讨论】:

    • 谢谢我现在看到了我的愚蠢。我从来没有数过长度。 @Georgy 也给了我正确的答案。非常感谢您的意见。
    • 这违反了 DRY
    • @usr2564301 The Zen of Python 说“平面比嵌套更好”但是现在你有了嵌套循环。仍然可以避免重复 {{:{:d}}}。而且,如果用户决定添加一个新的打印密钥,他将不得不在 5 个地方编辑此代码!看看我对此的尝试:link。如果问题被重新打开,我会在这里发布。无论如何,当 OP 询问错位背后的原因时,我收回了反对票,而不是最 Pythonic 的解决方案:)
    猜你喜欢
    • 2019-09-19
    • 2014-12-11
    • 2017-08-20
    • 1970-01-01
    • 1970-01-01
    • 2015-10-03
    • 1970-01-01
    • 2017-07-12
    • 2014-06-29
    相关资源
    最近更新 更多