打印Python词典的差异

Print diff of Python dictionaries

我想拿两本字典,把它们打印出来。这个差异应该包括键和值之间的差异。我创建了这个小片段,以使用unittest模块中的内置代码来实现结果。然而,这是一个令人讨厌的黑客,因为我必须将unittest.TestCase子类化,并提供一个runtest()方法使其工作。此外,此代码将导致应用程序出错,因为当存在差异时,它将引发AssertError。我真正想要的是打印差异。

1
2
3
4
5
6
7
8
9
import unittest
class tmp(unittest.TestCase):
    def __init__(self):
         # Show full diff of objects (dicts could be HUGE and output truncated)
        self.maxDiff = None
    def runTest():
        pass
_ = tmp()
_.assertDictEqual(d1, d2)

我希望使用difflib模块,但它看起来只适用于字符串。有没有办法解决这个问题,仍然使用difflib


改编自cpython来源:

https://github.com/python/cpython/blob/01fd68752e2d0a5f90ae8944ca35df0a5deaa/lib/unittest/case.py l1091

1
2
3
4
5
6
7
8
9
import difflib
import pprint

def compare_dicts(d1, d2):
    return ('
'
+ '
'
.join(difflib.ndiff(
                   pprint.pformat(d1).splitlines(),
                   pprint.pformat(d2).splitlines())))

你可以使用difflib,但是使用unittest方法对我来说更合适。但如果你想使用difflib。假设下面是两个听写。

1
2
3
4
5
In [50]: dict1
Out[50]: {1: True, 2: False}

In [51]: dict2
Out[51]: {1: False, 2: True}

您可能需要将它们转换为字符串(或字符串列表),然后将difflib用作正常业务。

1
2
3
4
5
6
7
8
9
10
11
12
In [43]: a = '
'
.join(['%s:%s' % (key, value) for (key, value) in sorted(dict1.items())])
In [44]: b = '
'
.join(['%s:%s' % (key, value) for (key, value) in sorted(dict2.items())])
In [45]: print a
1:True
2:False
In [46]: print b
1:False
2:True
In [47]: for diffs in difflib.unified_diff(a.splitlines(), b.splitlines(), fromfile='dict1', tofile='dict2'):
    print diffs

输出将是:

1
2
3
4
5
6
7
8
9
10
--- dict1

+++ dict2

@@ -1,2 +1,2 @@

-1:True
-2:False
+1:False
+2:True


我发现了一个名为datadiff的库(没有很好的文档记录),它给出了python中散列数据结构的不同之处。您可以使用pip或easy-install安装它。试试看!


您可以使用.items()和sets来执行如下操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> d = dict((i,i) for i in range(10))
>>> d2 = dict((i,i) for i in range(1,11))
>>>
>>> set(d.items()) - set(d2.items())
set([(0, 0)])
>>>
>>> set(d2.items()) - set(d.items())
set([(10, 10)])
>>>
>>> set(d2.items()) ^ set(d.items())  #symmetric difference
set([(0, 0), (10, 10)])
>>> set(d2.items()).symmetric_difference(d.items())  #only need to actually create 1 set
set([(0, 0), (10, 10)])

查看https://github.com/inveniosoftware/dictdiffer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
print list(diff(
    {2014: [
        dict(month=6, category=None, sum=672.00),
        dict(month=6, category=1, sum=-8954.00),
        dict(month=7, category=None, sum=7475.17),
        dict(month=7, category=1, sum=-11745.00),
        dict(month=8, category=None, sum=-12140.00),
        dict(month=8, category=1, sum=-11812.00),
        dict(month=9, category=None, sum=-31719.41),
        dict(month=9, category=1, sum=-11663.00),
    ]},

    {2014: [
       dict(month=6, category=None, sum=672.00),
       dict(month=6, category=1, sum=-8954.00),
       dict(month=7, category=None, sum=7475.17),
       dict(month=7, category=1, sum=-11745.00),
       dict(month=8, category=None, sum=-12141.00),
       dict(month=8, category=1, sum=-11812.00),
       dict(month=9, category=None, sum=-31719.41),
       dict(month=9, category=1, sum=-11663.00),
    ]}))

给出了我认为非常好的输出:

1
[('change', ['2014', 4, 'sum'], (-12140.0, -12141.0))]

也就是说,它给出了发生的事情:一个值"已更改",路径"['2014',4,'sum']",它从-12140.0更改为-12141.0。


使用@mgilson的解决方案,并进一步满足OP使用unittest模块的请求。

1
2
3
4
5
6
7
8
def test_dict_diff(self):
    dict_diff = list(set(self.dict_A.items()).symmetric_difference(set(self.dict_B.items()))))
    fail_message ="too many differences:
The differences:
"
+
                  "%s" %"
"
.join(dict_diff)
    self.assertTrue((len(dict_diff) < self.maxDiff), fail_message)


请参阅python配方以创建两个字典的差异(作为字典)。你能描述一下输出应该是什么样子吗(请附上一个例子)?