1-Python - deepdiff
about
DeepDiff模块常用来校验两个对象是否一致,并找出其中差异之处,它提供了:
- DeepDiff:字典,可迭代项,字符串和其他对象的深层差异。它将递归地查找所有更改。
- DeepSearch:在其他对象中搜索对象。
- DeepHash:根据对象的内容对其进行哈希处理。
另外,从DeepDiff v3 版本开始,在不同的数据中有两种不同的视图:文本视图(原始)和树视图(新);而DeepHash则是 v4 版本的新功能。
版本情况
DeepDiff适用于Python 3.4、3.5、3.6、3.7,Pypy3
注意:不再支持Python 2。DeepDiff v3.3.0是支持Python 2的最新版本。
下载
pip install deepdiff==4.3.2
DeepDiff
DeepDiff可以用来校验多种类型的文件内容,如txt、json、图片等......
校验:txt文件
from deepdiff import DeepDiff
"""
a.txt的内容是: abc
b.txt的内容是: abcd
"""
f1, f2 = open('a.txt', 'r', encoding='utf-8').read(), open('b.txt', 'r', encoding='utf-8').read()
print(DeepDiff(f1, f2)) # {'values_changed': {'root': {'new_value': 'abcd', 'old_value': 'abc'}}}
校验:json文件
a.json
文件内容:
{
"title": "V2EX",
"slogan": "way to explore",
"description": "创意工作者们的社区",
"domain": {
"url": "www.v2ex.com",
"host": "8080"
},
"data": [{"id":"5e4fa8531225c9423dcda9d8","author_id":"51f0f267f4963ade0e08f503","tab":"share"}, {"id":"5e16978581adfe260207a8c1","author_id":"54009f5ccd66f2eb37190485","tab":"share"}]
}
b.json
文件内容:
{
"title": "2VEX",
"slogan": "way to explore",
"description": "创意工作者们的社区",
"domain": {
"url": "www.v2ex.com",
"host": "8080"
},
"data": [{"id":"5e4fa8531225c9423dcda9d8","tab":"share"}, {"id":"5e16978581adfe260207a8c1","author_id":"54009f5ccd66f2eb37190485","tab":"share_we"}]
}
示例:
import json
from deepdiff import DeepDiff
f1, f2 = json.loads(open('a.json', 'r', encoding='utf-8').read()), json.loads(open('b.json', 'r', encoding='utf-8').read())
print(DeepDiff(f1, f2))
""" 对比结果
{
'dictionary_item_removed': [root['data'][0]['author_id']],
'values_changed': {
"root['data'][1]['tab']": {
'new_value': 'share_we',
'old_value': 'share'
},
"root['title']": {
'new_value': '2VEX',
'old_value': 'V2EX'
}
}
}
"""
校验字典
from deepdiff import DeepDiff
t1 = {1:1, 3: 3, 4: 4}
t2 = {1:1, 3: 3, 5: 5, 6: 6}
print(DeepDiff(t1, t2)) # {'dictionary_item_added': [root[5], root[6]], 'dictionary_item_removed': [root[4]]}
print(DeepDiff(t1, t2, view='tree')) # {'dictionary_item_removed': [<root[4] t1:4, t2:not present>], 'dictionary_item_added': [<root[5] t1:not present, t2:5>, <root[6] t1:not present, t2:6>]}
默认是文本视图;使用树视图请添加view='tree'
参数。
DeepSearch
DeepDiff附带了一个实用程序,可以找到要查找的项目的路径。它被称为Deep Search,它有一个类似于DeepDiff的接口。
如果你有个很深的嵌套结构对象,然后你去查找指定的元素(key和value都行)是否存在,并且返回该元素的路径,Deep Search是个好选择。
来看示例:
from deepdiff import grep
obj = {"long": "somewhere", "string": 2, 0: 0, "somewhere": "around", "a": {"b": {"c": {"d": "somewhere"}}}}
ds1 = obj | grep("somewhere")
print(ds1) # {'matched_paths': {"root['somewhere']"}, 'matched_values': {"root['long']", "root['a']['b']['c']['d']"}}
ds2 = obj | grep("someone")
print(ds2) # {}
如上例,指定元素存在则返回它的路径;不存在则返回一个空字典。
DeepHash
DeepHash以确定的方式根据对象的内容计算对象的哈希。这样,具有相同内容的2个对象应具有相同的哈希值。
DeepHash的主要用途是计算原本无法哈希的对象的哈希。例如,您可以使用DeepHash计算集合或字典的哈希!
DeepHash的核心是将对象确定性序列化为字符串,以便可以将其传递给哈希函数。默认情况下,它使用Murmur 3 128位哈希函数。但是您可以根据需要将另一个哈希函数传递给它。
来看示例:
from deepdiff import DeepHash
obj = {1: 2, 'a': 'b'}
# hash(obj) # TypeError: unhashable type: 'dict'
我们试图hash上述的字典,但是报错了。
来看使用DeppHash:
from deepdiff import DeepHash
obj = {1: 2, 'a': 'b'}
print(DeepHash(obj))
"""
{
1: 'c1800a30c736483f13615542e7096f7973631fef8ca935ee1ed9f35fb06fd44e',
2: '610e2bb86cee5362640bd1ab01b8a4a4559cced9dd6058376894c041629a7b69',
'a': '980410da9522db17c3ab8743541f192a5ab27772a6154dbc7795ee909e653a5c',
'b': 'd05faa460a5b4fbbfbd54286ef4e3080f5420c61daf22663163af098cd10182c',
'!>*id2118775877424': 'bf5478de322aa033da36bf3bcf9f0599e13a520773f50c6eb9f2487377a7929b'
}
"""
那么在这种情况下obj的哈希到底是什么?DeepHash正在计算obj和obj包含的任何其他对象的哈希。DeepHash的输出是对象ID与其哈希值的字典。为了获取obj本身的哈希,您需要使用对象(或对象的id)来获取其哈希:
from deepdiff import DeepHash
obj = {1: 2, 'a': 'b'}
hashes = DeepHash(obj)
print(DeepHash(obj))
print(hashes[obj]) # bf5478de322aa033da36bf3bcf9f0599e13a520773f50c6eb9f2487377a7929b
# 简写形式
print(DeepHash(obj)[obj]) # bf5478de322aa033da36bf3bcf9f0599e13a520773f50c6eb9f2487377a7929b
另外,DeepDiff倾向于使用Murmur3进行哈希处理。但是,您必须通过运行以下命令手动安装Murmur3:
pip install 'deepdiff[murmur]'
否则,DeepDiff将使用SHA256进行散列,这是加密散列,并且速度相当慢。
deepdiff[murmur]
安装遇到问题参考:https://deepdiff.readthedocs.io/en/latest/#troubleshoot
DeepDiff在单元测试中的应用
先来个unittest框架示例:
import unittest
import requests
from deepdiff import DeepDiff
class MyCase(unittest.TestCase):
expect = {
"title": "V2EX",
"slogan": "way to explore",
"description": "创意工作者们的社区",
"domain": "www.v2ex.com"
}
@classmethod
def setUpClass(cls):
cls.response = requests.get('https://www.v2ex.com/api/site/info.json').json()
def test_case_01(self):
self.assertEqual(DeepDiff(self.response, self.expect), {})
def test_case_02(self):
self.assertEqual(DeepDiff(self.response['title'], 'v2ex'), {})
"""
AssertionError: {'values_changed': {'root': {'new_value': 'v2ex', 'old_value': 'V2EX'}}} != {}
"""
if __name__ == '__main__':
unittest.main()
上述的第一个用例,由于实际的请求结果和预期值的json数据都一致,所以DeepDiff返回空字典,然后断言成功;而第二个用例我们修改预期值的title字段值,所以,DeepDiff返回了对比后的结果,与预期值不符,断言失败。
pytest框架中的使用套路是一样的:
import pytest
import requests
from deepdiff import DeepDiff
class TestCase(object):
expect = {
"title": "V2EX",
"slogan": "way to explore",
"description": "创意工作者们的社区",
"domain": "www.v2ex.com"
}
def setup_class(self):
self.response = requests.get('https://www.v2ex.com/api/site/info.json').json()
def test_case_01(self):
assert not DeepDiff(self.response, self.expect)
def test_case_02(self):
assert not DeepDiff(self.response['title'], 'v2ex')
"""
self = <temp.TestCase object at 0x00000272F7B6FE10>
def test_case_02(self):
> assert not DeepDiff(self.response['title'], 'v2ex')
AssertionError: {'values_changed': {'root': {'new_value': 'v2ex', 'old_value': 'V2EX'}}} != {}
E AssertionError: assert not {'values_changed': {'root': {'new_value': 'v2ex', 'old_value': 'V2EX'}}}
E + where {'values_changed': {'root': {'new_value': 'v2ex', 'old_value': 'V2EX'}}} = DeepDiff('V2EX', 'v2ex')
temp.py:104: AssertionError
"""
if __name__ == '__main__':
pytest.main(['-v', __file__])
see also: