DeepDiff介绍
DeepDiff库常用来校验两个对象是否一致并找出差异处。
DeepDiff库由以下功能模块组成:
- DeepDiff:该模块通过递归方式比较两个字典、可迭代对象、字符串和其他对象的深度差异。
- DeepSearch:该模块支持在对象中搜索对象。
- Extract:该模块可以根据值抽取其Key的路径,反过来根据Key路径提取其值。
由于我们测试人员用的比较多的是DeepDiff,下面就重点讲讲这个类的使用(以下说明均参考官方文档,如果有高阶使用需求但本文未提及,可以直接去官方文档查~)
基本使用
基础用法
from deepdiff import DeepDiff
t1 = {1:1, 2:2, 3:3}
t2 = t1
# 同一对象返回{}
print(DeepDiff(t1, t2))
{}
t1 = {1:1, 2:2, 3:3}
t2 = {1:1, 2:"2", 3:3}
# 类型改变时会校验并返回差异(一个是int,一个str)
print(DeepDiff(t1, t2))
{'type_changes': {'root[2]': {'old_type': <class 'int'>, 'new_type': <class 'str'>, 'old_value': 2, 'new_value': '2'}}}
t1 = {1:1, 2:2, 3:3}
t2 = {1:1, 2:4, 3:3}
# 值改变时会校验并返回差异
print(DeepDiff(t1, t2))
{'values_changed': {'root[2]': {'new_value': 4, 'old_value': 2}}}
t1 = {1:1, 2:2, 3:3}
t2 = {1:1, 3:3, 5:5, 6:6}
# 有新增或减少key时会校验并返回差异key
print(DeepDiff(t1, t2))
{'dictionary_item_added': [root[5], root[6]], 'dictionary_item_removed': [root[2]]}
t1 = {1:1, 2:2, 3:3}
t2 = {1:1, 3:3, 5:5, 6:6}
# 有新增或减少key时,如果想key和value一起返回,把verbose_level设为2即可
print(DeepDiff(t1, t2, verbose_level=2))
{'dictionary_item_added': {'root[5]': 5, 'root[6]': 6}, 'dictionary_item_removed': {'root[2]': 2}}
Group_by
Group_by可以在处理字典列表使用,将对象转化为按Group_by定义的值分组对比。
t1 = [ {'id': 'AA', 'name': 'Joe', 'last_name': 'Nobody'}, {'id': 'BB', 'name': 'James', 'last_name': 'Blue'}, {'id': 'CC', 'name': 'Mike', 'last_name': 'Apple'},]
t2 = [ {'id': 'AA', 'name': 'Joe', 'last_name': 'Nobody'}, {'id': 'BB', 'name': 'James', 'last_name': 'Brown'}, {'id': 'CC', 'name': 'Mike', 'last_name': 'Apple'},]
print(DeepDiff(t1, t2))
{'values_changed': {"root[1]['last_name']": {'new_value': 'Brown', 'old_value': 'Blue'}}}
print(DeepDiff(t1, t2, group_by='id'))
{'values_changed': {"root['BB']['last_name']": {'new_value': 'Brown', 'old_value': 'Blue'}}}
定制diff
可以通过函数编写定制化的比较规则或前提:(更多使用方法可参考官方文档)
from deepdiff import DeepDiff
from deepdiff.operator import BaseOperator
class CustomClass:
def __init__(self, d: dict, l: list):
self.dict = d
self.dict['list'] = l
custom1 = CustomClass(d=dict(a=1, b=2), l=[1, 2, 3])
custom2 = CustomClass(d=dict(c=3, d=4), l=[1, 2, 3, 2])
custom3 = CustomClass(d=dict(a=1, b=2), l=[1, 2, 3, 4])
class ListMatchOperator(BaseOperator):
def give_up_diffing(self, level, diff_instance):
# 只有对象子类dict中的dict值(set后的值)不相等的话才比较
if set(level.t1.dict['list']) == set(level.t2.dict['list']):
return True
print(DeepDiff(custom1, custom2, custom_operators=[
ListMatchOperator(types=[CustomClass])]))
{}
print(DeepDiff(custom2, custom3, custom_operators=[
ListMatchOperator(types=[CustomClass])]))
{'dictionary_item_added': [root.dict['a'], root.dict['b']], 'dictionary_item_removed': [root.dict['c'], root.dict['d']], 'values_changed': {"root.dict['list'][3]": {'new_value': 4, 'old_value': 2}}}
Deep Distance-差异值的深度值
Deep Distance是两个对象之间的深度距离,它是一个介于0到1之间的浮点数Deep Distance是两个对象之间的深度距离,它是一个介于0到1之间的浮点数。值越高,则结果中展示的差异深度越高。
# 获取deep distance
print(DeepDiff(10.0, 10.1, get_deep_distance=True))
{'values_changed': {'root': {'new_value': 10.1, 'old_value': 10.0}}, 'deep_distance': 0.0014925373134328302}
Exclude Paths-排除不需比较的路径
t1 = {"for life": "vegan", "ingredients": ["no meat", "no eggs", "no dairy"]}
t2 = {"for life": "vegan", "ingredients": ["veggies", "tofu", "soy sauce"]}
# 指定ingredients这个路径不对比差异
print(DeepDiff(t1, t2, exclude_paths="root['ingredients']"))
{}
# 也可指定多个路径
print(DeepDiff(t1, t2, exclude_paths=["root['ingredients']","root['ingredients2']"]))
{}
也可使用正则指定路径
import re
t1 = [{'a': 1, 'b': 2}, {'c': 4, 'b': 5}]
t2 = [{'a': 1, 'b': 3}, {'c': 4, 'b': 5}]
print(DeepDiff(t1, t2, exclude_regex_paths=r"root\[\d+\]\['b'\]"))
# 另一种写法
exclude_path = re.compile(r"root\[\d+\]\['b'\]")
print(DeepDiff(t1, t2, exclude_regex_paths=[exclude_path]))
{}
Ignore Order-忽略对比的顺序
t1 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":[1, 2, 3]}}
t2 = {1:1, 2:2, 3:3, 4:{"a":"hello", "b":[1, 3, 2, 3]}}
# 默认的ignore_order=False
ddiff = DeepDiff(t1, t2)
pprint (ddiff, indent = 2)
{ 'iterable_item_added': {"root[4]['b'][3]": 3},
'values_changed': { "root[4]['b'][1]": {'new_value': 3, 'old_value': 2},
"root[4]['b'][2]": {'new_value': 2, 'old_value': 3}}}
ddiff = DeepDiff(t1, t2, ignore_order=True)
pprint (ddiff, indent = 2)
{}
Dynamic Ignore Order-可以设置忽略指定情况的顺序
from deepdiff import DeepDiff
t1 = {'a': [1, 2], 'b': [3, 4]}
t2 = {'a': [2, 1], 'b': [4, 3]}
print(DeepDiff(t1, t2, ignore_order=True))
{}
# 设置只忽略a路径的顺序
def ignore_order_func(level):
return 'a' in level.path()
print(DeepDiff(t1, t2, ignore_order=True, ignore_order_func=ignore_order_func))
{'values_changed': {"root['b'][0]": {'new_value': 4, 'old_value': 3}, "root['b'][1]": {'new_value': 3, 'old_value': 4}}}
同理我们也可以设置忽略type和value的差异
- ignore_string_type_changes=True : 忽略字符串类型的差异
- ignore_numeric_type_changes=True:忽略数字类型的差异
- ignore_string_case=True:忽略字母大小写的差异
- truncate_datetime='minute':时间的比较截止到分钟,只比较年月日小时
import datetime
from deepdiff import DeepDiff
d1 = {'a': datetime.datetime(2020, 5, 17, 22, 15, 34, 913070)}
d2 = {'a': datetime.datetime(2020, 5, 17, 22, 15, 39, 296583)}
print(DeepDiff(d1, d2, truncate_datetime='minute'))
{}
- Ignore Encoding Errors:忽略编码的差异
cutoff_distance_for_pairs-根据deep_distance的值设置比较的维度
使用cutoff_distance_for_pairs前提是ignore_order=True
from deepdiff import DeepDiff
t1 = [[1.0]]
t2 = [[20.0]]
# 设置的cutoff_distance_for_pairs值越大,比较的深度越深;
print(DeepDiff(t1, t2, ignore_order=True, cutoff_distance_for_pairs=0.3))
{'values_changed': {'root[0][0]': {'new_value': 20.0, 'old_value': 1.0}}}
print(DeepDiff(t1, t2, ignore_order=True, cutoff_distance_for_pairs=0.1))
{'values_changed': {'root[0]': {'new_value': [20.0], 'old_value': [1.0]}}}
Significant Digits-数字用于比较的位数
significant_digits 一个大于等于0的int值, default=None,significant_digits定义了小数点后用于比较的位数。
from deepdiff import DeepDiff
from decimal import Decimal
t1 = Decimal('1.52')
t2 = Decimal('1.57')
t3 = Decimal('1.53')
# 小数点后0位比较(近似比较)
print(DeepDiff(t1, t2, significant_digits=0))
{}
# 小数点后1位比较(近似比较)即1.5跟1.6对比
print(DeepDiff(t1, t2, significant_digits=1))
{'values_changed': {'root': {'new_value': Decimal('1.57'), 'old_value': Decimal('1.52')}}}
print(DeepDiff(t1, t3, significant_digits=1))
{}
一些本身的设置
Serialization 序列化
- ddiff.to_dict(view_override='text'):转化为dict
- ddiff.to_json():转化为json
View视图
- view=’tree’:设置视图为树
- DeepDiff(t1, t2).pretty():输出可读化结果
- 默认为text view:上面演示的输出
Stats and Logging
- DeepDiff(t1, t2, log_frequency_in_sec=1):按1s的频率输出操作日志
- diff.get_stats():在diff对象上运行get_stats()方法可以获取该对象的一些统计信息