有没有正确漂亮地打印OrderedDict的方法?

119
我喜欢Python中的pprint模块。我经常用它来进行测试和调试。我经常使用width选项确保输出适合我的终端窗口。
这一直很好,直到他们在Python 2.7中添加了新的ordered dictionary type(另一个我非常喜欢的很酷的功能)。如果我尝试漂亮地打印有序字典,它就不会显示得很好。整个东西不是每个键值对都在自己的行上,而是在一条长线上显示,这条长线会多次换行,很难阅读:
>>> from collections import OrderedDict
>>> o = OrderedDict([("aaaaa", 1), ("bbbbbb", 2), ("ccccccc", 3), ("dddddd", 4), ("eeeeee", 5), ("ffffff", 6), ("ggggggg", 7)])
>>> import pprint
>>> pprint.pprint(o)
OrderedDict([('aaaaa', 1), ('bbbbbb', 2), ('ccccccc', 3), ('dddddd', 4), ('eeeeee', 5), ('ffffff', 6), ('ggggggg', 7)])

这里有人知道如何使它像旧的无序字典一样打印得好看吗?如果我花足够的时间,可能可以使用PrettyPrinter.format方法来解决,但我想知道这里是否已经有人知道解决方案。

更新:我为此提交了一个错误报告。您可以在http://bugs.python.org/issue10592中查看它。


2
建议在 http://bugs.python.org/issue7434 中添加有关有序字典的注释。 - Ned Deily
这个问题现在已经在更新的Python 3版本中得到了修复。pprint将更好地处理OrderedDict对象。 - Flimm
很多人似乎误解了这个问题是关于dict对象的(在Python的新版本中保留插入顺序),而不是关于OrderedDict对象的。我修改了标题以使其更清晰。 - Flimm
如果您不想让 pprintdict 对象中的键进行排序,请参阅此问题:https://dev59.com/BF8e5IYBdhLWcg3wfajv - Flimm
我想要的是字符串,而不是打印帮助。pprint会破坏f-strings,所以对我来说毫无用处。我直接想要字符串。 - Charlie Parker
15个回答

151
自 Python 3.7 起,Python 确保字典中的键将保留其插入顺序。(然而,它们仍然不会像 OrderedDict 对象一样完全相同,因为即使键的顺序不同,两个字典 a 和 b 可以被认为是相等的 a == b,而 OrderedDict 在测试相等性时确实会检查这一点。)
Python 3.8 或更新版本: 您可以使用 sort_dicts=False 来防止它按字母顺序排序。
>>> example_dict = {'x': 1, 'b': 2, 'm': 3}
>>> import pprint
>>> pprint.pprint(example_dict, sort_dicts=False)
{'x': 1, 'b': 2, 'm': 3}

Python 3.7 或更早版本:

作为临时解决方法,您可以尝试使用 JSON 格式进行转储,而不是使用 pprint

虽然会失去一些类型信息,但它看起来很好,并且保持了顺序。

>>> import json
>>> print(json.dumps(example_dict, indent=4))
{
    "x": 1,
    "b": 2,
    "m": 3
}

7
为什么不直接使用pprint.pprint(dict(data))呢? - Alfe
3
如果您不关心键的顺序,pprint.pprint(dict(data)) 很好用。个人而言,我希望 OrderedDict__repr__ 会产生这样的输出但保留键的顺序。 - ws_e_c421
10
如果字典中有嵌套的OrderedDicts,它们将无法以漂亮的方式显示出来。 - Catskul
1
同样在整数键上失败 - DimmuR
4
因为这样输出的结果是无序的。使用OrderedDict而不是普通字典(dict)的原因是因为顺序很重要。 - Teekin
显示剩余5条评论

