在设计单元测试时,我们往往需要将运行结果与预期结果相比较。但是,如果运行结果是一个比较复杂的数据结构的话,那么我们可能需要将预期结果硬编码到代码中,或存储到文件中,都需要占用一定的空间。为了减少单元测试预期结果的存储空间,我们考虑仅存储预期结果的哈希值,并通过比较哈希值来判断运行结果的准确性。

为此,我实现了一个工具类,用于将 Python 中的可变数据结构转换为不可变数据结构,从而支持数据结构哈希值的计算。

需要注意的是,因为 Python 中每次运行时会随机生成不同的环境变量 PYTHONHASHSEED,这就导致字符串(包括 str 类型和 bytes 类型)和其他一些对象虽然在每次运行中都能保证哈希值相同,但是每次运行之间则无法保证哈希值一定相同。

因此,我添加了 stable_str 参数和 stable_bytes 参数,如果这两个参数为 True,则会使用标准库 hashlib 的 md5 函数来将字符串压缩为十六进制的数字,以确保每次运行时的哈希值相同。如果这种处理方法不能满足您的需要,那么您可以通过重写 freeze_str 方法和 freeze_bytes 方法来解决。

如果工具类中对其他数据类型的不可变处理不满足您的需要,那么您同样可以通过重写该类型对应的处理方法来解决。

如果工具类中不包含您需要不可变处理的数据类型,那么您可以通过重写 default 方法,并在该方法中增加对该类型的处理来解决。这里与标准库 json 的 JSONEncoder 类的设计十分相似。

代码如下:

"""
Author: Changxing
Create: 2021-12-20
"""import hashlib
from collections import deque
from typing import Hashabletry:import numpy as np
except ImportError:np = Nonetry:import pandas as pd
except ImportError:pd = Noneclass FrozenData:"""将数据结构转换为不可变类型,从而支持计算数据结构的哈希值支持的数据类型:Args:stable_str <bool> 因为 Python 中每次运行时会随机生成不同的环境变量 PYTHONHASHSEED,这就导致字符串(包括str 类型和 bytes 类型)虽然在运行中能保证哈希值相同,但是每次运行时的哈希值则不一定相同。如果添加stable_str 这个参数,那么在 freeze 过程中会将字符串(str 类型)通过 MD5 算法转换为整数,从而保证每次运行时字符串的哈希值稳定化。默认值为 True。stable_bytes <bool> 因为 Python 中每次运行时会随机生成不同的环境变量 PYTHONHASHSEED,这就导致字符串(包括 str 类型和 bytes 类型)虽然在运行中能保证哈希值相同,但是每次运行时的哈希值则不一定相同。如果添加stable_str 这个参数,那么在 freeze 过程中会将字符串(bytes 类型)通过 MD5 算法转换为整数,从而保证每次运行时字符串的哈希值稳定化。默认值为 True。"""def __init__(self, stable_str: bool = True, stable_bytes: bool = True):self.stable_str = stable_strself.stable_bytes = stable_bytesdef default(self, obj):"""在子类中重写这个方法,需返回一个将对象 `obj` 冻结后的可哈希的对象;如果没有重写这个方法,那么遇到未知类型将会抛出 TypeError。"""raise TypeError(f"Object of type {obj.__class__.__name__} is not freezable")def hash(self, obj):"""返回对象 `obj` 冻结后对象的哈希值"""return hash(self.freeze(obj))def freeze(self, obj):"""返回对象 `obj` 冻结后的可哈希的对象。>>> FrozenData().freeze([123, {1234, 123, 12}, 2, "123"])(123, frozenset({1234, 123, 12}), 2, 42767516990368493138776584305024125808)"""# 处理字符串类型if isinstance(obj, str) and self.stable_str:return self.freeze_str(obj)if isinstance(obj, bytes) and self.stable_bytes:return self.freeze_bytes(obj)# 处理 numpy 数据类型if np:if isinstance(obj, np.ndarray):return self.freeze_np_ndarray(obj)# 处理 pandas 数据类型if pd:if isinstance(obj, pd.DataFrame):return self.freeze_pd_dataframe(obj)if isinstance(obj, pd.Series):return self.freeze_pd_series(obj)# 处理容器类类型if isinstance(obj, tuple):return self.freeze_tuple(obj)if isinstance(obj, set):return self.freeze_set(obj)if isinstance(obj, list):return self.freeze_list(obj)if isinstance(obj, dict):return self.freeze_dict(obj)if isinstance(obj, deque):return self.freeze_deque(obj)# 处理其他 Hashable 类型if isinstance(obj, Hashable):return obj# 处理其他未知类型的对象return self.default(obj)def freeze_set(self, obj: set):"""返回 set 类型的对象 `obj` 冻结后的可哈希对象"""return frozenset(self.freeze(c) for c in obj)def freeze_tuple(self, obj: tuple):"""返回 set 类型的对象 `obj` 冻结后的可哈希对象"""return tuple(self.freeze(c) for c in obj)def freeze_list(self, obj: list):"""返回 list 类型的对象 `obj` 冻结后的可哈希对象"""return tuple(self.freeze(c) for c in obj)def freeze_dict(self, obj: dict):"""返回 dict 类型的对象 `obj` 冻结后的可哈希对象"""return frozenset((self.freeze(k), self.freeze(v)) for k, v in obj.items())def freeze_deque(self, obj: deque):"""返回 deque 类型的对象 `obj` 冻结后的可哈希对象"""return tuple(self.freeze(c) for c in obj)def freeze_np_ndarray(self, obj: np.ndarray):"""返回 numpy.ndarray 类型的对象 `obj` 冻结后的可哈希对象"""return tuple(self.freeze(c) for c in obj)def freeze_pd_dataframe(self, obj: pd.DataFrame):"""返回 pandas.DataFrame 类型的对象 `obj` 冻结后的可哈希对象"""return tuple(self.freeze(row) for idx, row in obj.iterrows())def freeze_pd_series(self, obj: pd.Series):"""返回 pandas.Series 类型的对象 `obj` 冻结后的可哈希对象"""return frozenset((self.freeze(k), self.freeze(v)) for k, v in obj.items())@staticmethoddef freeze_str(obj: str):"""返回 str 类型的对象 `obj` 冻结后的可哈希对象"""return int(hashlib.md5(obj.encode()).hexdigest(), base=16)@staticmethoddef freeze_bytes(obj: bytes):"""返回 bytes 类型的对象 `obj` 冻结后的可哈希对象"""return int(hashlib.md5(obj).hexdigest(), base=16)

使用案例

测试代码:

if __name__ == "__main__":fz = FrozenData()frozen_data1 = fz.freeze([123, {1234, 123, 12}, 2, "12345", deque([1, 3])])print(hash(frozen_data1), frozen_data1)if np:frozen_data2 = fz.freeze([np.zeros((2, 3))])print(hash(frozen_data2), frozen_data2)if pd:frozen_data3 = fz.freeze([pd.DataFrame({"A": [1, 2]})])print(hash(frozen_data3), frozen_data3)

输出结果:

3655714760255887991 (123, frozenset({1234, 123, 12}), 2, 173447602773428053556316684567667297915, (1, 3))
2215184705033713445 (((0.0, 0.0, 0.0), (0.0, 0.0, 0.0)),)
-3378297251431396859 ((frozenset({(169836834567204038179966570894283554345, 1)}), frozenset({(169836834567204038179966570894283554345, 2)})),)