20
如果你的OrderedDict是按字母顺序排序的,那么以下内容将起作用,因为pprint会在打印之前对字典进行排序。
>>> from collections import OrderedDict
>>> o = OrderedDict([("aaaaa", 1), ("bbbbbb", 2), ("ccccccc", 3), ("dddddd", 4), ("eeeeee", 5), ("ffffff", 6), ("ggggggg", 7)])
>>> import pprint
>>> pprint.pprint(dict(o.items()))
{'aaaaa': 1,
 'bbbbbb': 2,
 'ccccccc': 3,
 'dddddd': 4,
 'eeeeee': 5,
 'ffffff': 6,
 'ggggggg': 7}

自从Python 3.7版本以后,Python保证字典中的键将保持其插入顺序。因此,如果您使用的是Python 3.7+,则不需要确保您的OrderedDict按字母顺序排序。


3
由于OrderedDict是按照插入顺序排序的,所以这可能只适用于少数情况。不管怎样,将OD转换为dict应该可以避免把所有内容都放在一行的问题。 - martineau
我想要的是字符串,而不是打印帮助。pprint会破坏f-strings,所以对我来说毫无用处。我直接想要字符串。 - Charlie Parker

9
这里有另一个答案,通过覆盖并在内部使用原始的pprint()函数来实现。与我之前的答案不同,它可以处理嵌套在其他容器(如list)中的OrderedDict,并且还应该能够处理给定的任何可选关键字参数 - 但它对输出的控制程度不如另一个方法。
它通过将原始函数的输出重定向到临时缓冲区,然后将其换行后发送到输出流。虽然最终产生的输出不是特别漂亮,但它还可以作为一种解决方法。
更新2.0
通过使用标准库的textwrap模块简化,并修改以在Python 2和3中运行。
from collections import OrderedDict
try:
    from cStringIO import StringIO
except ImportError:  # Python 3
    from io import StringIO
from pprint import pprint as pp_pprint
import sys
import textwrap

def pprint(object, **kwrds):
    try:
        width = kwrds['width']
    except KeyError: # unlimited, use stock function
        pp_pprint(object, **kwrds)
        return
    buffer = StringIO()
    stream = kwrds.get('stream', sys.stdout)
    kwrds.update({'stream': buffer})
    pp_pprint(object, **kwrds)
    words = buffer.getvalue().split()
    buffer.close()

    # word wrap output onto multiple lines <= width characters
    try:
        print >> stream, textwrap.fill(' '.join(words), width=width)
    except TypeError:  # Python 3
        print(textwrap.fill(' '.join(words), width=width), file=stream)

d = dict((('john',1), ('paul',2), ('mary',3)))
od = OrderedDict((('john',1), ('paul',2), ('mary',3)))
lod = [OrderedDict((('john',1), ('paul',2), ('mary',3))),
       OrderedDict((('moe',1), ('curly',2), ('larry',3))),
       OrderedDict((('weapons',1), ('mass',2), ('destruction',3)))]

样例输出:
pprint(d, width=40)

»   {'约翰': 1, '玛丽': 3, '保罗': 2}

pprint(od, width=40)

» 有序字典([('john', 1), ('paul', 2),
('mary', 3)])

pprint(lod, width=40)

» [有序字典([('john', 1), ('paul', 2),
('mary', 3)]), 有序字典([('moe', 1),
('curly', 2), ('larry', 3)]),
有序字典([('weapons', 1), ('mass',
2), ('destruction', 3)])]


我尝试了一下,它可以工作。正如你所说,它不是最漂亮的,但这是我目前看到的最好的解决方案。 - Elias Zamaria
我想要的是字符串,而不是打印帮助。pprint会破坏f-strings,所以对我来说毫无用处。我直接想要字符串。 - Charlie Parker
@Charlie:那可能是什么字符串? - martineau
@martineau 由 pprint 显示的那个是一个很好的选择。 - Charlie Parker
@Charlie:我至少想到两种方法可以在当前答案的代码中获取该字符串(以及其他一些方法)——一种是pprint()函数接受一个stream=关键字参数。第三种方法需要对其进行相对简单的更改。 - martineau

7
为了打印有序字典,例如:
from collections import OrderedDict

d=OrderedDict([
    ('a', OrderedDict([
        ('a1',1),
        ('a2','sss')
    ])),
    ('b', OrderedDict([
        ('b1', OrderedDict([
            ('bb1',1),
            ('bb2',4.5)])),
        ('b2',4.5)
    ])),
])

I do

def dict_or_OrdDict_to_formatted_str(OD, mode='dict', s="", indent=' '*4, level=0):
    def is_number(s):
        try:
            float(s)
            return True
        except ValueError:
            return False
    def fstr(s):
        return s if is_number(s) else '"%s"'%s
    if mode != 'dict':
        kv_tpl = '("%s", %s)'
        ST = 'OrderedDict([\n'; END = '])'
    else:
        kv_tpl = '"%s": %s'
        ST = '{\n'; END = '}'
    for i,k in enumerate(OD.keys()):
        if type(OD[k]) in [dict, OrderedDict]:
            level += 1
            s += (level-1)*indent+kv_tpl%(k,ST+dict_or_OrdDict_to_formatted_str(OD[k], mode=mode, indent=indent, level=level)+(level-1)*indent+END)
            level -= 1
        else:
            s += level*indent+kv_tpl%(k,fstr(OD[k]))
        if i!=len(OD)-1:
            s += ","
        s += "\n"
    return s

print dict_or_OrdDict_to_formatted_str(d)

产生哪些结果?
"a": {
    "a1": 1,
    "a2": "sss"
},
"b": {
    "b1": {
        "bb1": 1,
        "bb2": 4.5
    },
    "b2": 4.5
}

或者

print dict_or_OrdDict_to_formatted_str(d, mode='OD')

产生的结果是
("a", OrderedDict([
    ("a1", 1),
    ("a2", "sss")
])),
("b", OrderedDict([
    ("b1", OrderedDict([
        ("bb1", 1),
        ("bb2", 4.5)
    ])),
    ("b2", 4.5)
]))

5
这里有一种方法可以修改pprint的实现。 pprint在打印之前对键进行排序,因此为了保留顺序,我们只需要按照我们想要的方式排序键。请注意,这会影响items()函数。因此,在执行pprint之后,您可能需要保留和恢复覆盖的函数。
from collections import OrderedDict
import pprint

class ItemKey(object):
  def __init__(self, name, position):
    self.name = name
    self.position = position
  def __cmp__(self, b):
    assert isinstance(b, ItemKey)
    return cmp(self.position, b.position)
  def __repr__(self):
    return repr(self.name)

OrderedDict.items = lambda self: [
    (ItemKey(name, i), value)
    for i, (name, value) in enumerate(self.iteritems())]
OrderedDict.__repr__ = dict.__repr__

a = OrderedDict()
a[4] = '4'
a[1] = '1'
a[2] = '2'
print pprint.pformat(a) # {4: '4', 1: '1', 2: '2'}

2
不错,但最好是子类型化然后重写函数。 - xmedeko

4
这是我美化OrderDict的方法:
from collections import OrderedDict
import json
d = OrderedDict()
d['duck'] = 'alive'
d['parrot'] = 'dead'
d['penguin'] = 'exploded'
d['Falcon'] = 'discharged'
print(d)
print(json.dumps(d,indent=4))

OutPut:

OrderedDict([('duck', 'alive'), ('parrot', 'dead'), ('penguin', 'exploded'), ('Falcon', 'discharged')])

{
    "duck": "alive",
    "parrot": "dead",
    "penguin": "exploded",
    "Falcon": "discharged"
}

如果你想要按照排序后的键值对格式化字典
print(json.dumps(indent=4,sort_keys=True))
{
    "Falcon": "discharged",
    "duck": "alive",
    "parrot": "dead",
    "penguin": "exploded"
}

@AlxVallejo 你可能正在使用 python3。请检查一下。 - CHINTAN VADGAMA

2
def pprint_od(od):
    print "{"
    for key in od:
        print "%s:%s,\n" % (key, od[key]) # Fixed syntax
    print "}"

There you go ^^

for item in li:
    pprint_od(item)

或者

(pprint_od(item) for item in li)