Python小工具:将对象转换为不可变类型并计算其哈希值相关推荐

  1. python将bytes转为对象_Python3中bytes类型转换为str类型

    Python3中bytes类型转换为str类型 Python 3最重要的新特性之一是对字符串和二进制数据流做了明确的区分.文本总是Unicode,由str类型表示,二进制数据则由bytes类型表示.P ...

  2. python小工具—图片转为字符txt

    python小工具-图片转为字符txt 图片转为字符txt python小工具-图片转为字符txt 效果展示 转换图片信息 图片信息转字符 完整代码 效果展示 转换图片信息 将图片的rgb色彩信息转为 ...

  3. 【Python小工具】若干图片合并生成动态图(.gif)

    相信很多学生党.上班族在日常的学习.科研.办公中总会有一些比较特殊的需求,本人作为一个理工科(非计算机相关专业)学生和大家一样.有时好不容易找到了比较心仪的工具,却发现还要收费,质量和使用的便捷性也不 ...

  4. Python 小工具:调用「百度翻译API」实现英汉互译及多语言翻译

    Python 小工具:调用「百度翻译 API」实现英汉互译及多语言翻译 API 简介 过程详解 完整代码 结果展示 附:官方 Demo - Python 2 版本 Python 小工具:调用「百度翻译 ...

  5. python小工具myqr生成动态二维码

    python小工具myqr生成动态二维码 (一)安装 (二)使用 (一)安装 命令: pip install myqr 安装完成后,就可以在命令行中输入 myqr 查看下使用帮助: myqr --he ...

  6. 自己整理实现的python小工具

    文章目录 记录一些自己整理实现的python小工具 python获取文件路径 pytho使用opencv进行图像拼接 记录一些自己整理实现的python小工具 python获取文件路径 因为有的程序需 ...

  7. 自制python小工具(3)——Gadgets1.1

    自制python小工具(3)--Gadgets 1.1 文章目录 自制python小工具(3)--Gadgets 1.1 1. 前言 2. 功能实现 2.1 主程序界面 2.1.1 标签与按钮 2.1 ...

  8. python小工具之pdf转excel

    python小工具记录 #xlwt只支持后缀xls文件 不支持xlxs文件 #openpyxl不支持xls文件 支持xlxs文件 import pdfplumber import xlwt wb = ...

  9. Python小工具之翻译词典

    Python小工具之翻译词典 功能实现:请求网易有道词典的接口,获取翻译结果 代码如下: # -*- coding:utf-8 -*- """ Python制作翻译词典 ...

最新文章

  1. 面试常备题---链表总结篇
  2. MFC Initinstance中DoModal()返回-1
  3. Oracle rac进阶管理专家指导系列文档
  4. Tomcat配置虚拟主机的两种方式
  5. linux下查看无线网卡的命令,lspci命令可看无线网卡 ifconfig看不到 如何操作
  6. 连载 | 知识图谱发展报告 2018 -- 前言
  7. mysql分布式写入_分布式系统知识点七:mysql读写分离简介(转载)
  8. 【转】Linux设备驱动之I/O端口与I/O内存
  9. Oracle的CaseWhen
  10. Wellcome Sanger研究所选择Arima Genomics作为HiC技术合作伙伴; 公司宣布针对高覆盖HiC的抢先体验活动
  11. asp.net WebResource.axd请求报404错误
  12. 如何快速制作一个漂亮的生日祝福网站
  13. eSPI自学笔记(一):前言和简介
  14. SQL——查询和1002号的同学学习的课程完全相同的其他同学的学号和姓名
  15. AtCoder Beginner Contest 164 E - Two Currencies
  16. 华为IT“智”存高远,普“慧”于民
  17. 伦敦银走势分析最新,十大国际黄金白银交易平台排名
  18. DNS协议分析(域名解析)
  19. 数列的极限和无穷大量
  20. 共享打印机查找计算机名,如何查找网络共享打印机

热门文章

  1. C#生成随机数100次都是一样的数
  2. ArcGIS API for Silverlight 绘制降雨路径动画
  3. C++静态成员函数与静态成员变量
  4. 关于1M=1024K 和1M=1000K的主要使用区别
  5. VS2017运行时控制台一闪即逝解决方法
  6. 单电源运放全波整流电路
  7. 【python学习】matplotlib图例分开显示
  8. 前端上传文件,multipart-formdata,boundary的使用
  9. C#一个完整判断18位身份证号正确函数
  10. NOI 2008 假面舞会