我正在寻找一种方式,可以有一个函数既可以pretty-print OrderedDicts,也可以处理其他类型。我不知道如何使用你的函数来pretty-print,比如一组OrderedDicts的列表。 - Elias Zamaria
-1 pprint_od() 函数不起作用 - for key, item in od 语句导致 ValueError: too many values to unpack 错误,并且唯一的输出缩进是最终的 " }"print 语句中的 key, item 需要在括号内。 - martineau

2

我已经在Python3.5上测试了这个基于不可描述的猴子补丁的黑科技,它可以正常工作:

pprint.PrettyPrinter._dispatch[pprint._collections.OrderedDict.__repr__] = pprint.PrettyPrinter._pprint_dict


def unsorted_pprint(data):
    def fake_sort(*args, **kwargs):
        return args[0]
    orig_sorted = __builtins__.sorted
    try:
        __builtins__.sorted = fake_sort
        pprint.pprint(data)
    finally:
        __builtins__.sorted = orig_sorted

你可以让pprint使用通常基于字典的摘要,并在调用期间禁用排序,以便不会对打印进行任何键排序。

你也可以将 pretty_print.py 作为本地模块复制并进行修改(删除 sorted 调用或其他任何想要的修改)。 - Karl Rosaen

2

这很原始,但我只是需要一种可视化由任意映射和迭代器组成的数据结构的方法,这是我在放弃之前想出来的方法。它是递归的,因此可以很好地处理嵌套的结构和列表。我使用了collections中的Mapping和Iterable抽象基类来处理几乎所有内容。

我的目标是使用简洁的Python代码实现几乎yaml样式的输出,但没有完全实现。

def format_structure(d, level=0):
    x = ""
    if isinstance(d, Mapping):
        lenk = max(map(lambda x: len(str(x)), d.keys()))
        for k, v in d.items():
            key_text = "\n" + " "*level + " "*(lenk - len(str(k))) + str(k)
            x += key_text + ": " + format_structure(v, level=level+lenk)
    elif isinstance(d, Iterable) and not isinstance(d, basestring):
        for e in d:
            x += "\n" + " "*level + "- " + format_structure(e, level=level+4)
    else:
        x = str(d)
    return x

使用OrderedDict和OrderedDict列表来测试一些数据...(天啊,Python真的非常需要OrderedDict字面量...)

d = OrderedDict([("main",
                  OrderedDict([("window",
                                OrderedDict([("size", [500, 500]),
                                             ("position", [100, 900])])),
                               ("splash_enabled", True),
                               ("theme", "Dark")])),
                 ("updates",
                  OrderedDict([("automatic", True),
                               ("servers",
                                [OrderedDict([("url", "http://server1.com"),
                                              ("name", "Stable")]),
                                 OrderedDict([("url", "http://server2.com"),
                                              ("name", "Beta")]),
                                 OrderedDict([("url", "http://server3.com"),
                                              ("name", "Dev")])]),
                               ("prompt_restart", True)])),
                 ("logging",
                  OrderedDict([("enabled", True),
                               ("rotate", True)]))])

print format_structure(d)

产生以下输出:
   main: 
               window: 
                         size: 
                             - 500
                             - 500
                     position: 
                             - 100
                             - 900
       splash_enabled: True
                theme: Dark
updates: 
            automatic: True
              servers: 
                     - 
                          url: http://server1.com
                         name: Stable
                     - 
                          url: http://server2.com
                         name: Beta
                     - 
                          url: http://server3.com
                         name: Dev
       prompt_restart: True
logging: 
       enabled: True
        rotate: True

我在使用str.format()进行更好的对齐时有一些想法,但是并不想深入研究。您需要根据所需的对齐方式动态指定字段宽度,这可能会变得棘手或笨重。无论如何,这个方法可以将数据以易读的分层方式显示给我,对我来说这很有效!

2
您也可以使用这个 kzh 回答的简化版:
pprint(data.items(), indent=4)

它保留顺序并输出的结果与 webwurst 的回答(通过 json dump 打印)几乎相同。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接